Skip to content

Commit 92eada8

Browse files
authored
Merge pull request #3033 from github/mbg/ci/rollback-release
Add workflow for rolling back release
2 parents 02ab253 + 872a6a4 commit 92eada8

File tree

7 files changed

+408
-79
lines changed

7 files changed

+408
-79
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
name: "Prepare mergeback branch"
2+
description: Prepares a mergeback branch and opens a PR for it
3+
inputs:
4+
base:
5+
description: "The name of the base branch"
6+
required: true
7+
head:
8+
description: "The name of the head branch"
9+
required: true
10+
branch:
11+
description: "The name of the branch to create."
12+
required: true
13+
version:
14+
description: "The new version"
15+
required: true
16+
token:
17+
description: "The token to use"
18+
required: true
19+
dry-run:
20+
description: "Set to true to skip creating the PR. The branch will still be pushed."
21+
default: "false"
22+
runs:
23+
using: composite
24+
steps:
25+
- name: Create mergeback branch
26+
shell: bash
27+
env:
28+
VERSION: "${{ inputs.version }}"
29+
NEW_BRANCH: "${{ inputs.branch }}"
30+
run: |
31+
set -exu
32+
33+
# Ensure we are on the new branch
34+
git checkout "${NEW_BRANCH}"
35+
36+
# Update the version number ready for the next release
37+
npm version patch --no-git-tag-version
38+
39+
# Update the changelog, adding a new version heading directly above the most recent existing one
40+
awk '!f && /##/{print "'"## [UNRELEASED]\n\nNo user facing changes.\n"'"; f=1}1' CHANGELOG.md > temp && mv temp CHANGELOG.md
41+
git add .
42+
git commit -m "Update changelog and version after ${VERSION}"
43+
44+
git push origin "${NEW_BRANCH}"
45+
46+
- name: Create PR
47+
shell: bash
48+
if: inputs.dry-run != 'true'
49+
env:
50+
VERSION: "${{ inputs.version }}"
51+
BASE_BRANCH: "${{ inputs.base }}"
52+
HEAD_BRANCH: "${{ inputs.head }}"
53+
NEW_BRANCH: "${{ inputs.branch }}"
54+
GITHUB_TOKEN: "${{ inputs.token }}"
55+
run: |
56+
set -exu
57+
pr_title="Mergeback ${VERSION} ${HEAD_BRANCH} into ${BASE_BRANCH}"
58+
pr_body=$(cat << EOF
59+
This PR bumps the version number and updates the changelog after the ${VERSION} release.
60+
61+
Please do the following:
62+
63+
- [ ] Remove and re-add the "Rebuild" label to the PR to trigger just this workflow.
64+
- [ ] Wait for the "Rebuild" workflow to push a commit updating the distribution files.
65+
- [ ] Mark the PR as ready for review to trigger the full set of PR checks.
66+
- [ ] Approve and merge the PR. When merging the PR, make sure "Create a merge commit" is
67+
selected rather than "Squash and merge" or "Rebase and merge".
68+
EOF
69+
)
70+
71+
# PR checks won't be triggered on PRs created by Actions. Therefore mark the PR as draft
72+
# so that a maintainer can take the PR out of draft, thereby triggering the PR checks.
73+
gh pr create \
74+
--head "${NEW_BRANCH}" \
75+
--base "${BASE_BRANCH}" \
76+
--title "${pr_title}" \
77+
--label "Rebuild" \
78+
--body "${pr_body}" \
79+
--assignee "${GITHUB_ACTOR}" \
80+
--draft

.github/workflows/post-release-mergeback.yml

