Repository navigation
221 lines (201 loc) · 8.85 KB
/
Copy pathrelease.yml
File metadata and controls
221 lines (201 loc) · 8.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
name: Release
on:
workflow_run:
workflows: [Release candidate, CI]
types: [completed]
branches: [main]
workflow_dispatch:
inputs:
release_sha:
description: Exact main commit with verified release artifacts
required: true
type: string
candidate_run_id:
description: Successful release-candidate run containing the verified artifacts
required: true
type: string
permissions: {}
concurrency:
group: release-main
cancel-in-progress: false
jobs:
candidate:
if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' && github.event.workflow_run.head_branch == 'main'
runs-on: ubuntu-24.04
permissions:
actions: read
contents: read
outputs:
ready: ${{ steps.verified.outputs.ready }}
candidate_run_id: ${{ steps.verified.outputs.candidate_run_id }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
ref: ${{ github.event.workflow_run.head_sha }}
- id: owner
shell: bash
env:
RELEASE_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
VERSION="$(sed -nE 's/^version = "([^"]+)".*/\1/p' pyproject.toml | head -n 1)"
TARGET="$(git ls-remote origin "refs/tags/release-candidate/$VERSION" | awk '{print $1}')"
BRANCH_SHA="$(git ls-remote origin refs/heads/main | cut -f1)"
[[ "$TARGET" == "$RELEASE_SHA" && "$BRANCH_SHA" == "$RELEASE_SHA" ]] && echo 'ready=true' >> "$GITHUB_OUTPUT" || echo 'ready=false' >> "$GITHUB_OUTPUT"
- name: Require successful full CI for this commit
id: verified
if: steps.owner.outputs.ready == 'true'
env:
GH_TOKEN: ${{ github.token }}
RELEASE_SHA: ${{ github.event.workflow_run.head_sha }}
shell: bash
run: |
set -Eeuo pipefail
CANDIDATE_RUN_ID="$(gh run list --repo "$GITHUB_REPOSITORY" --workflow release-candidate.yml \
--commit "$RELEASE_SHA" --event push --limit 1 \
--json databaseId,status,conclusion \
--jq '.[0] | select(.status == "completed" and .conclusion == "success") | .databaseId // empty')"
CI_RUN_ID="$(gh run list --repo "$GITHUB_REPOSITORY" --workflow ci.yml \
--commit "$RELEASE_SHA" --event push --limit 1 \
--json databaseId,status,conclusion \
--jq '.[0] | select(.status == "completed" and .conclusion == "success") | .databaseId // empty')"
[[ -n "$CANDIDATE_RUN_ID" && -n "$CI_RUN_ID" ]] || exit 0
RELEASE_RUN_IDS="$(gh run list --repo "$GITHUB_REPOSITORY" --workflow release.yml \
--commit "$RELEASE_SHA" --limit 20 --json databaseId,status,conclusion \
--jq '.[] | select(.status == "completed" and .conclusion == "success") | .databaseId')"
while read -r RELEASE_RUN_ID; do
[[ -n "$RELEASE_RUN_ID" ]] || continue
PUBLISHED="$(gh run view "$RELEASE_RUN_ID" --repo "$GITHUB_REPOSITORY" --json jobs \
--jq '.jobs[] | select(.name == "publish" and .conclusion == "success") | .name')"
[[ -z "$PUBLISHED" ]] || exit 0
done <<< "$RELEASE_RUN_IDS"
echo 'ready=true' >> "$GITHUB_OUTPUT"
echo "candidate_run_id=$CANDIDATE_RUN_ID" >> "$GITHUB_OUTPUT"
dispatch-publish:
needs: candidate
if: needs.candidate.outputs.ready == 'true'
runs-on: ubuntu-24.04
permissions: {}
steps:
- name: Dispatch the OIDC-compatible publish workflow
env:
GH_TOKEN: ${{ secrets.RELEASE_GH_TOKEN }}
RELEASE_SHA: ${{ github.event.workflow_run.head_sha }}
CANDIDATE_RUN_ID: ${{ needs.candidate.outputs.candidate_run_id }}
run: |
set -Eeuo pipefail
test -n "${GH_TOKEN:-}" || { echo 'RELEASE_GH_TOKEN is required to dispatch the trusted-publishing workflow' >&2; exit 1; }
gh workflow run release.yml --repo "$GITHUB_REPOSITORY" --ref main \
-f release_sha="$RELEASE_SHA" \
-f candidate_run_id="$CANDIDATE_RUN_ID"
publish:
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
environment: pypi
timeout-minutes: 45
permissions:
actions: read
contents: write
id-token: write
outputs:
version: ${{ steps.release.outputs.version }}
cascade_ready: ${{ steps.cascade-state.outputs.ready }}
env:
RELEASE_SHA: ${{ inputs.release_sha }}
RELEASE_BRANCH: main
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
ref: ${{ env.RELEASE_SHA }}
- name: Revalidate release commit and tested runs
env:
GH_TOKEN: ${{ github.token }}
CANDIDATE_RUN_ID: ${{ inputs.candidate_run_id }}
run: |
set -Eeuo pipefail
[[ "$RELEASE_SHA" =~ ^[0-9a-f]{40}$ ]]
[[ "$(git rev-parse HEAD)" == "$RELEASE_SHA" ]]
[[ "$(git ls-remote origin refs/heads/main | cut -f1)" == "$RELEASE_SHA" ]]
VERSION="$(sed -nE 's/^version = "([^\"]+)".*/\1/p' pyproject.toml | head -n 1)"
[[ -n "$VERSION" ]]
[[ "$(git ls-remote origin "refs/tags/release-candidate/$VERSION" | cut -f1)" == "$RELEASE_SHA" ]]
CANDIDATE="$(gh run view "$CANDIDATE_RUN_ID" --repo "$GITHUB_REPOSITORY" \
--json headSha,event,status,conclusion --jq '[.headSha,.event,.status,.conclusion]|@tsv')"
[[ "$CANDIDATE" == "$RELEASE_SHA"$'\tpush\tcompleted\tsuccess' ]]
CI_RUN_ID="$(gh run list --repo "$GITHUB_REPOSITORY" --workflow ci.yml \
--commit "$RELEASE_SHA" --event push --limit 1 \
--json databaseId,status,conclusion \
--jq '.[0] | select(.status == "completed" and .conclusion == "success") | .databaseId // empty')"
[[ -n "$CI_RUN_ID" ]]
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.12'
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
with:
version: "0.13.5"
enable-cache: false
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
with:
version: 10.33.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 24.22.1
cache: pnpm
cache-dependency-path: abxbus-ts/pnpm-lock.yaml
- name: Use npm with trusted publishing support
run: npm install -g npm@12.0.1
- name: Download exact tested release artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: release-artifacts-${{ env.RELEASE_SHA }}
path: ${{ runner.temp }}/release-artifacts
run-id: ${{ inputs.candidate_run_id }}
github-token: ${{ github.token }}
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable
with:
toolchain: 1.99.1
- name: Authenticate with crates.io trusted publishing
id: crates-auth
uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1
- name: Record verified release
id: release
run: |
set -Eeuo pipefail
VERSION="$(sed -nE 's/^version = "([^"]+)".*/\1/p' pyproject.toml | head -n 1)"
test -n "$VERSION"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Publish tested commit
env:
GH_TOKEN: ${{ secrets.RELEASE_GH_TOKEN || github.token }}
RELEASE_ARTIFACT_DIR: ${{ runner.temp }}/release-artifacts
CARGO_REGISTRY_TOKEN: ${{ steps.crates-auth.outputs.token }}
run: ./bin/release.sh
- name: Confirm this tested commit owns the release tag
id: cascade-state
run: |
set -Eeuo pipefail
TARGET=""
while read -r sha _; do
TARGET="$sha"
done < <(git ls-remote origin "refs/tags/${{ steps.release.outputs.version }}")
if [[ "$TARGET" == "$RELEASE_SHA" ]]; then
echo "ready=true" >> "$GITHUB_OUTPUT"
else
echo "ready=false" >> "$GITHUB_OUTPUT"
fi
cascade:
name: Cascade verified release
needs: publish
if: needs.publish.outputs.cascade_ready == 'true'
permissions:
actions: read
contents: read
uses: ArchiveBox/monorepo/.github/workflows/cascade-release.yml@main
with:
upstream_package: abxbus
upstream_version: ${{ needs.publish.outputs.version }}
upstream_artifact_name: release-artifacts-${{ inputs.release_sha }}
upstream_run_id: ${{ inputs.candidate_run_id }}
secrets:
release_token: ${{ secrets.RELEASE_GH_TOKEN }}