From 9568cb187bcfe269fd272a005b1fc3127c9265c5 Mon Sep 17 00:00:00 2001 From: Ivan Pavlovic Date: Mon, 5 Feb 2024 09:59:01 +0100 Subject: [PATCH] Add check required workflow Signed-off-by: Ivan Pavlovic Co-authored-by: Jonathan Giannuzzi --- .github/workflows/check-required.yml | 97 ++++++++++++++++++++++++++++ .github/workflows/ci.yml | 16 ++--- 2 files changed, 102 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/check-required.yml diff --git a/.github/workflows/check-required.yml b/.github/workflows/check-required.yml new file mode 100644 index 00000000000..2cb24575921 --- /dev/null +++ b/.github/workflows/check-required.yml @@ -0,0 +1,97 @@ +name: Check required jobs + +# This workflow is triggered when a workflow run for the CI is completed. +# It checks if the "All required checks done" job was actually successful +# (and not just skipped) and creates a check run if that is the case. The +# check run can be used to protect the main branch from being merged if the +# CI is not passing. We need to use a GitHub app token to create the check +# run because otherwise the check suite will be assigned to the first workflow +# run for the CI, which might not be the latest one. See +# https://github.com/orgs/community/discussions/24616#discussioncomment-6088422 +# for more details. + +on: + workflow_run: + workflows: [CI] + +permissions: + actions: read + checks: write + +jobs: + required-jobs: + name: Check required jobs + if: ${{ !github.event.repository.fork }} + environment: create-check + runs-on: ubuntu-latest + steps: + - name: Generate an app token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - uses: actions/github-script@v7 + with: + github-token: ${{ steps.app-token.outputs.token }} + script: | + const ghaAppId = 15368; + const ghaName = 'All required checks done'; + const myAppId = ${{ secrets.APP_ID }}; + const myName = 'All required checks succeeded'; + const owner = context.payload.repository.owner.login; + const repo = context.payload.repository.name; + const sha = context.payload.workflow_run.head_sha; + + core.info(`List GitHub Actions check runs for ${sha}.`) + const { data: { check_runs: ghaChecks } } = await github.rest.checks.listForRef({ + owner: owner, + repo: repo, + ref: sha, + app_id: ghaAppId, + check_name: ghaName, + }); + + var newCheck = { + owner: owner, + repo: repo, + name: myName, + head_sha: sha, + status: 'in_progress', + started_at: context.payload.workflow_run.created_at, + output: { + title: 'Not all required checks succeeded', + }, + }; + + core.summary.addHeading('The following required checks have been considered:', 3); + ghaChecks.forEach(check => { + core.summary + .addLink(check.name, check.html_url) + .addCodeBlock(JSON.stringify(check, ['status', 'conclusion', 'started_at', 'completed_at'], 2), 'json'); + + if (check.status === 'completed' && check.conclusion === 'success') { + newCheck.status = 'completed'; + newCheck.conclusion = 'success'; + newCheck.started_at = check.started_at; + newCheck.completed_at = check.completed_at; + newCheck.output.title = 'All required checks succeeded'; + } else if (check.started_at > newCheck.started_at) { + newCheck.started_at = check.started_at; + } + }); + if (ghaChecks.length === 0) { + core.summary.addRaw(`No check runs for ${sha} found.`); + } + newCheck.output.summary = core.summary.stringify(); + await core.summary.write(); + + core.info(`Create own check run for ${sha}: ${JSON.stringify(newCheck, null, 2)}.`) + const { data: { html_url } } = await github.rest.checks.create(newCheck); + + await core.summary + .addHeading('Check run created:', 3) + .addLink(myName, html_url) + .addCodeBlock(JSON.stringify(newCheck, null, 2), 'json') + .write(); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b5ac50bd82..22103ada621 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,23 +35,17 @@ jobs: build: if: github.event_name == 'schedule' || github.event_name == 'push' || github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id uses: ./.github/workflows/build.yml + # Virtual job that can be configured as a required check before a PR can be merged. + # As GitHub considers a check as successful if it is skipped, we need to check its status in + # another workflow (check-required.yml) and create a check there. all-required-checks-done: name: All required checks done - if: github.event_name == 'schedule' || github.event_name == 'push' || github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id needs: - lint - test - build - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - - uses: actions/github-script@v6 - with: - script: | - const results = ${{ toJSON(needs.*.result) }}; - if (results.every(res => res === 'success')) { - core.info('All required checks succeeded'); - } else { - core.setFailed('Some required checks failed'); - } + - run: echo "All required checks done"