70bf21e064
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled
142 lines
7.1 KiB
YAML
142 lines
7.1 KiB
YAML
name: "Dependabot - auto-merge workerd updates"
|
|
|
|
# workerd ships a release every weekday, so the workerd-and-workers-types
|
|
# Dependabot group produces a steady stream of mechanical PRs that bump
|
|
# `workerd`, `@cloudflare/workers-types`, and miniflare's pinned version in
|
|
# lockstep (see .github/dependabot.yml). When CI is green these PRs require
|
|
# no human review, so we enable GitHub auto-merge on them — required status
|
|
# checks remain the gate, and a failing build still parks the PR for a human.
|
|
#
|
|
# Security model: this workflow effectively bypasses the human-review
|
|
# requirement on PRs whose head branch matches the Dependabot naming pattern,
|
|
# so we have to be paranoid about exactly what we're auto-merging. Before
|
|
# enabling auto-merge we verify that the PR contains exactly the two commits
|
|
# we expect (one from Dependabot, one from `miniflare-dependabot-versioning-prs.yml`)
|
|
# and that nothing outside the expected fileset has been touched. If any
|
|
# subsequent push violates those invariants we actively *disable* auto-merge,
|
|
# so a maintainer pushing a follow-up commit cancels rather than rides the
|
|
# auto-merge.
|
|
#
|
|
# DO NOT add `actions/checkout` to this workflow — `pull_request_target`
|
|
# grants write-scoped tokens, and checking out PR-controlled code with
|
|
# those tokens is the standard pwn vector.
|
|
|
|
on:
|
|
pull_request_target: # zizmor: ignore[dangerous-triggers] intentionally privileged metadata-only workflow; it never checks out or executes PR code
|
|
types: [opened, reopened, synchronize, ready_for_review]
|
|
|
|
permissions:
|
|
contents: write
|
|
pull-requests: write
|
|
|
|
jobs:
|
|
enable-auto-merge:
|
|
if: github.event.pull_request.user.login == 'dependabot[bot]'
|
|
runs-on: ubuntu-slim
|
|
steps:
|
|
- name: Fetch Dependabot metadata
|
|
id: meta
|
|
uses: dependabot/fetch-metadata@d7267f607e9d3fb96fc2fbe83e0af444713e90b7 # v2.3.0
|
|
with:
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Verify PR matches expected workerd-bump shape
|
|
id: verify
|
|
if: steps.meta.outputs.dependency-group == 'workerd-and-workers-types'
|
|
env:
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
REPO: ${{ github.repository }}
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
# Pull commits and changed files via the GitHub API. `--repo` is
|
|
# required because this workflow runs without `actions/checkout`,
|
|
# so `gh` has no git remote to infer the repo from.
|
|
commits_json=$(gh pr view --repo "$REPO" "$PR_NUMBER" --json commits)
|
|
files_json=$(gh pr view --repo "$REPO" "$PR_NUMBER" --json files)
|
|
|
|
fail() {
|
|
echo "verified=false" >> "$GITHUB_OUTPUT"
|
|
echo "reason=$1" >> "$GITHUB_OUTPUT"
|
|
echo "::warning::Refusing to enable auto-merge: $1"
|
|
exit 0
|
|
}
|
|
|
|
# --- Commit shape ---------------------------------------------------
|
|
# Expected: exactly two commits.
|
|
# 1. dependabot[bot] author, with a verified GitHub signature.
|
|
# 2. The changeset commit pushed by miniflare-dependabot-versioning-prs.yml,
|
|
# authored as `Wrangler automated PR updater <wrangler@cloudflare.com>`.
|
|
#
|
|
# We can't require a signature on the second commit (it's pushed via
|
|
# GH_ACCESS_TOKEN, which doesn't sign), so we lean on path/content
|
|
# checks below to constrain what that commit can do.
|
|
|
|
commit_count=$(echo "$commits_json" | jq '.commits | length')
|
|
if [ "$commit_count" -ne 2 ]; then
|
|
fail "expected exactly 2 commits, found $commit_count"
|
|
fi
|
|
|
|
first_author=$(echo "$commits_json" | jq -r '.commits[0].authors[0].login')
|
|
first_oid=$(echo "$commits_json" | jq -r '.commits[0].oid')
|
|
if [ "$first_author" != "dependabot[bot]" ]; then
|
|
fail "first commit author is '$first_author', expected 'dependabot[bot]'"
|
|
fi
|
|
|
|
# `gh pr view --json commits` doesn't expose signature info, so look
|
|
# it up via the REST commit endpoint.
|
|
first_verified=$(gh api "repos/$REPO/commits/$first_oid" --jq '.commit.verification.verified')
|
|
if [ "$first_verified" != "true" ]; then
|
|
fail "first commit (Dependabot) does not have a verified signature"
|
|
fi
|
|
|
|
second_email=$(echo "$commits_json" | jq -r '.commits[1].authors[0].email // ""')
|
|
second_message=$(echo "$commits_json" | jq -r '.commits[1].messageHeadline // ""')
|
|
if [ "$second_email" != "wrangler@cloudflare.com" ]; then
|
|
fail "second commit author email is '$second_email', expected 'wrangler@cloudflare.com'"
|
|
fi
|
|
if ! echo "$second_message" | grep -qE '^Update dependencies of '; then
|
|
fail "second commit message '$second_message' does not match expected changeset commit shape"
|
|
fi
|
|
|
|
# --- Changed files allowlist ---------------------------------------
|
|
# The only paths a workerd bump should touch:
|
|
# - .changeset/dependabot-update-*.md (added by the changeset job)
|
|
# - packages/*/package.json (workerd, workers-types pins)
|
|
# - pnpm-lock.yaml
|
|
# - pnpm-workspace.yaml (catalog entries)
|
|
allowed='^(\.changeset/dependabot-update-.*\.md|packages/[^/]+/package\.json|pnpm-lock\.yaml|pnpm-workspace\.yaml)$'
|
|
|
|
unexpected=$(echo "$files_json" | jq -r '.files[].path' | grep -vE "$allowed" || true)
|
|
if [ -n "$unexpected" ]; then
|
|
fail "PR touches unexpected files:$(echo "$unexpected" | sed 's/^/ /' | tr '\n' ',')"
|
|
fi
|
|
|
|
# Make sure the changeset is actually present — the changeset job
|
|
# may not have run yet, in which case we bail and wait for the
|
|
# `synchronize` event from its push.
|
|
if ! echo "$files_json" | jq -e '.files[] | select(.path | startswith(".changeset/dependabot-update-"))' > /dev/null; then
|
|
fail "changeset file not yet present; waiting for changeset job to push it"
|
|
fi
|
|
|
|
echo "verified=true" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Enable auto-merge
|
|
if: steps.meta.outputs.dependency-group == 'workerd-and-workers-types' && steps.verify.outputs.verified == 'true'
|
|
run: gh pr merge --auto --squash "$PR_URL"
|
|
env:
|
|
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Disable auto-merge if verification failed
|
|
# If a previous run enabled auto-merge but a later push broke the
|
|
# invariants, actively cancel auto-merge so the bad commit can't ride.
|
|
# `gh pr merge --disable-auto` is a no-op (and exits 0) if auto-merge
|
|
# was never enabled, so this is safe to always run on the failure path.
|
|
if: always() && steps.meta.outputs.dependency-group == 'workerd-and-workers-types' && steps.verify.outputs.verified != 'true'
|
|
run: gh pr merge --disable-auto "$PR_URL" || true
|
|
env:
|
|
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|