jshah
jshah

Reputation: 1661

How to run a github actions job on workflow failure?

I want to run a notification job that lets me know that my workflow failed, is there a way to do that without having to needs every job and checking the status of each one?

This is how I would have to do it now but it gets cumbersome if I have a ton of jobs:

jobs:
  first-job:
    runs-on: ubuntu-20.04
    steps:
      - exit 0


  second-job:
    runs-on: ubuntu-20.04
    steps:
      - exit 1

  notify-job:
    runs-on: ubuntu-20.04
    needs: [first-job, second-job]
    if: ${{ always() && (needs.first-job.result == 'failure' || needs.second-job.result == 'failure') }}
    steps:
      - ./notify.sh

I want to simply check if the workflow failed in any capacity at the end, i.e. if any job failed, is that possible?

I see this documentation to check if the triggering workflow failed (https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#running-a-workflow-based-on-the-conclusion-of-another-workflow).

Is there a way to run a finally or ensure status check at the end of the current workflow?

Upvotes: 13

Views: 18443

Answers (2)

Yann
Yann

Reputation: 736

I found this less verbose syntax:

if: ${{ always() && contains(needs.*.result, 'failure') }}

Meaning, if any of the jobs listed in needs return "failure" then run this job...

Source: https://docs.github.com/en/actions/learn-github-actions/expressions#contains

Upvotes: 23

GuiFalourd
GuiFalourd

Reputation: 22890

Github partner brightran stated in this post:

By default, once a step in a running job fails, all the subsequent steps in this job will be skipped and this job will be marked as failed. If you want the subsequent steps still execute, you can add the if conditional (if: always()) in each subsequent step.

Add 2 extra steps at the end of the job1 and job2, and set the 2 steps always execute (if: always()). The first one is used to create a text file and write the job status into it, and the second one is used to upload this text file as an artifact. In job3, you also need to add the steps to download the artifacts and read the statuses of jo1 and jo2.

Using this workflow as demo:

jobs:
  JOB_01:
    name: Job 01
    . . .
    steps:
      - name: Some steps of job 01
      . . .
      - name: Create file status_job01.txt and write the job status into it
        if: always()
        run: |
          echo ${{ job.status }} > status_job01.txt

      - name: Upload file status_job01.txt as an artifact
        if: always()
        uses: actions/upload-artifact@v1
        with:
          name: pass_status_job01
          path: status_job01.txt

  JOB_02:
    name: Job 02
    . . .
    steps:
      - name: Some steps of job 02
      . . .
      - name: Create file status_job02.txt and write the job status into it
        if: always()
        run: |
          echo ${{ job.status }} > status_job02.txt

      - name: Upload file status_job02.txt as an artifact
        if: always()
        uses: actions/upload-artifact@v1
        with:
          name: pass_status_job02
          path: status_job02.txt

  JOB_03:
    needs: [JOB_01, JOB_02]
    if: always()
    name: Job 03
    . . .
    steps:
      - name: Download artifact pass_status_job01
        uses: actions/download-artifact@v1
        with:
          name: pass_status_job01

      - name: Download artifact pass_status_job02
        uses: actions/download-artifact@v1
        with:
          name: pass_status_job02

      - name: Set the statuses of Job 01 and Job 02 as output parameters
        id: set_outputs
        run: |
          echo "::set-output name=status_job01::$(<pass_status_job01/status_job01.txt)"
          echo "::set-output name=status_job02::$(<pass_status_job02/status_job02.txt)"

      - name: Show the values of the outputs
        run: |
          # using the syntax steps.<step_id>.outputs.<output_name> to access the output parameters
          echo "status_job01 = ${{ steps.set_outputs.outputs.status_job01 }}"
          echo "status_job02 = ${{ steps.set_outputs.outputs.status_job02 }}"

      - name: Some other steps of job 03
      . . .

In your case, you could eventually use those status job outputs to execute or not a job according to their value in an if condition like your suggested in your question (it would probably be in a 4th job after setting them as job3 outputs).

It isn't a nice solution (as it's quite verbose), but it works.

Upvotes: 4

Related Questions