Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 53 additions & 5 deletions .github/workflows/renovate-auto-approve.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ name: renovate-auto-approve
# approve:
# uses: opendefensecloud/dev-kit/.github/workflows/renovate-auto-approve.yml@<sha-or-tag>
# permissions:
# contents: read
# pull-requests: write
#
# It approves a Renovate PR that carries the automerge label and not the block
# label (security). If a PR later gains the block label, it revokes its own
# earlier approval so a human is required again. The merge itself always stays
# gated on the repo's required status checks.
# It approves a Renovate PR that carries the automerge label, added by Renovate,
# and not the block label (security), and only while the branch still holds
# exactly one commit, authored by Renovate and signed, and pushed by Renovate.
# Once someone pushes
# onto the branch it stops qualifying, because that change was never reviewed by
# anyone. If a PR stops qualifying or later gains the block label, the workflow
# revokes its own earlier approval so a human is required again. The merge
# itself always stays gated on the repo's required status checks.
#
# Which update types are eligible (the automerge label) is decided by the
# renovate-config presets, not here: github>opendefensecloud/renovate-config.
Expand All @@ -33,6 +38,7 @@ on:
default: renovate[bot]

permissions:
contents: read # repository activity API, to see who pushed the head
pull-requests: write

concurrency:
Expand All @@ -48,14 +54,16 @@ jobs:
(contains(github.event.pull_request.labels.*.name, inputs.automerge-label) ||
contains(github.event.pull_request.labels.*.name, inputs.block-label))
steps:
- name: Approve, or revoke a prior approval if the block label appears
- name: Approve an unmodified Renovate PR, or revoke a prior approval
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
LABEL: ${{ inputs.automerge-label }}
BLOCK_LABEL: ${{ inputs.block-label }}
HAS_BLOCK: ${{ contains(github.event.pull_request.labels.*.name, inputs.block-label) }}
RENOVATE_ACTOR: ${{ inputs.renovate-actor }}
run: |
set -euo pipefail
# Latest review state left by this workflow's identity (github-actions[bot]).
Expand All @@ -71,6 +79,46 @@ jobs:
fi
exit 0
fi
# Stops without approving, and revokes an earlier automated approval.
reject() {
echo "Not approving: $1."
# Dismiss a stale approval
if [ "$latest_state" = "APPROVED" ]; then
echo "Revoking the earlier automated approval."
gh pr review "$PR_NUMBER" --request-changes \
--body "Revoked: $1. This PR needs a human review."
fi
exit 0
}
# The job-level gate only checks who opened the PR. Also check that
# the branch holds exactly one commit, authored by Renovate, signed, and
# pushed by Renovate, and that Renovate itself added the automerge label.
commits=$(gh api -X GET "repos/$GH_REPO/pulls/$PR_NUMBER/commits" -f per_page=2)
if [ "$(jq 'length' <<<"$commits")" != 1 ]; then
reject "the branch holds more than one commit"
fi
head_sha=$(jq -r '.[0].sha' <<<"$commits")
author=$(jq -r '.[0].author.login // empty' <<<"$commits")
if [ "$author" != "$RENOVATE_ACTOR" ]; then
reject "commit $head_sha is authored by '${author:-unknown}', not $RENOVATE_ACTOR"
fi
if [ "$(jq -r '.[0].commit.verification.verified' <<<"$commits")" != true ]; then
reject "commit $head_sha is not signed"
fi
activity=$(gh api -X GET "repos/$GH_REPO/activity" -f ref="refs/heads/$HEAD_REF" -f per_page=1)
if [ "$(jq -r '.[0].after // empty' <<<"$activity")" != "$head_sha" ]; then
reject "the latest push to $HEAD_REF is not the push of commit $head_sha"
fi
pusher=$(jq -r '.[0].actor.login // empty' <<<"$activity")
if [ "$pusher" != "$RENOVATE_ACTOR" ]; then
reject "commit $head_sha was pushed by '$pusher', not $RENOVATE_ACTOR"
fi
# Events come oldest first; keep who added the automerge label last.
labeler=$(gh api --paginate "repos/$GH_REPO/issues/$PR_NUMBER/events" \
--jq '.[] | select(.event == "labeled" and .label.name == env.LABEL) | .actor.login' | tail -n 1)
if [ "$labeler" != "$RENOVATE_ACTOR" ]; then
reject "the $LABEL label was added by '$labeler', not $RENOVATE_ACTOR"
fi
if [ "$latest_state" = "APPROVED" ]; then
echo "Already approved by github-actions; nothing to do."
exit 0
Expand Down
2 changes: 1 addition & 1 deletion docs/NEW_REPO.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ preCommitHooks = {

### Renovate auto-approve (optional)

To let Renovate auto-merge digest, patch, and minor PRs without a manual approval, copy `example/.github/workflows/renovate-auto-approve.yml` into your project and pin the `uses:` ref to a dev-kit release tag or commit SHA. It calls dev-kit's reusable `renovate-auto-approve.yml`, which approves Renovate PRs labeled `automerge` and skips (and revokes) anything labeled `security`.
To let Renovate auto-merge digest, patch, and minor PRs without a manual approval, copy `example/.github/workflows/renovate-auto-approve.yml` into your project and pin the `uses:` ref to a dev-kit release tag or commit SHA. It calls dev-kit's reusable `renovate-auto-approve.yml`, which approves Renovate PRs labeled `automerge` by Renovate itself and skips (and revokes) anything labeled `security`. It approves a PR only while the branch is still exactly one signed Renovate commit that Renovate itself pushed, so pushing onto a Renovate branch, including amending or rebasing its commit, revokes the automated approval and hands the review back to a human.

Two more things are needed:

Expand Down
2 changes: 2 additions & 0 deletions example/.github/workflows/renovate-auto-approve.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ on:
pull_request:
types: [opened, reopened, synchronize, labeled, unlabeled]
permissions:
contents: read
pull-requests: write
jobs:
approve:
# Pin @<sha-or-tag> to a dev-kit release tag or commit SHA before using this.
uses: opendefensecloud/dev-kit/.github/workflows/renovate-auto-approve.yml@<sha-or-tag>
permissions:
contents: read
pull-requests: write
Loading