How to use the new composite GitHub Actions for reusability?

GitHub actions is the best thing that happened to CI/CD world recently. Its composite actions have been there for quite sometime now but with a very limited capability of running only the shell commands. Recently, GitHub actions made it possible to make composite actions much more versatile with the support for uses keyword.

Previously, if you wanted to re-use some of the steps in your multiple workflow files which used other actions, there was no way to do it, but now it’s possible. Imagine your team manages multiple repositories or may be you have different workflows in the same repo and you have to repeat some basic/common steps in each and every repository/workflow and then include the maintenance burden. 😣

We at Event Espresso have a monorepo of our JavaScript codebase, for which we run the PR checks and multiple deployment workflows. All these workflows have some basic steps repeated in each of them:

  • Checking out git repo
  • Setting up Node environment
  • Restoring dependencies from cache (if found)
  • Installing dependencies
  • Caching dependencies (if needed)

The simplest workflow would look like this:

# pr-checks-workflow.yml

name: Pull Request checks

on: [pull_request]

jobs:
    build:
        runs-on: ubuntu-latest
        name: Build the code
        steps:
            - name: Checkout the commit
              uses: actions/checkout@v2

            - name: Set up Node
              uses: actions/setup-node@v2
              with:
                  node-version: lts/*

            - name: Cache dependencies
              id: cache-deps
              uses: actions/cache@v2
              with:
                  path: '**/node_modules'
                  key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}

            - name: Install dependencies
              # install deps only if lockfile has changed
              if: steps.cache-deps.outputs.cache-hit != 'true'
              run: yarn install --frozen-lockfile

            - name: Run build
              run: yarn build

An example can be seen here.

Now, barring the last step, all those steps had to be repeated in every workflow. Even, we had to repeat it in the same workflow for two different jobs, which obviously was a bit of a pain to maintain. But, after GitHub added support for uses keyword to composite actions, the job became much easier. We extracted those repeated steps into a separate composite action and I named it checkout-and-yarn 😄. You can see it here.

# checkout-and-yarn/action.yml

name: 'Checkout and run yarn'
description: 'This action checks out the commit, sets up Node and installs deps using yarn.'
author: 'eventespresso'
inputs:
    fetch-depth:
        required: false
        description: 'Number of commits to fetch during checkout. 0 indicates all history for all branches and tags.'
        default: '1'
    token:
        required: false
        description: 'Personal access token (PAT) used to fetch the repository.'
        default: ${{ github.token }}
runs:
    using: 'composite'
    steps:
        - name: Checkout the commit
          uses: actions/checkout@v2
          with:
              fetch-depth: ${{ inputs.fetch-depth }}
              token: ${{ inputs.token }}

        - name: Set up Node
          uses: actions/setup-node@v2
          with:
              node-version: lts/*

        - name: Cache dependencies
          id: cache-deps
          uses: actions/cache@v2
          with:
              path: '**/node_modules'
              key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}

        - name: Install dependencies
          # install deps only if lockfile has changed
          #   TODO enable 'if' when composite actions support it
          #   if: steps.cache-deps.outputs.cache-hit != 'true'
          run: yarn install --frozen-lockfile
          shell: bash

Now can re-use this action in all our workflows like this

# pr-checks-workflow.yml

name: Pull Request checks

on: [pull_request]

jobs:
    build:
        runs-on: ubuntu-latest
        name: Build the code
        steps:
            - name: Checkout and yarn
              uses: eventespresso/actions/packages/checkout-and-yarn@main

            - name: Run build
              run: yarn build

Now you can see how much repetition has been avoided after using that composite action.

Just in case you are wondering what eventespresso/actions is, it’s just another monorepo for all our custom GitHub actions which can be used by anyone.

Another example of our use of the new composite actions can be seen here for setting up LAMP stack for our back-end codebase workflows. That action:

  • Checks out git repo
  • Sets up PHP environment
  • Starts the MySQL service
  • Restores composer dependencies from cache (if found)
  • Installs composer dependencies
  • Caches composer dependencies (if needed)

We are happy that we switched to GitHub Actions for all our CI/CD needs and we are going to extensively use it in future for all our release pipeline as well. I will try to write that someday.

Tags: , , ,