|
| 1 | +--- |
| 2 | +name: Verify Success reusable workflow |
| 3 | + |
| 4 | +# Use this reusable workflow as a job of workflow to check that |
| 5 | +# all jobs, including ones ran through a matrix, were successful. |
| 6 | +# This job can then be used as a required status check in the |
| 7 | +# repo's rulesets, that allows to change the required jobs or |
| 8 | +# the matrix values of a required job without needing to change |
| 9 | +# the rulesets settings. In the future, GitHub might have a |
| 10 | +# solution to this natively. |
| 11 | + |
| 12 | +# This reusable workflow has inputs to change what is required |
| 13 | +# to have this workflow pass. It handles the cases were there were |
| 14 | +# skipped jobs, and no successful jobs. |
| 15 | + |
| 16 | +# The jobs to check must set as the `needs` for the job calling this |
| 17 | +# reusable workflow. This also means that the job ids should be in the |
| 18 | +# same workflow file. The calling job must be set to always run to be |
| 19 | +# triggered when jobs are skipped or cancelled. |
| 20 | +# Then, set the `needs_context` input like: |
| 21 | +# `needs_context: ${{ toJson(needs) }}` |
| 22 | + |
| 23 | +# Example usage, as a job inside a workflow: |
| 24 | +# ``` |
| 25 | +# jobs: |
| 26 | +# a-job-id: |
| 27 | +# ... |
| 28 | +# another-job-id: |
| 29 | +# ... |
| 30 | +# some-job-success: |
| 31 | +# name: Some Job Result |
| 32 | +# needs: |
| 33 | +# - a-job-id |
| 34 | +# - another-job-id |
| 35 | +# if: ${{ always() }} |
| 36 | +# uses: ./.github/workflows/verify-success.yml |
| 37 | +# with: |
| 38 | +# needs_context: ${{ toJson(needs) }} |
| 39 | +# ``` |
| 40 | + |
| 41 | +on: |
| 42 | + workflow_call: |
| 43 | + inputs: |
| 44 | + needs_context: |
| 45 | + type: string |
| 46 | + required: true |
| 47 | + # Can't escape the handlebars in the description |
| 48 | + description: |
| 49 | + In the calling job that defines all the needed jobs, |
| 50 | + send `toJson(needs)` inside `$` followed by `{{ }}` |
| 51 | + fail_if_failure: |
| 52 | + type: boolean |
| 53 | + default: true |
| 54 | + description: |
| 55 | + If true, this workflow will fail if any job from 'needs_context was |
| 56 | + failed |
| 57 | + fail_if_cancelled: |
| 58 | + type: boolean |
| 59 | + default: true |
| 60 | + description: |
| 61 | + If true, this workflow will fail if any job from 'needs_context' was |
| 62 | + cancelled |
| 63 | + fail_if_skipped: |
| 64 | + type: boolean |
| 65 | + default: false |
| 66 | + description: |
| 67 | + If true, this workflow will fail if any job from 'needs_context' was |
| 68 | + skipped |
| 69 | + require_success: |
| 70 | + type: boolean |
| 71 | + default: true |
| 72 | + description: |
| 73 | + If true, this workflow will fail if no job from 'needs_context' was |
| 74 | + successful |
| 75 | + |
| 76 | +jobs: |
| 77 | + verify-success: |
| 78 | + name: Success |
| 79 | + runs-on: ubuntu-latest |
| 80 | + continue-on-error: true |
| 81 | + steps: |
| 82 | + - name: Set outputs for each job result type |
| 83 | + id: has-result |
| 84 | + run: | |
| 85 | + echo "failure=${{ |
| 86 | + contains(env.NEEDS_RESULT, 'failure') }}" >> "$GITHUB_OUTPUT" |
| 87 | + echo "cancelled=${{ |
| 88 | + contains(env.NEEDS_RESULT, 'cancelled') }}" >> "$GITHUB_OUTPUT" |
| 89 | + echo "skipped=${{ |
| 90 | + contains(env.NEEDS_RESULT, 'skipped') }}" >> "$GITHUB_OUTPUT" |
| 91 | + echo "success=${{ |
| 92 | + contains(env.NEEDS_RESULT, 'success') }}" >> "$GITHUB_OUTPUT" |
| 93 | + env: |
| 94 | + NEEDS_RESULT: ${{ toJson(fromJson(inputs.needs_context).*.result) }} |
| 95 | + - name: Set exit codes for each job result type |
| 96 | + id: exit-code |
| 97 | + run: | |
| 98 | + echo "failure=${{ inputs.fail_if_failure && |
| 99 | + fromJson(steps.has-result.outputs.failure) && 1 || 0 |
| 100 | + }}" >> "$GITHUB_OUTPUT" |
| 101 | + echo "cancelled=${{ inputs.fail_if_cancelled && |
| 102 | + fromJson(steps.has-result.outputs.cancelled) && 1 || 0 |
| 103 | + }}" >> "$GITHUB_OUTPUT" |
| 104 | + echo "skipped=${{ inputs.fail_if_skipped && |
| 105 | + fromJson(steps.has-result.outputs.skipped) && 1 || 0 |
| 106 | + }}" >> "$GITHUB_OUTPUT" |
| 107 | + echo "success=${{ inputs.require_success && |
| 108 | + !fromJson(steps.has-result.outputs.success) && 1 || 0 |
| 109 | + }}" >> "$GITHUB_OUTPUT" |
| 110 | + - name: Set messages for each job result type |
| 111 | + id: message |
| 112 | + run: | |
| 113 | + echo "failure=${{ format('{0}{1} were failed', |
| 114 | + (steps.exit-code.outputs.failure == 1) && env.P1 || env.P2, |
| 115 | + (steps.has-result.outputs.failure == 'true') && env.M1 || env.M2) |
| 116 | + }}" >> "$GITHUB_OUTPUT" |
| 117 | + echo "cancelled=${{ format('{0}{1} were cancelled', |
| 118 | + (steps.exit-code.outputs.cancelled == 1) && env.P1 || env.P2, |
| 119 | + (steps.has-result.outputs.cancelled == 'true') && env.M1 || env.M2) |
| 120 | + }}" >> "$GITHUB_OUTPUT" |
| 121 | + echo "skipped=${{ format('{0}{1} were skipped', |
| 122 | + (steps.exit-code.outputs.skipped == 1) && env.P1 || env.P2, |
| 123 | + (steps.has-result.outputs.skipped == 'true') && env.M1 || env.M2) |
| 124 | + }}" >> "$GITHUB_OUTPUT" |
| 125 | + echo "success=${{ format('{0}{1} were successful', |
| 126 | + (steps.exit-code.outputs.success == 1) && env.P1 || env.P2, |
| 127 | + (steps.has-result.outputs.success == 'true') && env.M1 || env.M2) |
| 128 | + }}" >> "$GITHUB_OUTPUT" |
| 129 | + env: |
| 130 | + P1: "::error ::" # Common message prefix if step will fail |
| 131 | + P2: "" # Common message prefix if step will not fail |
| 132 | + M1: "Some jobs" # Common message if result is true |
| 133 | + M2: "No jobs" # Common message if result is false |
| 134 | + |
| 135 | + - name: Check for failed jobs |
| 136 | + run: | |
| 137 | + echo "${{ steps.message.outputs.failure }}" |
| 138 | + exit ${{ steps.exit-code.outputs.failure }} |
| 139 | + - name: Check for cancelled jobs |
| 140 | + run: | |
| 141 | + echo "${{ steps.message.outputs.cancelled }}" |
| 142 | + exit ${{ steps.exit-code.outputs.cancelled }} |
| 143 | + - name: Check for skipped jobs |
| 144 | + run: | |
| 145 | + echo "${{ steps.message.outputs.skipped }}" |
| 146 | + exit ${{ steps.exit-code.outputs.skipped }} |
| 147 | + - name: Check for successful jobs |
| 148 | + run: | |
| 149 | + echo "${{ steps.message.outputs.success }}" |
| 150 | + exit ${{ steps.exit-code.outputs.success }} |
| 151 | +
|
| 152 | + - run: echo "Checks passed successfully" |
| 153 | + if: ${{ success() }} |
| 154 | + - run: echo "Checks failed" |
| 155 | + if: ${{ !success() }} |
0 commit comments