In this topic, we will discuss conditionals in GitHub Actions workflows. Then learn about how jobs can depend on one another during a run. Finally, you will learn to stop a workflow from failing if an error occurs.
If conditional
Conditionals in GitHub Actions workflows are similar to other any programming language. if conditional prevents a job or a step from executing unless specific conditions are met.
To execute a job within the workflow or not →
job.<job_id>.ifTo execute a step inside a job or not →
job.<job_id>.steps.if
Variables accessible in these conditions are the same ones that a user can access on either the job level or the step level. You can use the following variables as conditions to evaluate the if conditional.
Context variables
Environment variables
Output variables (from other jobs)
You can also use expressions to validate conditions with the help of operators. For example: && (And), || (Or), < (Less than), > (Greater than), <=(Less than or equal), >=(Greater than or equal), == (Equal), and != (Not equal). Visit the official documentation to learn more about operators.
Let's look at a workflow file to see how the if conditional can be used.
name: Conditional Workflow
on: push
jobs:
number_generator:
runs-on: ubuntu-latest
steps:
- id: generator
run: echo "num=$RANDOM % 2"
- if: steps.generator.outputs.num == 0
run: echo zero
- if: steps.generator.outputs.num == 1
run: echo oneRunning this snippet will give the following result:
A random number is generated — 28232. When 28232 is modded with 2, the output is zero. Therefore, the second step's condition is fulfilled, and an output is produced. The third step is omitted as the conditions to run the step are not met. You can even skip a job entirely. For instance, since issues and PRs are similar under the hood and have a similar event payload in GitHub Actions, you can check if the event is a PR before proceeding:
on:
issue_comment:
types: [created, deleted]
jobs:
pr_comments:
if: github.event.issue.pull_request # only runs for PR comments
runs-on: ubuntu-latest
steps:
- run: |
echo "You have a new comment on PR ${{ github.event.issue.number }}"In the following run, we created new comments on an issue and a PR. As you can see, the job ran successfully for the PR and was skipped for the Issue:
GitHub automatically evaluates if conditional as an expression. Therefore we can omit the expression syntax (${{ }}) in if conditionals.
Using predefined functions with if conditional
In addition to variables, you can also use predefined functions in conditions.
startsWith(searchString, searchValue)returnstruewhensearchStrinstarts withsearchValue;contains(search, item)returnstrueif the search contains the expected item;endsWith(searchString, searchValue)returnstrueifsearchStringends withsearchValue.
Let's look at another example that uses these predefined functions.
name: Predefined function
on: push
jobs:
job1:
runs-on: ubuntu-latest
steps:
- id: step1
run: "echo test=hello >> $GITHUB_OUTPUT"
- name: step2
if: contains(steps.step1.outputs.test, 'hello')
run: echo is executed
- name: step3
if: contains(steps.step1.outputs.test, 'bye')
run: echo not executedIn this workflow, only step 2 is executed. Step 3 is skipped as the conditions are not met.
$GITHUB_OUTPUT was used in step 1 to set a step's output parameter. You will need to define an id for the step to later retrieve the output value.
Using state validations
You can also use state validations in conditions;
success()returnstrueif no previous steps have failed or have been canceled;failure()returnstrueif any previous step failed;always()returnstrueno matter if a step failed or the workflow was canceled;canceled()returnstrueif the workflow was canceled.
But what if there is a step that must be executed regardless of whether the previous step succeeds or fails? A common example of this scenario is when the result of a task should be notified by email or messaging application. Because you want to be notified whether the execution of the job results in success or failure.
In this case, you can set the expression of GitHub Actions to the if property of the step with always() that must be performed unconditionally.
For example, let's set up a job so that the first step succeeds or fails randomly. Then the second step always outputs the first step's result. In order to check the output result of a specific step, let's use steps.<step_id>.outcome.
name: State validations
on: push
jobs:
validate:
runs-on: ubuntu-latest
steps:
- id: generator
run: if [[ $((RANDOM % 2)) == 0 ]]; then exit 0; else exit 1; fi
- if: ${{ always() }}
run: echo ${{ steps.generator.outcome }}If the first step succeeds, the second step will print success. But if the first step fails, the second step will print failure. This shows that the second step is executed regardless of the result of the first step.
Occasionally, you may want to set up a backup step that is performed only if a step fails. In this case, you can set the GitHub Actions expression called in the if properties with failure() of the corresponding backup step.
For example, let's set up a job so that the first step fails unconditionally, and the second step takes its place.
name: failure
on: push
jobs:
backup:
runs-on: ubuntu-latest
steps:
- name: original
run: exit 1
- name: backup
if: ${{ failure() }}
run: echo backupIn contrast, let's modify the workflow so that the first step passes unconditionally.
name: fixed-failure
on: push
jobs:
backup:
runs-on: ubuntu-latest
steps:
- name: original
run: exit 0
- name: backup
if: ${{ failure() }}
run: echo backupAfter running this workflow, you will see that the second step was not performed.
Job dependencies
By default, jobs are executed in parallel. Now let's create a workflow where you need to validate the state of a previous job to proceed to the next job. The needs keyword will help to make sure certain jobs depend on another job.
Let's take a look at an example:
jobs:
Job1:
Job2:
Job3:
needs: [Job1, Job2]In this example, you can see that Job3 depends on Job1 and Job2. This means that both jobs need to be completed successfully before Job3 can run. Another important point to note is that if a job fails, all the jobs that need it are skipped unless the jobs use a conditional expression to continue. In this example, if either Job1 or Job2 fails, then Job3 will not run.
Ignoring failures in jobs and steps (continue-on-error)
In GitHub Actions, by default, if a step fails during the execution of a job, subsequent steps are not executed and the job is aborted. In most cases, the default handling of these GitHub Actions makes sense, as unnecessary steps can be omitted.
But in a real project, there may be an unstable phase where a step may fail. A typical example – there may be a job or step whose success or failure is unknown. In this situation, the whole team would be in trouble if the entire task was brought to a halt whenever a step failed.
continue-on-error supports properties. If this property is set to true, the job or workflow will not be interrupted even if the corresponding step fails. The remaining steps will continue to be executed.
For example, let's look at the following task.
name: continue-on-error
on: push
jobs:
ignore:
runs-on: ubuntu-latest
steps:
- id: step1
continue-on-error: true
run: exit 1
- run: echo "I don't care!"The first step always fails, but the second step prints I don't care! because the continue-on-error property is set to true.
Conclusion
GitHub Actions allows you to control the execution of jobs and steps in various ways. You can use if conditional to prevent a job or step from running if certain conditions are not met. And using state validations within conditionals can give you even more flexibility. Also, jobs run in parallel by default. But they may be dependent on each other. The needs keyword can help you manage such dependencies and run jobs sequentially. Just in case a step fails, continue-on-error will prevent interruptions and the job will not halt.