Lines changed: 8 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -124,48 +124,15 @@ jobs:
124124
cat $PARTIAL_CHANGELOG
125125
echo "::endgroup::"
126126
127-
- name: Create mergeback branch
127+
- name: Create mergeback branch and PR
128128
if: ${{ steps.check.outputs.exists != 'true' && endsWith(github.ref_name, steps.getVersion.outputs.latest_release_branch) }}
129-
env:
130-
VERSION: "${{ steps.getVersion.outputs.version }}"
131-
NEW_BRANCH: "${{ steps.getVersion.outputs.newBranch }}"
132-
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
133-
run: |
134-
set -exu
135-
pr_title="Mergeback ${VERSION} ${HEAD_BRANCH} into ${BASE_BRANCH}"
136-
pr_body=$(cat << EOF
137-
This PR bumps the version number and updates the changelog after the ${VERSION} release.
138-
139-
Please do the following:
140-
141-
- [ ] Remove and re-add the "Rebuild" label to the PR to trigger just this workflow.
142-
- [ ] Wait for the "Rebuild" workflow to push a commit updating the distribution files.
143-
- [ ] Mark the PR as ready for review to trigger the full set of PR checks.
144-
- [ ] Approve and merge the PR. When merging the PR, make sure "Create a merge commit" is
145-
selected rather than "Squash and merge" or "Rebase and merge".
146-
EOF
147-
)
148-
149-
# Update the version number ready for the next release
150-
npm version patch --no-git-tag-version
151-
152-
# Update the changelog, adding a new version heading directly above the most recent existing one
153-
awk '!f && /##/{print "'"## [UNRELEASED]\n\nNo user facing changes.\n"'"; f=1}1' CHANGELOG.md > temp && mv temp CHANGELOG.md
154-
git add .
155-
git commit -m "Update changelog and version after ${VERSION}"
156-
157-
git push origin "${NEW_BRANCH}"
158-
159-
# PR checks won't be triggered on PRs created by Actions. Therefore mark the PR as draft
160-
# so that a maintainer can take the PR out of draft, thereby triggering the PR checks.
161-
gh pr create \
162-
--head "${NEW_BRANCH}" \
163-
--base "${BASE_BRANCH}" \
164-
--title "${pr_title}" \
165-
--label "Rebuild" \
166-
--body "${pr_body}" \
167-
--assignee "${GITHUB_ACTOR}" \
168-
--draft
129+
uses: ./.github/actions/prepare-mergeback-branch
130+
with:
131+
base: "${{ env.BASE_BRANCH }}"
132+
head: "${{ env.HEAD_BRANCH }}"
133+
branch: "${{ steps.getVersion.outputs.newBranch }}"
134+
version: "${{ steps.getVersion.outputs.version }}"
135+
token: "${{ secrets.GITHUB_TOKEN }}"
169136

170137
- name: Generate token
171138
uses: actions/[email protected]

