Skip to content

Commit 958c5b6

Browse files
authored
Gate release publishing (#3539)
* Gate release publishing * Tighten release tag gate * Allow historical backport release tags
1 parent 3fed219 commit 958c5b6

1 file changed

Lines changed: 113 additions & 1 deletion

File tree

.github/workflows/publish.yaml

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,123 @@ name: Publish
33
on:
44
push:
55
tags:
6-
- '**'
6+
- '[0-9]+.[0-9]+.[0-9]+'
7+
- '[0-9]+.[0-9]+.[0-9]+rc[0-9]+'
8+
- '[0-9]+.[0-9]+.[0-9]+\+backport-[0-9]+'
9+
workflow_dispatch:
10+
inputs:
11+
skip_test_gate:
12+
description: "Skip the test-gate check for an emergency release. Run only with --ref <tag>."
13+
required: false
14+
default: false
15+
type: boolean
716

817
env:
918
dists-artifact-name: python-package-distributions
1019

1120
jobs:
21+
verify:
22+
name: Verify release gate
23+
runs-on: ubuntu-latest
24+
permissions:
25+
actions: read
26+
checks: read
27+
contents: read
28+
steps:
29+
- name: Validate release tag
30+
env:
31+
RELEASE_REF: ${{ github.ref }}
32+
RELEASE_TAG: ${{ github.ref_name }}
33+
run: |
34+
set -euo pipefail
35+
if [ "$RELEASE_REF" != "refs/tags/$RELEASE_TAG" ]; then
36+
echo "Publish must run from a release tag ref. Use: gh workflow run publish.yaml --ref <tag>" >&2
37+
exit 1
38+
fi
39+
python3 - <<'PY'
40+
import os
41+
import re
42+
import sys
43+
44+
tag = os.environ["RELEASE_TAG"]
45+
if re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:rc[0-9]+|\+backport-[0-9]+)?", tag):
46+
sys.exit(0)
47+
print(f"Release tag {tag!r} must look like 0.66.3, 0.66.3rc1, or 0.2.11+backport-1.", file=sys.stderr)
48+
sys.exit(1)
49+
PY
50+
- name: Verify test-gate passed
51+
if: ${{ github.event_name != 'workflow_dispatch' || inputs.skip_test_gate == false }}
52+
env:
53+
GH_TOKEN: ${{ github.token }}
54+
RELEASE_TAG: ${{ github.ref_name }}
55+
REPOSITORY: ${{ github.repository }}
56+
run: |
57+
set -euo pipefail
58+
response="$(
59+
gh api "repos/${REPOSITORY}/commits/refs/tags/${RELEASE_TAG}/check-runs?check_name=test-gate&filter=latest"
60+
)"
61+
runs="$(
62+
jq -c '
63+
[
64+
.check_runs[]?
65+
| select(.name == "test-gate")
66+
| {
67+
status,
68+
conclusion,
69+
completed_at,
70+
started_at,
71+
head_sha,
72+
html_url,
73+
run_id: (try ((.html_url // "") | capture("/actions/runs/(?<id>[0-9]+)/job/").id) catch "")
74+
}
75+
]
76+
| sort_by(.completed_at // .started_at // "")
77+
| reverse
78+
| .[]
79+
' <<< "$response"
80+
)"
81+
if [ -z "$runs" ]; then
82+
echo "No test-gate check run was found for tag ${RELEASE_TAG}." >&2
83+
echo "Wait for Test on main to finish, then re-run Publish. For emergency releases, dispatch with skip_test_gate=true." >&2
84+
exit 1
85+
fi
86+
87+
while IFS= read -r run; do
88+
run_id="$(jq -r '.run_id' <<< "$run")"
89+
head_sha="$(jq -r '.head_sha' <<< "$run")"
90+
if [ -z "$run_id" ]; then
91+
continue
92+
fi
93+
metadata="$(gh api "repos/${REPOSITORY}/actions/runs/${run_id}")"
94+
if ! jq -e --arg head_sha "$head_sha" \
95+
'.name == "Test" and .event == "push" and .head_branch == "main" and .head_sha == $head_sha' \
96+
<<< "$metadata" > /dev/null; then
97+
continue
98+
fi
99+
status="$(jq -r '.status' <<< "$run")"
100+
conclusion="$(jq -r '.conclusion' <<< "$run")"
101+
html_url="$(jq -r '.html_url' <<< "$run")"
102+
if [ "$status" != "completed" ]; then
103+
echo "test-gate for tag ${RELEASE_TAG} is ${status}; re-run Publish after it completes." >&2
104+
exit 1
105+
fi
106+
if [ "$conclusion" != "success" ]; then
107+
echo "test-gate for tag ${RELEASE_TAG} concluded ${conclusion}: ${html_url}" >&2
108+
exit 1
109+
fi
110+
exit 0
111+
done <<< "$runs"
112+
113+
echo "Found test-gate check runs for tag ${RELEASE_TAG}, but none came from the Test workflow push on main." >&2
114+
echo "Wait for Test on main to finish, then re-run Publish. For emergency releases, dispatch with skip_test_gate=true." >&2
115+
exit 1
116+
- name: Report skipped test gate
117+
if: ${{ github.event_name == 'workflow_dispatch' && inputs.skip_test_gate }}
118+
run: echo "::warning::test-gate verification was skipped for this manual release."
119+
12120
build:
121+
needs:
122+
- verify
13123
runs-on: ubuntu-latest
14124
permissions:
15125
contents: read
@@ -99,3 +209,5 @@ jobs:
99209
build-args: |
100210
VERSION=${{ github.ref_name }}
101211
platforms: linux/amd64,linux/arm64
212+
provenance: mode=max
213+
sbom: true

0 commit comments

Comments
 (0)