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.