Skip to content

Commit 0a42693

Browse files
committed
release-gate.yml creates tag to trigger release.yml to enforce branch protection rules
1 parent 6496c49 commit 0a42693

2 files changed

Lines changed: 165 additions & 1 deletion

File tree

.github/workflows/release-gate.yml

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
name: Release Gate
2+
3+
on:
4+
pull_request:
5+
types: [closed]
6+
branches: [main]
7+
8+
jobs:
9+
release-gate:
10+
name: Tag and update release branches
11+
runs-on: ubuntu-latest
12+
# Only run when a release/ branch is merged (not just closed)
13+
if: |
14+
github.event.pull_request.merged == true &&
15+
startsWith(github.event.pull_request.head.ref, 'release/v')
16+
17+
permissions:
18+
contents: write
19+
20+
steps:
21+
- uses: actions/create-github-app-token@v1
22+
id: app-token
23+
with:
24+
app-id: ${{ secrets.APP_ID }}
25+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
26+
27+
- uses: actions/checkout@v4
28+
with:
29+
# Full history required for version comparison against existing tags
30+
# and for the fast-forward push to stable/beta.
31+
fetch-depth: 0
32+
token: ${{ steps.app-token.outputs.token }}
33+
34+
- name: Extract and validate version
35+
id: version
36+
run: |
37+
BRANCH="${{ github.event.pull_request.head.ref }}"
38+
NEW_VERSION="${BRANCH#release/}"
39+
echo "new=${NEW_VERSION}" >> $GITHUB_OUTPUT
40+
41+
# Determine if this is an RC
42+
if echo "$NEW_VERSION" | grep -qE '\-rc[0-9]+$'; then
43+
echo "is_rc=true" >> $GITHUB_OUTPUT
44+
else
45+
echo "is_rc=false" >> $GITHUB_OUTPUT
46+
fi
47+
48+
- name: Validate version is strictly increasing
49+
run: |
50+
NEW_VERSION="${{ steps.version.outputs.new }}"
51+
52+
# Get the latest tag; if none exist yet, skip the comparison
53+
LATEST_TAG=$(git tag --list 'v*' --sort=-version:refname | head -n1)
54+
if [ -z "$LATEST_TAG" ]; then
55+
echo "No existing tags found — skipping version comparison"
56+
exit 0
57+
fi
58+
59+
LATEST_VERSION="${LATEST_TAG#v}"
60+
61+
python3 - <<EOF
62+
import sys
63+
from packaging.version import Version
64+
65+
def normalize(v):
66+
# Convert vX.Y.Z-rcQ → X.Y.ZrcQ (PEP 440)
67+
return v.replace("-rc", "rc")
68+
69+
new = Version(normalize("$NEW_VERSION"))
70+
latest = Version(normalize("$LATEST_VERSION"))
71+
72+
print(f"Latest tag : {latest}")
73+
print(f"New version: {new}")
74+
75+
if new <= latest:
76+
print(f"\n❌ {new} is not strictly greater than current {latest}")
77+
sys.exit(1)
78+
79+
print(f"\n✅ Version order is valid")
80+
EOF
81+
82+
- name: Configure git
83+
run: |
84+
git config user.name "commit-boost-release-bot[bot]"
85+
git config user.email "commit-boost-release-bot[bot]@users.noreply.github.com"
86+
87+
- name: Create and push tag
88+
run: |
89+
VERSION="${{ steps.version.outputs.new }}"
90+
git tag "$VERSION" HEAD
91+
git push origin "$VERSION"
92+
# Branch fast-forwarding happens in release.yml after all artifacts
93+
# are successfully built. stable/beta are never touched if the build fails.

.github/workflows/release.yml

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,8 +316,79 @@ jobs:
316316
with:
317317
files: ./artifacts/**/*
318318
draft: true
319-
prerelease: false
319+
prerelease: ${{ contains(github.ref_name, '-rc') }}
320320
tag_name: ${{ github.ref_name }}
321321
name: ${{ github.ref_name }}
322322
env:
323323
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
324+
325+
# Fast-forwards stable (full release) or beta (RC) to the new tag.
326+
# Runs after all artifacts are built and the draft release is created,
327+
# so stable/beta are never touched if any part of the pipeline fails.
328+
fast-forward-branch:
329+
needs:
330+
- finalize-release
331+
runs-on: ubuntu-latest
332+
steps:
333+
- uses: actions/create-github-app-token@v1
334+
id: app-token
335+
with:
336+
app-id: ${{ secrets.APP_ID }}
337+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
338+
339+
- uses: actions/checkout@v4
340+
with:
341+
fetch-depth: 0
342+
token: ${{ steps.app-token.outputs.token }}
343+
344+
- name: Configure git
345+
run: |
346+
git config user.name "commit-boost-release-bot[bot]"
347+
git config user.email "commit-boost-release-bot[bot]@users.noreply.github.com"
348+
349+
- name: Fast-forward beta branch (RC releases)
350+
if: contains(github.ref_name, '-rc')
351+
run: |
352+
git checkout beta
353+
git merge --ff-only "${{ github.ref_name }}"
354+
git push origin beta
355+
356+
- name: Fast-forward stable branch (full releases)
357+
if: "!contains(github.ref_name, '-rc')"
358+
run: |
359+
git checkout stable
360+
git merge --ff-only "${{ github.ref_name }}"
361+
git push origin stable
362+
363+
# Deletes the tag if any job in the release pipeline fails.
364+
# This keeps the tag and release artifacts in sync — a tag should only
365+
# exist if the full pipeline completed successfully.
366+
# stable/beta are never touched on failure since fast-forward-branch
367+
# only runs after finalize-release succeeds.
368+
#
369+
# Note: if finalize-release specifically fails, a draft release may already
370+
# exist on GitHub pointing at the now-deleted tag and will need manual cleanup.
371+
cleanup-on-failure:
372+
needs:
373+
- build-binaries-linux
374+
- build-binaries-darwin
375+
- sign-binaries
376+
- build-and-push-pbs-docker
377+
- build-and-push-signer-docker
378+
- finalize-release
379+
- fast-forward-branch
380+
runs-on: ubuntu-latest
381+
if: failure()
382+
steps:
383+
- uses: actions/create-github-app-token@v1
384+
id: app-token
385+
with:
386+
app-id: ${{ secrets.APP_ID }}
387+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
388+
389+
- uses: actions/checkout@v4
390+
with:
391+
token: ${{ steps.app-token.outputs.token }}
392+
393+
- name: Delete tag
394+
run: git push origin --delete ${{ github.ref_name }}

0 commit comments

Comments
 (0)