mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
## Summary This PR makes two changes to the push gateway image job (`push-gateway-merge` in `docker.yml`). Together they let staging auto-promote the gateway the same way it already auto-promotes the relay: 1. **Publish only after same-SHA CI qualification.** `push-gateway-merge` now needs `qualify` as well as `push-gateway-build`. This is the same gate the relay's `merge` job already uses: a successful `ci.yml` push run on the exact source SHA. Before this change, the gateway image was published even when main CI was red. That was safe while staging pinned the gateway by hand. It isn't safe once the newest main image deploys automatically. 2. **Publish the sortable `:main-<YYYYMMDDHHmmss>-<sha7>` tag on main pushes.** It's the same `type=raw` line the relay matrix gained in #8240. It uses the same `enable=` guard, so it applies only to `push` on `refs/heads/main`. A Lexical selector on `^main-\d{14}-[0-9a-f]{7}$` can then pick the newest gateway build from the tag list alone. `:sha-<7>` has no ordering. `:main`, `:sha-<7>`, and the release semver tags don't change. The gateway has no debug variant, so no `debug-main-*` tag is published. **Tradeoff:** a gateway image now appears only after `qualify` finishes, which takes as long as main CI. If main CI fails, no gateway image is published for that commit, and that includes `:main` and `:sha-<7>`. The relay already behaves this way, and it's the behavior we want for anything that auto-deploys. **What got simpler:** the relay and gateway images now share the same publication gate and the same tag set. Downstream tooling no longer needs a special case for the gateway's unqualified, unordered tags. **What stays different:** the relay's `merge` job also attests the `deployment-eligibility/v1` predicate (`docs/deployment-identity.md`); the gateway publishes no such attestation. That's intentional for now: the staging promotion guard selects the gateway by package and `main-*` tag and does not verify attestations, so the gateway warehouse must not be wired to require one. If promotion ever starts verifying eligibility, the gateway needs the same attestation first. ### Related issue N/A (staging rollout tooling). The downstream promotion-guard and warehouse changes land separately. The warehouse switches on only after this tag has been published. ### Testing - `scripts/test-relay-image-eligibility-workflow.sh` now pins both properties of the `push-gateway-merge` job (it runs in CI under "Relay image eligibility contract"). Both checks match whole active lines, so a commented-out copy can't satisfy them. - At `9b8c49600` it passes. - With the live `needs` line replaced by `needs: [push-gateway-build]` and a commented-out `# needs: [push-gateway-build, qualify]` left behind, it fails. - With the gateway `main-*` line commented out, it fails. - With `needs` reverted to `push-gateway-build`, it fails with `push gateway image must publish only after same-SHA CI qualification`. - With the `main-*` tag line removed, it fails with `push gateway image must publish the sortable main-<commit UTC>-<7> tag on main pushes`. - `scripts/test-release-ref-contract.sh` now counts the sortable `main-*` tag line on all three image legs (relay, relay-debug, gateway) instead of two. It passes at `9b8c49600`. - `bin/actionlint .github/workflows/docker.yml` is clean at `f6c03b6b5`. - PR runs don't push images. The first real `main-*` gateway tag appears on the first main push after merge, and I'll check the registry for it on that manifest. --------- Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
132 lines
5.5 KiB
Bash
Executable File
132 lines
5.5 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"'
|
|
|
|
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"
|