Simplifying GitHub Workflows with Matrix Configurations

I came across GitHub Actions matrix strategies during a failed attempt in trying to derive a self-hosted runner label from workflow environment variables.

Below is a sample of the workflow that failed to run.

name: Workflow will fail

on:
  push:

env:
  runner_os: ubuntu
  runner_arch: amd64
  runner_size : large

jobs:
  build:
    runs-on: ${{ env.runner_os }}-${{ env.runner_arch }}-${{ env.runner_size }}
    steps:
    - name: run basic test
      run: |
        echo ${{ env.runner_os }}
        echo ${{ env.runner_arch }}     
        echo ${{ env.runner_size }} 

I was expecting:

runs-on: ${{ env.runner_os }}-${{ env.runner_arch }}-${{ env.runner_size }}

to resolve to the following:

runs-on: ubuntu-amd64-large

Instead, the workflow failed:

Image 27

Turns out that runs-on: will only resolve expressions referencing the following contexts:

github, inputs, vars, needs, strategy, matrix

Working within the constraints of the current development project, the only feasible option was to go with: strategy, matrix.

To resolve the issue, the workflow was modified to reference variables defined at the the strategy, matrix level.

name: Workflow will pass

on:
  push:

jobs:
  build:
    strategy:
      matrix:
        include:
          - runner_os: ubuntu
            runner_arch: amd64
            runner_size: large
    runs-on: ${{ matrix.runner_os }}-${{ matrix.runner_arch }}-${{ matrix.runner_size }}
    steps:
    - name: run basic test
      run: |
        echo ${{ matrix.runner_os }}
        echo ${{ matrix.runner_arch }}     
        echo ${{ matrix.runner_size }}   

The workflow resolved the runner label as expected.

Image 26

This particular use-case of strategy/matrix could be considered as “off-label”, and fails to elaborate on additional benefits of using strategy/matrix within workflows: job parallelism and concurrency.

Expand on Learnings: Creating a Basic Multi-dimensional Matrix

Take the case where you wish to concurrently deploy an AWS app to two different accounts, i.e. SIT and UAT.

The app and account details are:

Using a multi-dimension matrix, the sample template workflow below would deploy the app in parallel to the target AWS accounts:

name: Multi-dimension matrix sample

on:
  push:

jobs:
  deploy_to_aws:
    strategy:
      matrix:
        deploy_to: [SIT, UAT]
        app_name: [new-app]
        include:
          - deploy_to: SIT
            aws_acct_id: 123456789012
            cluster_name: cluster-system-integration-test
          - deploy_to: UAT
            aws_acct_id: 234567890123
            cluster_name: cluster-user-acceptance-test
    runs-on: ubuntu-latest
    steps:
      - name: "Checkout"
        uses: actions/checkout@v4

      - name:
        run: |
          echo "Deploying application: ${{ matrix.app_name }} to target environment: ${{ matrix.deploy_to }}"
          echo "Target AWS Account id: ${{ matrix.aws_acct_id }}"
          echo "Target Cluster name:  ${{ matrix.cluster_name }}"

The workflow run would generate the following jobs:

  • {deploy_to: SIT, app_name: new-app}
    • the matrix’ include attribute allows expansion of the SIT configuration so that it includes elements which are specific to SIT context, i.e:
      • aws_acct_id: 123456789012
      • cluster_name: cluster-system-integration-test
  • {deploy_to: UAT, app_name: new-app}
    • again, the configuration is expanded through the include by adding
      • aws_acct_id: 234567890123
      • cluster_name: cluster-user-acceptance-test

Running the workflow produces the following output:

Image 28