mirror of
https://github.com/block/buzz.git
synced 2026-11-10 11:21:05 +00:00
## Summary Every push to `main` published a new relay and push gateway image, even when it only changed `desktop/`, `mobile/`, `docs/` or other paths `.dockerignore` keeps out of the Docker build context. Deployments that track the sortable `main-<commit UTC>-<7>` tag then rolled their relays onto an image built from identical inputs. The `push` trigger now skips the directories excluded from the build context (`desktop/`, `mobile/`, `docs/`, `bin/`, `sidecars/`, `.github/`) and re-includes `.github/workflows/docker.yml`, which defines the build. Tag pushes (`relay-v*`) still always publish, because GitHub does not evaluate path filters for tag pushes. Pull request builds keep their existing allowlist. ## Guard `scripts/test-relay-image-eligibility-workflow.sh`, which `ci.yml` runs, now requires the push filter to be exactly, in order: 1. `"**"`, 2. only `"!<dir>/**"` exclusions, each also listed in `.dockerignore`, 3. the `.github/workflows/docker.yml` re-inclusion. Comment and blank lines inside the list are skipped rather than ending it. So nobody can add a file exclusion, exclude a directory Docker sees, or reorder the re-inclusion in a way that hides a change to the image. ## Testing - `scripts/test-relay-image-eligibility-workflow.sh` passes. - The guard fails with a specific message for each of these: `"!crates/**"` added, `"!Cargo.lock"` added, single-quoted `'!Cargo.lock'`, the re-inclusion moved before `"!.github/**"`, `"**"` removed, and `"!crates/**"` appended after a comment or a blank line. - `actionlint` passes on `docker.yml`. ## Simpler The image is published only when its inputs can change. This removes no-op staging rollouts, plus the cancelled or queued Docker runs that each merge to main caused. No new concepts: the filter reuses `.dockerignore` as the only definition of what the image sees. Identified follow-up: root-level Markdown (for example `README.md`) is in the build context, so root-docs-only merges still publish. Skipping them would need a `.dockerignore` entry and a file-level filter rule, which the guard currently rejects on purpose. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Luke Tornquist <tornquist@squareup.com> Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
158 lines
6.9 KiB
Bash
Executable File
158 lines
6.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
workflow="$repo_root/.github/workflows/docker.yml"
|
|
selector="$repo_root/scripts/select-qualified-ci-run.jq"
|
|
predicate_builder="$repo_root/scripts/create-deployment-eligibility-predicate.jq"
|
|
|
|
require_literal() {
|
|
local needle=$1
|
|
grep -Fq -- "$needle" "$workflow" || {
|
|
echo "relay image workflow is missing required delivery contract: $needle" >&2
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
require_literal " qualify:"
|
|
require_literal "actions: read"
|
|
require_literal "actions/workflows/ci.yml/runs"
|
|
require_literal 'select-qualified-ci-run.jq'
|
|
require_literal "needs: [build, qualify]"
|
|
require_literal "https://buzz.block.xyz/attestations/deployment-eligibility/v1"
|
|
require_literal "actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6"
|
|
require_literal "if: matrix.variant == 'release'"
|
|
require_literal "BUZZ_SOURCE_SHA"
|
|
require_literal "BUZZ_BUILD_ID"
|
|
require_literal "BUZZ_BUILD_URL"
|
|
require_literal '- "deploy/charts/buzz/Chart.yaml"'
|
|
require_literal '- "scripts/create-deployment-eligibility-predicate.jq"'
|
|
require_literal '- "scripts/select-qualified-ci-run.jq"'
|
|
require_literal '- "scripts/test-relay-image-eligibility-workflow.sh"'
|
|
|
|
# Main pushes may skip only paths Docker never sees. The push filter must be, in
|
|
# order: "**", then directory exclusions that .dockerignore also excludes, then
|
|
# the re-included image workflow. Any other entry, or another order, could stop
|
|
# a change that alters the published image from publishing it.
|
|
push_trigger=$(awk '/^ push:$/{f=1;next} f&&/^ [a-z_]+:/{exit} f' "$workflow")
|
|
push_paths=()
|
|
while IFS= read -r entry; do
|
|
push_paths+=("$entry")
|
|
done < <(awk '/^ paths:$/{f=1;next} f&&/^[[:space:]]*(#.*)?$/{next} f&&/^ - /{print;next} f{exit}' <<<"$push_trigger")
|
|
fail_push_paths() {
|
|
echo "main push path filter: $1" >&2
|
|
exit 1
|
|
}
|
|
(( ${#push_paths[@]} >= 3 )) || fail_push_paths "expected \"**\", directory exclusions, then the workflow"
|
|
[[ "${push_paths[0]}" == ' - "**"' ]] || fail_push_paths 'first entry must be "**"'
|
|
last=$(( ${#push_paths[@]} - 1 ))
|
|
[[ "${push_paths[$last]}" == ' - ".github/workflows/docker.yml"' ]] ||
|
|
fail_push_paths "last entry must re-include .github/workflows/docker.yml"
|
|
for entry in "${push_paths[@]:1:last-1}"; do
|
|
[[ "$entry" =~ ^\ {6}-\ \"!([A-Za-z0-9._-]+)/\*\*\"$ ]] ||
|
|
fail_push_paths "only \"!<dir>/**\" exclusions may sit between \"**\" and the workflow, found: ${entry# - }"
|
|
excluded=${BASH_REMATCH[1]}
|
|
grep -Fxq "$excluded/" "$repo_root/.dockerignore" ||
|
|
fail_push_paths "skips $excluded/, which .dockerignore keeps in the image build context"
|
|
done
|
|
|
|
if grep -Fq "buzz-staging-dev" "$workflow"; then
|
|
echo "canonical relay image workflow references the preview-only image package" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# The merge job reads scripts/create-deployment-eligibility-predicate.jq from the
|
|
# workspace, so it must check out the source first. Guard against the checkout being
|
|
# dropped from that job (the workspace is otherwise empty and jq exits non-zero).
|
|
merge_job=$(awk '/^ merge:/{f=1} f&&/^ [a-z][a-z_-]*:$/&&!/^ merge:/{exit} f' "$workflow")
|
|
grep -Fq "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10" <<<"$merge_job" || {
|
|
echo "merge job must check out the source before building the eligibility predicate" >&2
|
|
exit 1
|
|
}
|
|
|
|
# Staging auto-promotes the newest main-<ts>-<7> gateway image, so the gateway
|
|
# must publish that tag and only from a source the relay CI qualified. Match
|
|
# whole lines (-x) so a commented-out or otherwise inactive copy cannot satisfy
|
|
# the contract after the live line regresses.
|
|
gateway_merge_job=$(awk '/^ push-gateway-merge:/{f=1} f&&/^ [a-z][a-z_-]*:$/&&!/^ push-gateway-merge:/{exit} f' "$workflow")
|
|
grep -Fxq " needs: [push-gateway-build, qualify]" <<<"$gateway_merge_job" || {
|
|
echo "push gateway image must publish only after same-SHA CI qualification" >&2
|
|
exit 1
|
|
}
|
|
grep -Fxq " type=raw,value=main-{{commit_date 'YYYYMMDDHHmmss'}}-{{sha}},enable=\${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}" <<<"$gateway_merge_job" || {
|
|
echo "push gateway image must publish the sortable main-<commit UTC>-<7> tag on main pushes" >&2
|
|
exit 1
|
|
}
|
|
|
|
select_run() {
|
|
jq -r --arg source_sha aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -f "$selector" | jq -r '.id // empty'
|
|
}
|
|
|
|
selected=$(select_run <<'JSON'
|
|
{"workflow_runs":[
|
|
{"id":100,"run_attempt":1,"head_sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","head_branch":"main","event":"push","status":"completed","conclusion":"failure"},
|
|
{"id":101,"run_attempt":1,"head_sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","head_branch":"main","event":"push","status":"completed","conclusion":"success"}
|
|
]}
|
|
JSON
|
|
)
|
|
[[ "$selected" == "101" ]] || {
|
|
echo "latest successful same-SHA main run was not selected" >&2
|
|
exit 1
|
|
}
|
|
|
|
selected=$(select_run <<'JSON'
|
|
{"workflow_runs":[
|
|
{"id":100,"run_attempt":1,"head_sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","head_branch":"main","event":"push","status":"completed","conclusion":"success"},
|
|
{"id":101,"run_attempt":1,"head_sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","head_branch":"main","event":"push","status":"completed","conclusion":"failure"}
|
|
]}
|
|
JSON
|
|
)
|
|
[[ -z "$selected" ]] || {
|
|
echo "stale successful run remained eligible after a newer failure" >&2
|
|
exit 1
|
|
}
|
|
|
|
selected=$(select_run <<'JSON'
|
|
{"workflow_runs":[
|
|
{"id":102,"run_attempt":1,"head_sha":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","head_branch":"main","event":"push","status":"completed","conclusion":"success"},
|
|
{"id":103,"run_attempt":1,"head_sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","head_branch":"feature","event":"pull_request","status":"completed","conclusion":"success"}
|
|
]}
|
|
JSON
|
|
)
|
|
[[ -z "$selected" ]] || {
|
|
echo "wrong-SHA or pull-request CI run was accepted" >&2
|
|
exit 1
|
|
}
|
|
|
|
predicate=$(jq -n \
|
|
--arg source_repository "block/buzz" \
|
|
--arg source_ref "refs/heads/main" \
|
|
--arg source_sha "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
|
|
--arg build_workflow ".github/workflows/docker.yml" \
|
|
--argjson build_run_id 200 \
|
|
--argjson build_run_attempt 2 \
|
|
--arg build_run_url "https://github.com/block/buzz/actions/runs/200/attempts/2" \
|
|
--arg qualification_workflow ".github/workflows/ci.yml" \
|
|
--argjson qualification_run_id 201 \
|
|
--argjson qualification_run_attempt 1 \
|
|
--arg qualification_run_url "https://github.com/block/buzz/actions/runs/201" \
|
|
--arg chart_version "0.1.8" \
|
|
-f "$predicate_builder")
|
|
|
|
jq -e '
|
|
.predicate_version == 1 and
|
|
.eligible == true and
|
|
.source.sha == "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" and
|
|
.build.run_id == 200 and
|
|
.qualification.run_id == 201 and
|
|
.qualification.conclusion == "success" and
|
|
.helm_chart == {"name":"buzz","compatible_version":"0.1.8"} and
|
|
(has("schema") | not) and
|
|
(.helm_chart | has("schema_compatibility") | not)
|
|
' <<<"$predicate" >/dev/null || {
|
|
echo "deployment eligibility predicate has an invalid contract" >&2
|
|
exit 1
|
|
}
|
|
|
|
echo "relay image eligibility workflow contract passed"
|