Introduction
Conditional execution of jobs is one of those powerful features you find in GitHub Actions, underutilized at times. It enables you to block the execution of jobs unless certain conditions are met-something which is super useful if you want to control a workflow flow based on outputs or external factors. Whether for multiple environments, complex build pipelines, or job dependencies, conditions enable you to further define your automation.
Using Conditions to Control Job Execution
GitHub Actions similar to jobs.<job_id>.if syntax, you define conditions based on a wide range of inputs where a job would run. Amongst these are job outputs, GitHub contexts-like repository names or branches, environment variables amongst others. Adding a condition makes your workflow more efficient while skipping jobs by marking them as "success" if skipped.
Here's a simple example - we only want to run a deployment job if the repository is a production repository:
jobs:
  production-deploy:
    if: github.repository == 'my-org/prod-repo'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: echo "Deploying to production"
In this case, the production-deploy job will only execute if the workflow is triggered in the repository my-org/prod-repo, and be skipped for any other cases.
Combining Job Dependencies and Conditions
This is often the case when you want to make a job execute depending on the result of the execution of another job, deployment job after a build job. You can easily define job dependencies using the keyword needs in these cases, or you could combine the usage of needs with conditions to fine-tune this behavior.
Consider an example where we have the three jobs: build, test, and deploy. Here, we want deploy to run only if build succeeds and the test job either succeeds or is skipped.
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Building project"
  test:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - run: echo "Running tests"
      - id: set_test_output
        run: echo "::set-output name=should_deploy::yes"
    outputs:
      should_deploy: ${{ steps.set_test_output.outputs.should_deploy }}
  deploy:
    runs-on: ubuntu-latest
    needs: [build, test]
    if: needs.test.outputs.should_deploy == 'yes' && (needs.test.result == 'success' || needs.test.result == 'skipped')
    steps:
      - run: echo "Deploying project"
Overview: How It Works:
- In it, the job 
buildis executed and upon success of this job, thetestjob is triggered. - The 
testjob outputs ashould_deploy, which will be used in thedeployjob to decide if it should be executed or not. - Here, the 
deployjob is dependent on success of thebuildjob and either success or skip of thetestjob. The deployment proceeds when the output of thetestjob is set to'yes'. 
This will be an elastic way of dealing with job dependencies, which come with complementary conditions.
Handling of Skipped Jobs and Conditional Logic
One common challenge within workflows, is how to deal with skipped jobs. By default a job may be "skipped", yet its status can still be reported out as "success", yet you may wish for other jobs to behave differently based on this status. That's where using conditions like success(), failure(), cancelled() or always() can help.
Suppose you then have a test job which, under some circumstances is going to skip. Since the following job, notify should-only execute if prior test job succeed or is skipped.
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Running tests"
      - id: set_skip_condition
        run: echo "::set-output name=should_skip::no"
    outputs:
      should_skip: ${{ steps.set_skip_condition.outputs.should_skip }}
  notify:
    runs-on: ubuntu-latest
    needs: test
    if: needs.test.result == 'success' || needs.test.result == 'skipped'
    steps:
      - run: echo "Sending notification"
Breakdown:
- The 
testjob sets an output calledshould_skip. - The 
notifyjob will only run if thetestjob succeeds or is skipped. - This way, it would still generally be expected that the workflow proceeds, even if a job was skipped.
 
Monitoring GitHub Actions Workflows
CICube is a GitHub Actions monitoring tool that provides you with detailed insights into your workflows to further optimize your CI/CD pipeline. With CICube, you will be able to track your workflow runs, understand where the bottlenecks are, and tease out the best from your build times. Go to cicube.io now and create a free account to better optimize your GitHub Actions workflows!
Lessons Learned: Always Be Careful with Conditional Logic
When I was investigating the feature of conditional execution of jobs, that is when all those unexpected behaviors kicked in. For example, I used some combined conditions with if. Sometimes, the workflow didn't behave the way it was expected.
Sometimes you might want to make sure a job runs under several conditions. You can use always() as a way of forcing an evaluation alongside other conditions. Here's such an example where we wish the finalize job to run even if previous jobs were skipped, provided another condition is met:
jobs:
  finalize:
    runs-on: ubuntu-latest
    needs: [build, test]
    if: always() && (needs.build.result == 'success' || needs.test.result == 'skipped')
    steps:
      - run: echo "Finalizing workflow"
This will ensure that finalize is definitely executed regardless of the result of all the other jobs, but only of course, if at least one of the jobs succeeded or was skipped.
Conclusion
Conditional execution of jobs in GitHub Actions allows you to get much more efficient and maintainable continuous integration/continuous deployment pipelines. Because you're going to have some context, like outputs or external factors determining whether it's worth running the job, you will save not just time but also resources. You would need to consider how you will handle skipped jobs. Know about status checks like success(), failure() or always(). 
You'll be able to make your workflows more intelligent and responsive with conditional logic so that the need for redundant jobs or manual intervention will be reduced when different scenarios pop up.
 
    
Top comments (0)