.github/workflows/prepare-release.yml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: Prepare release
2+
on:
3+
workflow_call:
4+
outputs:
5+
version:
6+
description: "The version that is being released."
7+
value: ${{ jobs.prepare.outputs.version }}
8+
major_version:
9+
description: "The major version of the release."
10+
value: ${{ jobs.prepare.outputs.major_version }}
11+
latest_tag:
12+
description: "The most recent, existing release tag."
13+
value: ${{ jobs.prepare.outputs.latest_tag }}
14+
backport_source_branch:
15+
description: "The release branch for the given tag."
16+
value: ${{ jobs.prepare.outputs.backport_source_branch }}
17+
backport_target_branches:
18+
description: "JSON encoded list of branches to target with backports."
19+
value: ${{ jobs.prepare.outputs.backport_target_branches }}
20+
21+
push:
22+
paths:
23+
- .github/workflows/prepare-release.yml
24+
25+
jobs:
26+
prepare:
27+
name: "Prepare release"
28+
runs-on: ubuntu-latest
29+
if: github.repository == 'github/codeql-action'
30+
31+
permissions:
32+
contents: read
33+
34+
outputs:
35+
version: ${{ steps.versions.outputs.version }}
36+
major_version: ${{ steps.versions.outputs.major_version }}
37+
latest_tag: ${{ steps.versions.outputs.latest_tag }}
38+
backport_source_branch: ${{ steps.branches.outputs.backport_source_branch }}
39+
backport_target_branches: ${{ steps.branches.outputs.backport_target_branches }}
40+
41+
steps:
42+
- name: Checkout repository
43+
uses: actions/checkout@v5
44+
with:
45+
fetch-depth: 0 # Need full history for calculation of diffs
46+
47+
- name: Configure runner for release
48+
uses: ./.github/actions/release-initialise
49+
50+
- name: Get version tags
51+
id: versions
52+
run: |
53+
VERSION="v$(jq '.version' -r 'package.json')"
54+
echo "version=${VERSION}" >> $GITHUB_OUTPUT
55+
MAJOR_VERSION=$(cut -d '.' -f1 <<< "${VERSION}")
56+
echo "major_version=${MAJOR_VERSION}" >> $GITHUB_OUTPUT
57+
LATEST_TAG=$(git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+' | head -1)
58+
echo "latest_tag=${LATEST_TAG}" >> $GITHUB_OUTPUT
59+
60+
- name: Determine older release branches
61+
id: branches
62+
uses: ./.github/actions/release-branches
63+
with:
64+
major_version: ${{ steps.versions.outputs.major_version }}
65+
latest_tag: ${{ steps.versions.outputs.latest_tag }}
66+
67+
- name: Print release information
68+
run: |
69+
echo 'version: ${{ steps.versions.outputs.version }}'
70+
echo 'major_version: ${{ steps.versions.outputs.major_version }}'
71+
echo 'latest_tag: ${{ steps.versions.outputs.latest_tag }}'
72+
echo 'backport_source_branch: ${{ steps.branches.outputs.backport_source_branch }}'
73+
echo 'backport_target_branches: ${{ steps.branches.outputs.backport_target_branches }}'
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
name: Rollback release
2+
on:
3+
# You can trigger this workflow via workflow dispatch to start a rollback.
4+
# This will create a draft release that mirrors the release for `rollback-tag`.
5+
workflow_dispatch:
6+
inputs:
7+
rollback-tag:
8+
type: string
9+
description: "The tag of an old release to roll-back to."
10+
required: true
11+
# Only for dry-runs of changes to the workflow.
12+
push:
13+
paths:
14+
- .github/workflows/rollback-release.yml
15+
- .github/actions/prepare-mergeback-branch/**
16+
17+
jobs:
18+
prepare:
19+
name: "Prepare release"
20+
if: github.repository == 'github/codeql-action'
21+
22+
permissions:
23+
contents: read
24+
25+
uses: ./.github/workflows/prepare-release.yml
26+
27+
rollback:
28+
name: "Create rollback release"
29+
if: github.repository == 'github/codeql-action'
30+
runs-on: ubuntu-latest
31+
timeout-minutes: 45
32+
33+
# Don't set the deployment environment for test runs
34+
# The Actions token does not have permissions to push changes to workflow files.
35+
# Since workflow files may change as part of a backport PR, we use the "Automation" environment for real runs to authenticate as a GitHub App and push these changes.
36+
environment: ${{ github.event_name == 'workflow_dispatch' && 'Automation' || '' }}
37+
38+
needs:
39+
- prepare
40+
41+
permissions:
42+
contents: write # needed to push to the repo (tags and releases)
43+
pull-requests: write # needed to create the mergeback PR
44+
45+
steps:
46+
- name: Checkout repository
47+
uses: actions/checkout@v5
48+
with:
49+
fetch-depth: 0 # Need full history for calculation of diffs
50+
51+
- name: Configure runner for release
52+
uses: ./.github/actions/release-initialise
53+
54+
- name: Create tag for testing
55+
if: github.event_name != 'workflow_dispatch'
56+
shell: bash
57+
run: git tag v0.0.0
58+
59+
# We start by preparing the mergeback branch, mainly so that we have the updated changelog
60+
# readily available for the partial changelog that's needed for the release.
61+
- name: Prepare mergeback branch
62+
id: mergeback-branch
63+
env:
64+
BASE_BRANCH: ${{ (github.event_name == 'workflow_dispatch' && 'main') || github.ref_name }}
65+
VERSION: ${{ needs.prepare.outputs.version }}
66+
run: |
67+
set -x
68+
69+
# Checkout the base branch, since we may be testing on a different branch
70+
git checkout "$BASE_BRANCH"
71+
72+
# Generate a new branch name for the mergeback PR
73+
short_sha="${GITHUB_SHA:0:8}"
74+
NEW_BRANCH="mergeback/${VERSION}-to-${BASE_BRANCH}-${short_sha}"
75+
echo "new-branch=${NEW_BRANCH}" >> $GITHUB_OUTPUT
76+
77+
# Create the mergeback branch
78+
git checkout -b "${NEW_BRANCH}"
79+
80+
- name: Prepare rollback changelog
81+
env:
82+
NEW_CHANGELOG: "${{ runner.temp }}/new_changelog.md"
83+
# We usually expect to checkout `inputs.rollback-tag` (required for `workflow_dispatch`),
84+
# but use `v0.0.0` for testing.
85+
ROLLBACK_TAG: ${{ inputs.rollback-tag || 'v0.0.0' }}
86+
LATEST_TAG: ${{ needs.prepare.outputs.latest_tag }}
87+
VERSION: "${{ needs.prepare.outputs.version }}"
88+
run: |
89+
python .github/workflows/script/rollback_changelog.py \
90+
--target-version "${ROLLBACK_TAG:1}" \
91+
--rollback-version "${LATEST_TAG:1}" \
92+
--new-version "$VERSION" > $NEW_CHANGELOG
93+
94+
echo "::group::New CHANGELOG"
95+
cat $NEW_CHANGELOG
96+
echo "::endgroup::"
97+
98+
- name: Create tags
99+
shell: bash
100+
env:
101+
# We usually expect to checkout `inputs.rollback-tag` (required for `workflow_dispatch`),
102+
# but use `v0.0.0` for testing.
103+
ROLLBACK_TAG: ${{ inputs.rollback-tag || 'v0.0.0' }}
104+
RELEASE_TAG: ${{ needs.prepare.outputs.version }}
105+
MAJOR_VERSION_TAG: ${{ needs.prepare.outputs.major_version }}
106+
run: |
107+
git checkout "refs/tags/${ROLLBACK_TAG}"
108+
git tag --annotate "${RELEASE_TAG}" --message "${RELEASE_TAG}"
109+
git tag --annotate "${MAJOR_VERSION_TAG}" --message "${MAJOR_VERSION_TAG}" --force
110+
111+
- name: Push tags
112+
# skip when testing
113+
if: github.event_name == 'workflow_dispatch'
114+
shell: bash
115+
env:
116+
RELEASE_TAG: ${{ needs.prepare.outputs.version }}
117+
MAJOR_VERSION_TAG: ${{ needs.prepare.outputs.major_version }}
118+
run: |
119+
git push origin --atomic --force refs/tags/"${RELEASE_TAG}" refs/tags/"${MAJOR_VERSION_TAG}"
120+
121+
- name: Prepare partial Changelog
122+
env:
123+
NEW_CHANGELOG: "${{ runner.temp }}/new_changelog.md"
124+
PARTIAL_CHANGELOG: "${{ runner.temp }}/partial_changelog.md"
125+
VERSION: "${{ needs.prepare.outputs.version }}"
126+
run: |
127+
python .github/workflows/script/prepare_changelog.py $NEW_CHANGELOG "$VERSION" > $PARTIAL_CHANGELOG
128+
129+
echo "::group::Partial CHANGELOG"
130+
cat $PARTIAL_CHANGELOG
131+
echo "::endgroup::"
132+
133+
- name: Generate token
134+
if: github.event_name == 'workflow_dispatch'
135+
uses: actions/[email protected]
136+
id: app-token
137+
with:
138+
app-id: ${{ vars.AUTOMATION_APP_ID }}
139+
private-key: ${{ secrets.AUTOMATION_PRIVATE_KEY }}
140+
141+
- name: Create the rollback release
142+
if: github.event_name == 'workflow_dispatch'
143+
env:
144+
PARTIAL_CHANGELOG: "${{ runner.temp }}/partial_changelog.md"
145+
VERSION: "${{ needs.prepare.outputs.version }}"
146+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
147+
RELEASE_URL: "${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ needs.prepare.outputs.version }}"
148+
run: |
149+
set -exu
150+
151+
# Do not mark this release as latest. The most recent bundle release must be marked as latest.
152+
# Set as a draft to give us an opportunity to review the rollback release.
153+
gh release create \
154+
"$VERSION" \
155+
--latest=false \
156+
--draft \
157+
--title "$VERSION" \
158+
--notes-file "$PARTIAL_CHANGELOG"
159+
160+
echo "Created draft rollback release at $RELEASE_URL" >> $GITHUB_STEP_SUMMARY
161+
162+
- name: Update changelog
163+
shell: bash
164+
env:
165+
NEW_CHANGELOG: "${{ runner.temp }}/new_changelog.md"
166+
NEW_BRANCH: "${{ steps.mergeback-branch.outputs.new-branch }}"
167+
run: |
168+
git checkout "${NEW_BRANCH}"
169+
mv ${NEW_CHANGELOG} CHANGELOG.md
170+
171+
- name: Create mergeback branch and PR
172+
uses: ./.github/actions/prepare-mergeback-branch
173+
with:
174+
base: "main"
175+
head: ""
176+
branch: "${{ steps.mergeback-branch.outputs.new-branch }}"
177+
version: "${{ needs.prepare.outputs.version }}"
178+
token: "${{ secrets.GITHUB_TOKEN }}"
179+
# Setting this to `true` for non-workflow_dispatch events will
180+
# still push the `branch`, but won't create a corresponding PR
181+
dry-run: "${{ github.event_name != 'workflow_dispatch' }}"
182+

0 commit comments

Comments
 (0)