2023-03-05

Conditional Execution in GitHub Actions

Introduction

One of the key features of GitHub Actions is its support for conditional execution, which allows you to control when certain steps or jobs within a workflow should run. This feature is particularly useful when managing complex workflows with multiple jobs, steps, and configurations.

Conditional Expressions

Conditional expressions form the foundation of conditional execution in GitHub Actions. In this chapter, I will cover the basics of conditional expressions, the operators you can use, and how to work with contexts and functions.

Basics of Conditional Expressions

Conditional expressions in GitHub Actions are written using the GitHub Actions expression syntax, which is based on JavaScript. These expressions are typically used with the if keyword to determine whether a step or job should run. The result of the expression must be a boolean value, either true or false.

A simple example of a conditional expression is:

if: success()

In this case, the expression checks if the previous steps were successful using the success() function. If they were, the step or job will run; otherwise, it will be skipped.

Operators in Conditional Expressions

GitHub Actions expressions support a variety of operators, which can be used to create more complex expressions. These operators include:

  • Logical operators
    && (and), || (or), ! (not)
  • Equality operators
    == (equal), != (not equal)
  • Comparison operators
    < (less than), > (greater than), <= (less than or equal to), >= (greater than or equal to)

Here's an example that combines several operators:

if: github.event_name == 'pull_request' && github.event.action == 'opened'

This expression checks if the current event is a pull request being opened.

Using Contexts and Functions in Expressions

GitHub Actions expressions can access various context objects and built-in functions, which provide useful information and functionality. Some of these include:

  • github: Provides information about the current repository, event, workflow run, and more.
  • env: Contains environment variables.
  • secrets: Holds repository secrets.
  • steps: Gives access to outputs from previous steps.
  • needs: Allows you to access outputs from dependent jobs.

Functions like success(), failure(), cancelled(), always(), etc.

Here's an example that uses the github context object:

if: github.ref == 'refs/heads/main' && success()

This expression checks if the current branch is the main branch and if the previous steps were successful. If both conditions are met, the step or job will run.

Skipping Actions

In certain scenarios, you may want to skip specific steps or jobs within your workflow. This can be achieved using conditional expressions in combination with the if keyword. In this chapter, I will explore various use cases for skipping actions and how to implement them.

Utilizing the if Keyword

The if keyword allows you to conditionally run a step or job based on the result of a conditional expression. If the expression specified with if evaluates to true, the step or job will run. If the expression evaluates to false, the step or job will be skipped.

Here's an example that demonstrates the use of the if keyword:

steps:
  - name: Run tests
    run: npm test
    if: success()

In this example, the "Run tests" step will only be executed if the previous steps were successful.

Skipping Steps Based on Branches or Tags

You can use conditional expressions to run steps only for specific branches or tags. This can be helpful when certain steps should only be executed for specific releases or branches. Here's an example:

steps:
  - name: Deploy to production
    run: npm run deploy
    if: github.ref == 'refs/heads/main'

In this example, the "Deploy to production" step will only be executed if the current branch is main.

Skipping Steps Based on Modified Files

Another use case for conditional execution is running a step only when certain files have been modified. This can be useful when you want to trigger a step or job only if specific files have been changed in a commit or pull request.

You can achieve this using the contains() and fromJson() functions, along with a custom action that lists the changed files. Here's an example:

steps:
  - name: Get changed files
    id: changes
    uses: trilom/file-changes-action@v1
    with:
      output: 'json'

  - name: Run tests for important files
    run: npm run test:important
    if: contains(fromJson(steps.changes.outputs.files), 'path/to/important/file')

In this example, the "Run tests for important files" step will only be executed if the specified file (path/to/important/file) has been modified.

Matrix Builds

Matrix builds are a powerful feature of GitHub Actions that allow you to run the same job with multiple configurations. This is particularly useful when you want to test your code on different platforms or with various versions of a programming language or dependency. In this chapter, I will explore the concept of matrix builds, how to use conditional matrix builds, and how to combine matrix builds with the if keyword for advanced scenarios.

Understanding Matrix Builds

Matrix builds in GitHub Actions enable you to define a set of configurations that your job should run with. This is done by specifying a matrix key in the job definition, followed by a list of variables and their possible values. GitHub Actions will then run the job for each combination of these values.

Here's a basic example of a matrix build:

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node-version: [12, 14, 16]

    steps:
      - name: Set up Node.js
        uses: actions/setup-node@v2
        with:
          node-version: ${{ matrix.node-version }}

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

In this example, the test job will run on three different operating systems (Ubuntu, macOS, and Windows) and with three different Node.js versions (12, 14, and 16), resulting in a total of nine job runs.

Using Conditional Matrix Builds

Sometimes, you may want to use conditional expressions within a matrix build to include or exclude specific configurations based on certain conditions. This can be done using the include and exclude keys within the matrix definition.

Here's an example that demonstrates the use of conditional matrix builds:

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node-version: [12, 14, 16]
        include:
          - os: ubuntu-latest
            node-version: 18
            if: github.ref == 'refs/heads/main'

In this example, an additional configuration (Node.js version 18 on Ubuntu) will be included in the matrix only if the current branch is main.

Combining Matrix Builds with if for Advanced Scenarios

For more advanced scenarios, you can use the if keyword within a job or step to further control the execution of your matrix build. By combining conditional expressions with the matrix context object, you can create complex and flexible workflows.

Here's an example that demonstrates the use of the if keyword with a matrix build:

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node-version: [12, 14, 16]

    steps:
      - name: Set up Node.js
        uses: actions/setup-node@v2
        with:
          node-version: ${{ matrix.node-version }}

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Publish coverage report
        run: npm run coverage
        if: matrix.os == 'ubuntu-latest' && matrix.node-version == 14

In this example, the "Publish coverage report" step will only be executed if the current configuration is running on Ubuntu with Node.js version 14. This can be useful when you want to perform certain tasks, such as publishing a coverage report or deploying your application, only once in a matrix build or for a specific configuration.

Ryusei Kakujo

researchgatelinkedingithub

Focusing on data science for mobility

Bench Press 100kg!