You can run steps conditionally in your CI/CD pipelines using "when" expressions, but the most surprising thing is how much power you have to define "when" – it’s not just about simple booleans, but entire expressions that can evaluate complex conditions based on the state of your build.
Let’s see this in action. Imagine a pipeline that builds a Docker image, runs tests, and then deploys only if the tests pass and the branch is main.
jobs:
build_and_test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Build Docker image
id: docker_build
run: |
echo "Building Docker image..."
# Simulate building an image and setting an output
echo "image_tag=latest-$(date +%s)" >> $GITHUB_OUTPUT
- name: Run tests
id: run_tests
run: |
echo "Running tests..."
# Simulate tests passing
echo "tests_passed=true" >> $GITHUB_OUTPUT
deploy:
runs-on: ubuntu-latest
needs: build_and_test
steps:
- name: Deploy to production
uses: actions/deploy@v1 # Fictional deploy action
# This deploy step will only run if tests passed AND the branch is 'main'
when: needs.build_and_test.outputs.tests_passed == 'true' && github.ref == 'refs/heads/main'
env:
IMAGE_TAG: ${{ needs.build_and_test.outputs.image_tag }}
run: |
echo "Deploying image with tag: $IMAGE_TAG to production..."
# Actual deployment commands here
In this example, the deploy job has a when condition. It checks two things:
needs.build_and_test.outputs.tests_passed == 'true': This verifies if therun_testsstep in thebuild_and_testjob reported that tests passed. We’re accessing the output of a previous job usingneeds.<job_id>.outputs.<output_name>.github.ref == 'refs/heads/main': This checks if the current Git reference is themainbranch. Thegithubcontext provides a wealth of information about the workflow run, including the ref, event, repository, etc.
The && operator combines these two conditions, meaning the deploy job will only execute if both are true.
The mental model for "when" expressions is that they are evaluated as boolean values at the time the job or step is scheduled. The system looks at all the conditions defined and determines whether to proceed. You can access outputs from previous steps and jobs, as well as a rich set of context variables. This allows for incredibly granular control. For instance, you could deploy only if a specific commit message contains "release", or if a pull request is opened from a specific fork.
A common misconception is that when expressions are evaluated during the execution of a step. In reality, they are evaluated before the step or job is even started. This means if a when condition depends on an output that hasn’t been produced yet (e.g., a step that might produce an output but might also fail), the when condition will be evaluated based on the absence of that output, potentially leading to unexpected behavior. Always ensure that any outputs your when condition relies on are guaranteed to be set, or handle their absence gracefully within your expression.
The next step is to explore how to use functions within your when expressions to perform more complex string manipulations or data comparisons.