Repository navigation
378 lines (351 loc) · 17.4 KB
/
Copy pathrelease-prep.yml
File metadata and controls
378 lines (351 loc) · 17.4 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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# Test workflow — exercises `cargo xtask release prepare` and the prep-PR
# flow on a GitHub Actions runner. Designed to graduate to `dev` later
# (renamed and minus the push trigger), so the inputs and shape match what
# the eventual `release-dispatch.yml` will expect.
#
# Two trigger paths:
#
# - `workflow_dispatch` — the future graduation path. Won't actually fire
# until this file lands on the default branch (`dev`). Keeps the inputs
# in place so the call shape is locked in.
#
# - `push` to `abernix/test-prep-action-*` — the current sandbox. Picks
# `version` from the branch-name suffix and `dry_run` from a commit
# message marker (`[execute]` opts in to actually opening a PR).
#
# Defaults to **dry-run**: prepares, diffs, logs auto-tag would-do — does
# not commit, push, or open a PR unless explicitly opted in.
name: Release: prep
on:
workflow_dispatch:
inputs:
version:
description: "Target version (e.g., 2.14.0)"
required: true
type: string
dry_run:
description: "Read-only run — don't commit, push, or open the prep PR"
required: false
default: true
type: boolean
pre_release:
description: "Pre-release prep — skip CHANGELOG generation; push the version-bump commit directly to the dispatched branch (no prep PR)"
required: false
default: false
type: boolean
push:
branches:
- "abernix/test-prep-action-*"
jobs:
test-prep:
runs-on: ubuntu-latest
permissions:
contents: write # to push the prep branch (when not dry-run)
pull-requests: write # to open the prep PR (when not dry-run)
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
# No persisted credentials — keeps apollo-bot2's PAT out of the
# default remote URL so it isn't implicitly available to every
# subsequent step. The one step that needs write access to a
# protected branch (pre-release prep push) supplies the PAT
# inline via env at push time.
persist-credentials: false
- name: Resolve version + mode
id: vars
run: |
set -euo pipefail
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION='${{ inputs.version }}'
DRY_RUN='${{ inputs.dry_run }}'
PRE_RELEASE='${{ inputs.pre_release }}'
else
# Branch name like abernix/test-prep-action-2.14.0 → version 2.14.0
VERSION="${GITHUB_REF_NAME##*-}"
# Default dry-run unless commit message contains `[execute]`.
if git log -1 --format=%B "$GITHUB_SHA" | grep -qF '[execute]'; then
DRY_RUN=false
else
DRY_RUN=true
fi
# Auto-detect pre-release from semver suffix (e.g. 2.14.2-rc.0).
if echo "$VERSION" | grep -q -- '-'; then
PRE_RELEASE=true
else
PRE_RELEASE=false
fi
fi
echo "Resolved: version=$VERSION dry_run=$DRY_RUN pre_release=$PRE_RELEASE"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "dry_run=$DRY_RUN" >> "$GITHUB_OUTPUT"
echo "pre_release=$PRE_RELEASE" >> "$GITHUB_OUTPUT"
# ---------------------------------------------------------------------
# Diagnostic: list available env var NAMES (values redacted by GHA for
# secrets) so we can confirm what's plumbed in. Helpful for sanity-
# checking that secrets like APOLLO_BOT2_GITHUB_PAT are wired up.
# ---------------------------------------------------------------------
- name: Log env variable names (values redacted)
env:
# Surface the secret here so it shows up in the env list when set.
# GHA still redacts the actual value in logs.
APOLLO_BOT2_GITHUB_PAT: ${{ secrets.APOLLO_BOT2_GITHUB_PAT }}
run: |
echo "::group::Environment variable names"
env | cut -d= -f1 | sort
echo "::endgroup::"
echo
if [ -n "${APOLLO_BOT2_GITHUB_PAT:-}" ]; then
echo "✓ APOLLO_BOT2_GITHUB_PAT is set (token length: ${#APOLLO_BOT2_GITHUB_PAT})"
else
echo "✗ APOLLO_BOT2_GITHUB_PAT is NOT set — add it as a repo secret to enable apollo-bot2 auth."
fi
# ---------------------------------------------------------------------
# Auth check: prove APOLLO_BOT2_GITHUB_PAT actually authenticates as
# the apollo-bot2 user. Read-only — just calls /user. This is the
# gate before we can trust this PAT for tag-push in a future workflow.
# ---------------------------------------------------------------------
- name: Authenticate as apollo-bot2
env:
GH_TOKEN: ${{ secrets.APOLLO_BOT2_GITHUB_PAT }}
run: |
set -euo pipefail
if [ -z "${GH_TOKEN:-}" ]; then
echo "::warning::APOLLO_BOT2_GITHUB_PAT secret not configured — skipping auth check."
echo " Configure it (Settings → Secrets and variables → Actions) to enable this step."
exit 0
fi
echo "Attempting to authenticate as apollo-bot2..."
IDENTITY=$(gh api user --jq '"login=\(.login) id=\(.id) type=\(.type)"')
echo "✓ Authenticated: $IDENTITY"
# Loud failure if it's NOT apollo-bot2 — catches the wrong-PAT case.
case "$IDENTITY" in
*login=apollo-bot2*) echo " Identity matches expectation (apollo-bot2)." ;;
*) echo "::error::Authenticated user is not apollo-bot2. Check the PAT owner." ; exit 1 ;;
esac
- name: Install dev environment (mise)
uses: jdx/mise-action@v5
- name: Show toolchain
run: |
rustc --version
cargo --version
helm version --short
helm-docs --version || true
cargo about --version || true
cargo deny --version || true
- name: Run release prepare (no commit, no push)
run: |
set -euo pipefail
if [ '${{ steps.vars.outputs.pre_release }}' = 'true' ]; then
cargo xtask release prepare --pre-release '${{ steps.vars.outputs.version }}'
else
cargo xtask release prepare '${{ steps.vars.outputs.version }}'
fi
- name: Show resulting diff
run: |
echo "::group::git diff"
git diff
echo "::endgroup::"
echo "::group::git status"
git status --short
echo "::endgroup::"
- name: Verify diff is non-empty
run: |
if [ -z "$(git status --porcelain)" ]; then
echo "::error::release prepare produced no changes — that's suspicious."
exit 1
fi
- name: Verify CHANGELOG.md gained a v${{ steps.vars.outputs.version }} entry
if: steps.vars.outputs.pre_release != 'true'
run: |
VERSION='${{ steps.vars.outputs.version }}'
if ! grep -q "^# \[${VERSION}\]" CHANGELOG.md; then
echo "::error::CHANGELOG.md is missing the v${VERSION} heading."
exit 1
fi
echo "Found v${VERSION} heading in CHANGELOG.md"
- name: Verify CHANGELOG.md was NOT modified (pre-release)
if: steps.vars.outputs.pre_release == 'true'
run: |
if ! git diff --quiet -- CHANGELOG.md; then
echo "::error::Pre-release prep should not modify CHANGELOG.md, but it did."
echo " This usually means the --pre-release flag was lost or the xtask changed behavior."
git diff -- CHANGELOG.md | head -40
exit 1
fi
echo "CHANGELOG.md untouched, as expected for a pre-release."
# ---------------------------------------------------------------------
# Auto-tag observability (logs only — does not push tags).
#
# The eventual `auto-tag-release.yml` workflow (M2) fires on push to
# `main`/`main-v*` and tags the merge commit's second parent. Auth for
# actual tag pushes likely needs `apollo-bot2`, not the default
# github-actions[bot] — that's a separate M2 problem.
#
# For now: log what auto-tag WOULD do, including the exact commit SHA.
# ---------------------------------------------------------------------
- name: Log what auto-tag would do (including exact SHA)
run: |
set -euo pipefail
VERSION='${{ steps.vars.outputs.version }}'
# `release prepare` has set apollo-router/Cargo.toml to the target
# version. In production, auto-tag reads this from the version
# branch's tip (the merge commit's second parent on main).
PREPARED_VERSION=$(grep '^version = ' apollo-router/Cargo.toml | head -1 | cut -d'"' -f2)
PREPARED_SHA=$(git rev-parse HEAD)
echo "::group::Auto-tag would-do analysis"
echo "Prepared apollo-router version: $PREPARED_VERSION"
echo "Prepared commit SHA: $PREPARED_SHA"
echo "Branch ref: ${GITHUB_REF_NAME}"
echo "Triggering commit ($GITHUB_SHA): $(git log -1 --format='%s' "$GITHUB_SHA")"
echo
# Latest released tag on origin — proxy for "main's current version"
# since we're not running on main directly. Use `for-each-ref --count=1`
# instead of `git tag ... | head -1` to avoid SIGPIPE under pipefail
# when the tag list is long.
LATEST_TAG=$(git for-each-ref --count=1 --sort=-v:refname \
--format='%(refname:short)' \
'refs/tags/v*' --no-contains HEAD)
LATEST_VERSION="${LATEST_TAG#v}"
echo "Latest release tag (proxy for main's current version): ${LATEST_TAG:-<none>}"
if [ -z "$LATEST_VERSION" ]; then
DECISION="WOULD TAG v$PREPARED_VERSION (first release on this line)"
elif [ "$PREPARED_VERSION" = "$LATEST_VERSION" ]; then
DECISION="WOULD SKIP — prepared version equals latest tag (no version change)"
else
if git rev-parse "v$PREPARED_VERSION" >/dev/null 2>&1; then
EXISTING_SHA=$(git rev-parse "v$PREPARED_VERSION")
DECISION="WOULD SKIP — tag v$PREPARED_VERSION already exists at $EXISTING_SHA (idempotent)"
else
DECISION="WOULD TAG v$PREPARED_VERSION"
fi
fi
echo
echo "Decision: $DECISION"
echo
# The actual tag-target SHA for the future merge to main is NOT this
# commit — it's the second parent of the merge commit on main. After
# the prep PR squash-merges to ${VERSION}, that branch's HEAD becomes
# a new SHA, and that new SHA is what gets tagged when the release
# PR (${VERSION} → main) merges.
echo "Note on tag-target SHA:"
echo " - In production, auto-tag tags the SECOND PARENT of the merge"
echo " commit on main: \$(git rev-parse \$MERGE_COMMIT^2)."
echo " - That SHA equals ${VERSION}'s HEAD at merge time, which can"
echo " only be known after the prep PR squash-merges and any other"
echo " backports land. The current HEAD ($PREPARED_SHA) is just"
echo " a preview snapshot, not the final tag target."
echo "::endgroup::"
echo
echo "Reminder: actual tag push from CI requires apollo-bot2's PAT,"
echo "not the default github-actions[bot]. Until that's wired up,"
echo "a human pushes the tag manually after the merge to main."
# ---------------------------------------------------------------------
# Below this point: only runs when explicitly opted in.
# ---------------------------------------------------------------------
- name: Stop here in dry-run mode
if: steps.vars.outputs.dry_run == 'true'
run: |
echo "Dry-run mode — not committing, pushing, or opening the prep PR."
echo " - workflow_dispatch: pass dry_run=false to actually run"
echo " - push trigger: include '[execute]' in commit message"
# =====================================================================
# Pre-release path: push the version-bump commit directly to the
# dispatched branch. Used for RC/preview cuts where there's nothing
# for a human reviewer to look at — just version bumps, no CHANGELOG
# changes — and a separate prep PR is just ceremony.
#
# The push step is the only place apollo-bot2's PAT is exposed: a
# fresh remote URL with the token baked in is used for that single
# `git push` and the token is left out of any persistent config.
# =====================================================================
- name: Commit prep on the dispatched branch (pre-release)
if: steps.vars.outputs.dry_run != 'true' && steps.vars.outputs.pre_release == 'true'
run: |
set -euo pipefail
git config user.name "apollo-bot2"
git config user.email "apollo-bot2@users.noreply.github.com"
git add -A
git commit -m "prep release: v${{ steps.vars.outputs.version }}"
- name: Push prep commit directly to ${{ github.ref_name }} (pre-release)
if: steps.vars.outputs.dry_run != 'true' && steps.vars.outputs.pre_release == 'true'
env:
APOLLO_BOT2_GITHUB_PAT: ${{ secrets.APOLLO_BOT2_GITHUB_PAT }}
run: |
set -euo pipefail
if [ -z "${APOLLO_BOT2_GITHUB_PAT:-}" ]; then
echo "::error::APOLLO_BOT2_GITHUB_PAT secret is required to push to a protected release branch."
exit 1
fi
# PAT-bearing URL only used for this single push command — never
# persisted into the local repo's git config.
git push \
"https://x-access-token:${APOLLO_BOT2_GITHUB_PAT}@github.com/${GITHUB_REPOSITORY}.git" \
"HEAD:${GITHUB_REF_NAME}"
echo "Pushed prep commit directly to ${GITHUB_REF_NAME} as apollo-bot2."
# =====================================================================
# Final-release path: existing fresh-branch + open-PR flow. Reviewers
# need to look at the CHANGELOG entries before they hit the release
# branch, so we don't push direct here.
# =====================================================================
- name: Commit prep on a fresh branch (final release)
if: steps.vars.outputs.dry_run != 'true' && steps.vars.outputs.pre_release != 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
PREP_BRANCH="prep-${{ steps.vars.outputs.version }}"
git checkout -b "$PREP_BRANCH"
git add -A
git commit -m "prep release: v${{ steps.vars.outputs.version }}"
echo "PREP_BRANCH=$PREP_BRANCH" >> "$GITHUB_ENV"
- name: Push prep branch (final release)
if: steps.vars.outputs.dry_run != 'true' && steps.vars.outputs.pre_release != 'true'
env:
APOLLO_BOT2_GITHUB_PAT: ${{ secrets.APOLLO_BOT2_GITHUB_PAT }}
run: |
set -euo pipefail
# New prep branch — workflow token is enough if its `contents:
# write` permission covers branch creation; fall through to bot2
# PAT if not. Either way, no PAT persisted in local git config.
if [ -n "${APOLLO_BOT2_GITHUB_PAT:-}" ]; then
REMOTE_URL="https://x-access-token:${APOLLO_BOT2_GITHUB_PAT}@github.com/${GITHUB_REPOSITORY}.git"
else
REMOTE_URL="https://x-access-token:${{ github.token }}@github.com/${GITHUB_REPOSITORY}.git"
fi
git push "$REMOTE_URL" "$PREP_BRANCH"
- name: Open prep PR (final release)
if: steps.vars.outputs.dry_run != 'true' && steps.vars.outputs.pre_release != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION='${{ steps.vars.outputs.version }}'
CHANGELOG_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/blob/${PREP_BRANCH}/CHANGELOG.md"
BODY=$(cat <<EOF
> **Note**
>
> When approved, this PR will merge into **the \`$VERSION\` branch** which will — upon being approved itself — merge into \`main\`.
>
> **Things to review in this PR**:
> - **[Rendered \`CHANGELOG.md\` on this branch]($CHANGELOG_URL)** — the new \`v$VERSION\` section is at the top
> - Version bumps (see _Files Changed_ for the true reality)
> - That it targets the right release branch (\`$VERSION\` in this case!)
>
> Opened automatically by the \`test-release-prep\` workflow.
EOF
)
gh pr create \
--repo "$GITHUB_REPOSITORY" \
--base "$VERSION" \
--head "$PREP_BRANCH" \
--title "prep release: v$VERSION" \
--body "$BODY"
- name: Done
run: |
if [ '${{ steps.vars.outputs.dry_run }}' = 'true' ]; then
echo "Dry-run complete. No commit, push, or PR."
elif [ '${{ steps.vars.outputs.pre_release }}' = 'true' ]; then
echo "Pre-release prep pushed directly to ${GITHUB_REF_NAME} as apollo-bot2."
else
echo "Prep PR opened from $PREP_BRANCH into ${{ steps.vars.outputs.version }}."
fi