Monday 15 April 2024

Dynamically Control Matrix of Jobs in GitHub Actions Based on Input Parameter Value

 We can use matrix in GitHub Actions to use a single defnition of job to create multiple jobs as described here in the documentation. Let's say we input the list of application names we want to build, as an input parameter to the action workflow, and need to have the ability to remove the items from the app list at the time of triggering it manually (run workflow). For example, we have 4 apps by default. However, when we need we should be able to build only one or two out of them using the same action workflow without having to change, the workflow defintion. In this post let's explore how we can achieve that with GitHub actions workflow, utilizing the matrix strategy, and dynamical setting the matrix value.

Expected outcome

To understand what we need, let's look at what we are expecting to acheive.

When we trigger the workflow without any change to input parameter of app list, with its default value there should be 4 build and push jobs executed building and pushing each app docker image. As you can see in beow image all 4 apps are built using 4 jobs in matrix strategy.



Then we trigger the workflow and change input app list to only have two apps built as shown below.


Now using same wokflow we only run two jobs in the matrix strategy to build and push two apps as provided in the input.


How to do

Lets look at how to do this step by step. Understanding the below two posts by me will help to understand the code in this post better.


In .github/workflows/all_apps_cicd.yml we can setup below code

on: 
  push:
  workflow_dispatch:
    inputs:
      apps:
        description: 'App to deploy'
        type: string
        default: '["customer_api", "invoice_api", "order_api", "payment_api"]'
        required: true

env:
  apps: ${{ github.event_name == 'push' && '["customer_api", "invoice_api", "order_api", "payment_api"]' || inputs.apps}} # https://docs.github.com/en/actions/learn-github-actions/expressions#example

name: all_apps_cicd

jobs:
  env_vars: # Since passing env variables to reusable workflows not supported # https://github.com/orgs/community/discussions/26671
    name: Expose env variables
    runs-on: ubuntu-latest
    outputs:
      apps: ${{ env.apps }}
    steps:
      - run: echo "Exposing env vars to reusable workflows"
  
  build_unittest_and_push:
    permissions:
      id-token: write
      contents: read
      checks: write
    needs: [env_vars]
    uses: ./.github/workflows/build_unittest_and_push.yml
    with:
      apps: ${{ needs.env_vars.outputs.apps }}


The resuable workflow is implemented in .github/workflows/build_unittest_and_push.yml where matrix job is defined. You can see the input parameter to reusable workflow is taken as apps. Then then the ${{ fromJSON(inputs.apps) }} provided as matrix app. fromJSON ensures the string representation is properly supplied as a json array to the matrix. The build_and_push definition is used  to run multiple jobs, based on input at workflow trigger.

on:
  workflow_call:
    inputs:
      apps:
        description: 'App to deploy'
        type: string
        required: true

name: build_unittest_and_push

jobs:
  build_and_unittest_windows:
    runs-on: "windows-latest"
    steps:
      - uses: actions/checkout@v4
      - name: Build and Unit Test
        id: build_and_unittest
        uses: ./.github/actions/build_and_unittest
        with:
          build_os: Windows

  build_and_unittest_linux:
    runs-on: "ubuntu-latest"
    steps:
      - uses: actions/checkout@v4
      - name: Build and Unit Test
        id: build_and_unittest
        uses: ./.github/actions/build_and_unittest
        with:
          build_os: Linux
 
  build_and_push:
    strategy:
      matrix:
        app: ${{ fromJSON(inputs.apps) }}
    runs-on: "ubuntu-latest"
    steps:
      - uses: actions/checkout@v4
      - shell: pwsh
        run: echo 'app name is ${{ matrix.app }}'


The action used in above unit test execution is as below to provide the complete example. The two unit test steps are not related to this post topic. Action is in .github\actions\build_and_unittest\action.yml

name: "Build and Unit Test"
description: "Build projects and run unit tests"

inputs:
  build_os:
    description: “operating system”
    required: true
 
runs:
  using: "composite"
  steps:
    - shell: pwsh
      run: echo 'runing tests ${{ inputs.build_os }}'


No comments:

Popular Posts