GitHub: Using Dynamic Matrices

Overview

This blog post demonstrates how to set up a GitHub Actions workflow that uses a dynamic matrix strategy based on configurations stored in an external JSON file. This approach allows you to define multiple job configurations outside of your main workflow file, promoting reusability and maintainability.

Explanation with Inline Comments

jobs:
  # Job to prepare the matrix configuration from an external JSON file
  prepare-matrix:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}  # Output the matrix to be used by other jobs
    steps:
      - uses: actions/checkout@v2  # Checks out your repository under $GITHUB_WORKSPACE
      - id: set-matrix
        run: |
          # Using jq to parse the matrix-config.json file and convert it to a compact JSON string
          MATRIX_JSON=$(jq -c . < ./matrix-config.json)
          # Echo the matrix JSON into the GitHub Actions output variable
          echo "matrix=${MATRIX_JSON}" >> $GITHUB_OUTPUT

  # Deployment job that uses the matrix prepared in the prepare-matrix job
  deploy:
    needs: prepare-matrix  # Indicates that this job needs the output of prepare-matrix
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false  # Disable fail-fast to continue other matrix jobs even if one fails
      matrix: ${{fromJson(needs.prepare-matrix.outputs.matrix)}}  # Parse JSON output to matrix
    steps:
      - name: test
        run: |
          # Print the first name (fname) from the matrix configuration to demonstrate usage
          echo ${{ matrix.fname }}
{
  "include": [
    // Each object in this array represents a different configuration for the matrix
    {"fname": "f name 1", "lname": "l name 1"},
    {"fname": "f name 2", "lname": "l name 2"},
    {"fname": "f name 3", "lname": "l name 3"},
    {"fname": "f name 4", "lname": "l name 4"}
  ]
}

Defining a Matrix Configuration

Let’s start by defining a matrix configuration in an external JSON file named matrix-config.json. Each object in the JSON array represents a different configuration for the matrix.

{
  "include": [
    {"fname": "f name 1", "lname": "l name 1"},
    {"fname": "f name 2", "lname": "l name 2"},
    {"fname": "f name 3", "lname": "l name 3"},
    {"fname": "f name 4", "lname": "l name 4"}
  ]
}

Generating Matrix Configurations

Next, we’ll create a GitHub Actions workflow that prepares the matrix configuration from the matrix-config.json file. The prepare-matrix job will parse the JSON file using jq and output the matrix to be used by other jobs.

jobs:
  prepare-matrix:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
      - uses: actions/checkout@v2
      - id: set-matrix
        run: |
          MATRIX_JSON=$(jq -c . < ./matrix-config.json)
          echo "matrix=${MATRIX_JSON}" >> $GITHUB_OUTPUT

Using Matrix Configurations

Finally, we’ll create a deploy job that utilizes the matrix prepared in the prepare-matrix job. By specifying needs: prepare-matrix, we ensure that this job waits for the matrix to be generated before executing. The matrix is parsed from the JSON output using ${{fromJson(needs.prepare-matrix.outputs.matrix)}}.

  deploy:
    needs: prepare-matrix
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix: ${{fromJson(needs.prepare-matrix.outputs.matrix)}}
    steps:
      - name: test
        run: |
          echo ${{ matrix.fname }}