From b763256e78892fd2e8431315671fac0c1ecf4c02 Mon Sep 17 00:00:00 2001 From: Carlo Cabrera <30379873+carlocab@users.noreply.github.com> Date: Mon, 17 Apr 2023 13:54:36 +0800 Subject: [PATCH] workflows: publish approved PRs upon CI completion This is based on one of @MikeMcQuaid's suggestions on Slack. We add an `automerge.yml` workflow that is triggered by the `workflow_run` event. We need this in order to escalate access of `GITHUB_TOKEN` from the `pull_request` and `pull_request_review` events, which can only have read access to the GitHub API. The `automerge` workflow is triggered by two events: - completion of a `CI` workflow on a pull request - approval of a pull request Because both of these events are required in order to merge, we allow the workflow to wait for a while to check if the other event occurs. The basic idea of this workflow was borrowed from the documentation on `workflow_run` events [1]. [1] https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_run --- .github/workflows/automerge.yml | 172 ++++++++++++++++++++++++++++++++ .github/workflows/reviews.yml | 22 ++++ .github/workflows/tests.yml | 16 +++ 3 files changed, 210 insertions(+) create mode 100644 .github/workflows/automerge.yml create mode 100644 .github/workflows/reviews.yml diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 0000000000000..a42ddb215d865 --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,172 @@ +name: automerge + +on: + workflow_run: + workflows: + - CI + - Track approved PRs + types: + - completed + +concurrency: + group: automerge-${{ github.event.workflow_run.event }}-${{ github.event.workflow_run.pull_requests[0].number || github.event.workflow_run.id }} + cancel-in-progress: true + +env: + HOMEBREW_DEVELOPER: 1 + HOMEBREW_NO_AUTO_UPDATE: 1 + GH_REPO: ${{ github.repository }} + GH_NO_UPDATE_NOTIFIER: 1 + GH_PROMPT_DISABLED: 1 + +jobs: + status-check: + runs-on: ubuntu-latest + if: > + github.repository_owner == 'Homebrew' && + github.event.workflow_run.conclusion == 'success' && + (github.event.workflow_run.event == 'pull_request' || + github.event.workflow_run.name != 'CI') + outputs: + pull-number: ${{ steps.pr.outputs.number }} + approved: ${{ steps.approval-status.outputs.approved }} + complete: ${{ steps.approval-status.outputs.complete }} + mergeable: ${{ steps.approval-status.outputs.mergeable }} + permissions: + contents: read + pull-requests: read + actions: read + steps: + - name: Download `pull-number` artifact + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WORKFLOW_ID: ${{ github.event.workflow_run.id }} + run: gh run download --name pull-number "$WORKFLOW_ID" + + - run: echo "number=$(cat number)" >> "$GITHUB_OUTPUT" + id: pr + + - name: Check PR labels + id: check-labels + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR: ${{ steps.pr.outputs.number }} + run: | + publishable=yes + while IFS='' read -r label + do + if [[ "$label" = "do not merge" ]] || + [[ "$label" = "new formula" ]] || + [[ "$label" = "automerge-skip" ]] || + [[ "$label" = "CI-published-bottle-commits" ]] + then + publishable=no + break + fi + done < <( + gh api \ + --header 'Accept: application/vnd.github+json' \ + --header 'X-GitHub-Api-Version: 2022-11-28' \ + "repos/$GH_REPO/pulls/$PR" \ + --jq '.labels[].name' + ) + echo "publishable=$publishable" >> "$GITHUB_OUTPUT" + + - name: Get approval and CI status + if: steps.check-labels.outputs.publishable == 'yes' + id: approval-status + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR: ${{ steps.pr.outputs.number }} + run: | + attempt=0 + max_attempts=5 + timeout=5 + + while [[ "$attempt" -lt "$max_attempts" ]] + do + attempt=$(( attempt + 1 )) + + approved=false + complete=false + mergeable=false + + review_data="$( + gh api \ + --header 'Accept: application/vnd.github+json' \ + --header 'X-GitHub-Api-Version: 2022-11-28' \ + "repos/$GH_REPO/pulls/$PR/reviews" + )" + if jq --exit-status 'any(.[].state; .== "APPROVED")' <<< "$review_data" && + jq --exit-status 'all(.[].state; .!= "CHANGES_REQUESTED" )' <<< "$review_data" + then + approved=true + fi + + if gh pr checks "$PR" + then + complete=true + fi + + pr_data="$( + gh api \ + --header 'Accept: application/vnd.github+json' \ + --header 'X-GitHub-Api-Version: 2022-11-28' \ + "repos/$GH_REPO/pulls/$PR" + )" + mergeable_state="$(jq --raw-output .mergeable_state <<< "$pr_data")" + draft="$(jq --raw-output .draft <<< "$pr_data")" + + # See https://github.com/octokit/octokit.net/issues/1763 for possible values. + if [[ "$draft" = "false" ]] && [[ "$mergeable_state" = "clean" ]] + then + mergeable=true + fi + + if [[ "$approved" = "true" ]] && + [[ "$complete" = "true" ]] && + [[ "$mergeable" = "true" ]] || + [[ "$attempt" -eq "$max_attempts" ]] + then + break + fi + + echo "::notice ::PR #$PR status:" + echo "::notice ::Approved? $approved" + echo "::notice ::CI Complete? $complete" + echo "::notice ::Mergeable? $mergeable" + echo "::notice ::Checking again in ${timeout}s..." + sleep "$timeout" + timeout=$(( timeout * 2 )) + done + + { + echo "approved=$approved" + echo "complete=$complete" + echo "mergeable=$mergeable" + } >> "$GITHUB_OUTPUT" + + merge: + runs-on: ubuntu-latest + needs: status-check + if: > + fromJson(needs.status-check.outputs.approved) && + fromJson(needs.status-check.outputs.complete) && + fromJson(needs.status-check.outputs.mergeable) + container: + image: ghcr.io/homebrew/ubuntu22.04:master + permissions: + contents: read + pull-requests: read + actions: write # to dispatch publish workflow + defaults: + run: + shell: bash + steps: + - name: Set up Homebrew + uses: Homebrew/actions/setup-homebrew@master + + - run: brew pr-publish "$PR" + env: + HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR: ${{ needs.status-check.outputs.pull-number }} diff --git a/.github/workflows/reviews.yml b/.github/workflows/reviews.yml new file mode 100644 index 0000000000000..42ed07858fc45 --- /dev/null +++ b/.github/workflows/reviews.yml @@ -0,0 +1,22 @@ +name: Track approved PRs + +on: + pull_request_review: + types: [submitted] + +jobs: + record_pull_number: + if: > + github.repository_owner == 'Homebrew' && + github.event.review.state == 'approved' + runs-on: ubuntu-latest + steps: + - name: Save pull request number + run: | + mkdir -p pr + echo '${{ github.event.pull_request.number }}' > pr/number + + - uses: actions/upload-artifact@v3 + with: + name: pull-number + path: pr diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6c9cf6a0d3e8e..bca22e93c1e69 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -63,6 +63,22 @@ jobs: TESTING_FORMULAE: ${{ steps.formulae-detect.outputs.testing_formulae }} run: brew test-bot --only-bottles-fetch --testing-formulae="$TESTING_FORMULAE" + record_pull_number: + if: github.repository_owner == 'Homebrew' && github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Save pull request number + env: + PR: ${{github.event.number}} + run: | + mkdir -p pr + echo "$PR" > pr/number + + - uses: actions/upload-artifact@v3 + with: + name: pull-number + path: pr + setup_tests: permissions: pull-requests: read