Wes Bragavim-wes
JavaScript & Productivity

How to Use Shared Run Steps in Github Actions

December 14, 2023
github-actions-composite-run-steps

GitHub Actions simplify and automate workflows, but managing repetitive steps across multiple action files can become cumbersome. One powerful solution to this issue is using Composite Run Steps. In this guide, we'll explore how to leverage this feature to streamline and reuse blocks of steps within your workflows.

What are Composite Run Steps?

Composite run steps are a way to reuse common steps across multiple workflows. They are defined in a single file and can be used in any workflow file in your repository.

Use Case Scenario:

Let's say we have a project that builds and deploys a Node.js application. We want to run unit tests, integration tests, and end-to-end tests.

What's common between all these actions is the following:

- name: Checkout code
  uses: actions/checkout@v3
- uses: action/setup-node@v3
  with: 
    node-version-file: .nvmrc
- run: npm ci
- run: npm run test
- name: Checkout code
  uses: actions/checkout@v3
- uses: action/setup-node@v3
  with: 
    node-version-file: .nvmrc
- run: npm ci
- run: npm run lint
- name: Checkout code
  uses: actions/checkout@v3
- uses: action/setup-node@v3
  with: 
    node-version-file: .nvmrc
- run: npm ci
- run: npm run build

Creating a Composite Run Step

We all need to install project dependencies. Fortunately, we can create what's called a composite run step that can be shared among all these actions.

Step 1: Create a Composite Run Step

  1. Create a new file in your repository under .github/shared/setup/action.yaml
  2. Add the following content to the file:
# .github/shared/setup/action.yaml
name: "Setup"
description: "Setup Node.js environment and dependencies"

runs:
  using: "composite"
  steps:
    - name: Checkout code
      uses: actions/checkout@v3
    - uses: action/setup-node@v3
      with: 
        node-version-file: .nvmrc
    - run: npm ci

Step 2: Include the Composite Run Step in Your Workflow

  1. Commit and push the file to your repository

  2. Now we can use this composite run step in any workflow file in our repository. For example, in our test.yaml

# .github/workflows/test.yaml
name: "Test"

on:
  pull_request:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
        # This value should be quoted and a path to the composite action
      - uses: "./.github/shared/setup"
      - run: npm run test

Same thing in our build workflow file:

# .github/workflows/build.yaml
name: "Build"

on:
  pull_request:
    branches:
      - main

jobs:
    build:
        runs-on: ubuntu-latest
        steps:
        # This value should be quoted and a path to the composite action
        - uses: "./.github/shared/setup"
        - run: npm run build

Step 3: Test Your Composite Run Step

Let's create a pull-request and watch these workflows run with the shared composite run step.

What Are the Benefits of Using Shared Composite Run Steps?

Simplicity: Composite run steps allow you to reuse common steps across multiple workflows, making them easier to read and maintain. Consistency: Ensures that common steps are consistent across multiple workflows, aiding readability and maintenance. One Place to Update: Updates common steps in one place.

Swapping Package Managers

For example, if I decide to swap package managers from npm to pnpm, I can update the composite run step, and it will reflect in all my workflows.


```yaml
name: "Setup with PNPM"

runs:
  using: "composite"
  steps:
    - name: Checkout code
      uses: actions/checkout@v3
    - name: Install Node.js
      uses: actions/setup-node@v3
      with:
        node-version: 16

      - uses: pnpm/action-setup@v2
        name: Install pnpm
        with:
          version: 8
          run_install: false

      - name: Get pnpm store directory
        shell: bash
        run: |
          echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

      - uses: actions/cache@v3
        name: Setup pnpm cache
        with:
          path: ${{ env.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - name: Install dependencies
        run: pnpm install

Trade-offs

Like any abstraction in programming, there are trade-offs to using composite run steps that you should be aware of:

One Place to Break Them All: Given it's a shared step, if you break it, you break it for all workflows that use it. This is why it's important to test your composite run steps before pushing them to production. Complexity by Obscurity: It can be confusing for some developers to understand where the steps are being defined.

Conclusion

Composite run steps are a powerful feature of GitHub Actions that allow you to reuse common steps across multiple workflows, making them easier to read and maintain. Check out this repository for an example of how I use them for my open-source project mktable, and I hope you find this article helpful. Cheers!

Resources