Skip to content

Add workflow for rolling back release #3033

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .github/actions/prepare-mergeback-branch/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: "Prepare mergeback branch"
description: Prepares a mergeback branch and opens a PR for it
inputs:
base:
description: "The name of the base branch"
required: true
head:
description: "The name of the head branch"
required: true
branch:
description: "The name of the branch to create."
required: true
version:
description: "The new version"
required: true
token:
description: "The token to use"
required: true
dry-run:
description: "Set to true to skip creating the PR. The branch will still be pushed."
default: "false"
runs:
using: composite
steps:
- name: Create mergeback branch
shell: bash
env:
VERSION: "${{ inputs.version }}"
NEW_BRANCH: "${{ inputs.branch }}"
run: |
set -exu

# Update the version number ready for the next release
npm version patch --no-git-tag-version

# Update the changelog, adding a new version heading directly above the most recent existing one
awk '!f && /##/{print "'"## [UNRELEASED]\n\nNo user facing changes.\n"'"; f=1}1' CHANGELOG.md > temp && mv temp CHANGELOG.md
git add .
git commit -m "Update changelog and version after ${VERSION}"

git push origin "${NEW_BRANCH}"

- name: Create PR
shell: bash
if: inputs.dry-run != 'false'
env:
VERSION: "${{ inputs.version }}"
BASE_BRANCH: "${{ inputs.base }}"
HEAD_BRANCH: "${{ inputs.head }}"
NEW_BRANCH: "${{ inputs.branch }}"
GITHUB_TOKEN: "${{ inputs.token }}"
run: |
set -exu
pr_title="Mergeback ${VERSION} ${HEAD_BRANCH} into ${BASE_BRANCH}"
pr_body=$(cat << EOF
This PR bumps the version number and updates the changelog after the ${VERSION} release.

Please do the following:

- [ ] Remove and re-add the "Update dependencies" label to the PR to trigger just this workflow.
- [ ] Wait for the "Update dependencies" workflow to push a commit updating the dependencies.
- [ ] Mark the PR as ready for review to trigger the full set of PR checks.
- [ ] Approve and merge the PR. When merging the PR, make sure "Create a merge commit" is
selected rather than "Squash and merge" or "Rebase and merge".
EOF
)

# PR checks won't be triggered on PRs created by Actions. Therefore mark the PR as draft
# so that a maintainer can take the PR out of draft, thereby triggering the PR checks.
gh pr create \
--head "${NEW_BRANCH}" \
--base "${BASE_BRANCH}" \
--title "${pr_title}" \
--label "Update dependencies" \
--body "${pr_body}" \
--assignee "${GITHUB_ACTOR}" \
--draft
49 changes: 8 additions & 41 deletions .github/workflows/post-release-mergeback.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,48 +124,15 @@ jobs:
cat $PARTIAL_CHANGELOG
echo "::endgroup::"

- name: Create mergeback branch
- name: Create mergeback branch and PR
if: ${{ steps.check.outputs.exists != 'true' && endsWith(github.ref_name, steps.getVersion.outputs.latest_release_branch) }}
env:
VERSION: "${{ steps.getVersion.outputs.version }}"
NEW_BRANCH: "${{ steps.getVersion.outputs.newBranch }}"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
run: |
set -exu
pr_title="Mergeback ${VERSION} ${HEAD_BRANCH} into ${BASE_BRANCH}"
pr_body=$(cat << EOF
This PR bumps the version number and updates the changelog after the ${VERSION} release.

Please do the following:

- [ ] Remove and re-add the "Update dependencies" label to the PR to trigger just this workflow.
- [ ] Wait for the "Update dependencies" workflow to push a commit updating the dependencies.
- [ ] Mark the PR as ready for review to trigger the full set of PR checks.
- [ ] Approve and merge the PR. When merging the PR, make sure "Create a merge commit" is
selected rather than "Squash and merge" or "Rebase and merge".
EOF
)

# Update the version number ready for the next release
npm version patch --no-git-tag-version

# Update the changelog, adding a new version heading directly above the most recent existing one
awk '!f && /##/{print "'"## [UNRELEASED]\n\nNo user facing changes.\n"'"; f=1}1' CHANGELOG.md > temp && mv temp CHANGELOG.md
git add .
git commit -m "Update changelog and version after ${VERSION}"

git push origin "${NEW_BRANCH}"

# PR checks won't be triggered on PRs created by Actions. Therefore mark the PR as draft
# so that a maintainer can take the PR out of draft, thereby triggering the PR checks.
gh pr create \
--head "${NEW_BRANCH}" \
--base "${BASE_BRANCH}" \
--title "${pr_title}" \
--label "Update dependencies" \
--body "${pr_body}" \
--assignee "${GITHUB_ACTOR}" \
--draft
uses: ./.github/actions/prepare-mergeback-branch
with:
base: "${{ env.BASE_BRANCH }}"
head: "${{ env.HEAD_BRANCH }}"
branch: "${{ steps.getVersion.outputs.newBranch }}"
version: "${{ steps.getVersion.outputs.version }}"
token: "${{ secrets.GITHUB_TOKEN }}"

- name: Generate token
uses: actions/[email protected]
Expand Down
73 changes: 73 additions & 0 deletions .github/workflows/prepare-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Prepare release
on:
workflow_call:
outputs:
version:
description: "The version that is being released."
value: ${{ jobs.prepare.outputs.version }}
major_version:
description: "The major version of the release."
value: ${{ jobs.prepare.outputs.major_version }}
latest_tag:
description: "The most recent, existing release tag."
value: ${{ jobs.prepare.outputs.latest_tag }}
backport_source_branch:
description: "The release branch for the given tag."
value: ${{ jobs.prepare.outputs.backport_source_branch }}
backport_target_branches:
description: "JSON encoded list of branches to target with backports."
value: ${{ jobs.prepare.outputs.backport_target_branches }}

push:
paths:
- .github/workflows/prepare-release.yml

jobs:
prepare:
name: "Prepare release"
runs-on: ubuntu-latest
if: github.repository == 'github/codeql-action'

permissions:
contents: read

outputs:
version: ${{ steps.versions.outputs.version }}
major_version: ${{ steps.versions.outputs.major_version }}
latest_tag: ${{ steps.versions.outputs.latest_tag }}
backport_source_branch: ${{ steps.branches.outputs.backport_source_branch }}
backport_target_branches: ${{ steps.branches.outputs.backport_target_branches }}

steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0 # Need full history for calculation of diffs

- name: Configure runner for release
uses: ./.github/actions/release-initialise

- name: Get version tags
id: versions
run: |
VERSION="v$(jq '.version' -r 'package.json')"
echo "version=${VERSION}" >> $GITHUB_OUTPUT
MAJOR_VERSION=$(cut -d '.' -f1 <<< "${VERSION}")
echo "major_version=${MAJOR_VERSION}" >> $GITHUB_OUTPUT
LATEST_TAG=$(git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+' | head -1)
echo "latest_tag=${LATEST_TAG}" >> $GITHUB_OUTPUT

- name: Determine older release branches
id: branches
uses: ./.github/actions/release-branches
with:
major_version: ${{ steps.versions.outputs.major_version }}
latest_tag: ${{ steps.versions.outputs.latest_tag }}

- name: Print release information
run: |
echo 'version: ${{ steps.versions.outputs.version }}'
echo 'major_version: ${{ steps.versions.outputs.major_version }}'
echo 'latest_tag: ${{ steps.versions.outputs.latest_tag }}'
echo 'backport_source_branch: ${{ steps.branches.outputs.backport_source_branch }}'
echo 'backport_target_branches: ${{ steps.branches.outputs.backport_target_branches }}'
160 changes: 160 additions & 0 deletions .github/workflows/rollback-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
name: Rollback release
on:
# You can trigger this workflow via workflow dispatch to start a rollback.
# This will create a draft release that mirrors the release for `rollback-tag`.
workflow_dispatch:
inputs:
rollback-tag:
type: string
description: "The tag of an old release to roll-back to."
required: true
# Only for dry-runs of changes to the workflow.
push:
paths:
- .github/workflows/rollback-release.yml

jobs:
prepare:
name: "Prepare release"
if: github.repository == 'github/codeql-action'

permissions:
contents: read

uses: ./.github/workflows/prepare-release.yml

rollback:
name: "Create rollback release"
if: github.repository == 'github/codeql-action'
runs-on: ubuntu-latest
timeout-minutes: 45

# Don't set the deployment environment for test runs
environment: ${{ github.event_name == 'workflow_dispatch' && 'Automation' || '' }}

needs:
- prepare

permissions:
contents: write # needed to push to the repo (tags and releases)

steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0 # Need full history for calculation of diffs

- name: Configure runner for release
uses: ./.github/actions/release-initialise

- name: Create tag for testing
if: github.event_name != 'workflow_dispatch'
shell: bash
run: git tag v0.0.0

# We start by preparing the mergeback branch, mainly so that we have the updated changelog
# readily available for the partial changelog that's needed for the release.
- name: Prepare mergeback branch
id: mergeback-branch
env:
BASE_BRANCH: ${{ (github.event_name == 'workflow_dispatch' && 'main') || github.ref_name }}
VERSION: ${{ needs.prepare.outputs.version }}
run: |
set -x

# Checkout the base branch, since we may be testing on a different branch
git checkout "$BASE_BRANCH"

# Generate a new branch name for the mergeback PR
short_sha="${GITHUB_SHA:0:8}"
NEW_BRANCH="mergeback/${VERSION}-to-${BASE_BRANCH}-${short_sha}"
echo "new-branch=${NEW_BRANCH}" >> $GITHUB_OUTPUT

# Create the mergeback branch
git checkout -b "${NEW_BRANCH}"

- name: Prepare rollback changelog
env:
NEW_CHANGELOG: "${{ runner.temp }}/new_changelog.md"
# We usually expect to checkout `inputs.rollback-tag` (required for `workflow_dispatch`),
# but use `v0.0.0` for testing.
ROLLBACK_TAG: ${{ inputs.rollback-tag || 'v0.0.0' }}
LATEST_TAG: ${{ needs.prepare.outputs.latest_tag }}
VERSION: "${{ needs.prepare.outputs.version }}"
run: |
python .github/workflows/script/rollback_changelog.py "${ROLLBACK_TAG:1}" "${LATEST_TAG:1}" "$VERSION" > $NEW_CHANGELOG

echo "::group::New CHANGELOG"
cat $NEW_CHANGELOG
echo "::endgroup::"

- name: Create tags
shell: bash
env:
# We usually expect to checkout `inputs.rollback-tag` (required for `workflow_dispatch`),
# but use `v0.0.0` for testing.
ROLLBACK_TAG: ${{ inputs.rollback-tag || 'v0.0.0' }}
RELEASE_TAG: ${{ needs.prepare.outputs.version }}
MAJOR_VERSION_TAG: ${{ needs.prepare.outputs.major_version }}
run: |
git checkout "refs/tags/${ROLLBACK_TAG}"
git tag --annotate "${RELEASE_TAG}" --message "${RELEASE_TAG}"
git tag --annotate "${MAJOR_VERSION_TAG}" --message "${MAJOR_VERSION_TAG}" --force

- name: Push tags
if: github.event_name == 'workflow_dispatch'
shell: bash
env:
RELEASE_TAG: ${{ needs.prepare.outputs.version }}
MAJOR_VERSION_TAG: ${{ needs.prepare.outputs.major_version }}
run: |
git push origin --atomic --force refs/tags/"${RELEASE_TAG}" refs/tags/"${MAJOR_VERSION_TAG}"

- name: Prepare partial Changelog
env:
NEW_CHANGELOG: "${{ runner.temp }}/new_changelog.md"
PARTIAL_CHANGELOG: "${{ runner.temp }}/partial_changelog.md"
VERSION: "${{ needs.prepare.outputs.version }}"
run: |
python .github/workflows/script/prepare_changelog.py $NEW_CHANGELOG "$VERSION" > $PARTIAL_CHANGELOG

echo "::group::Partial CHANGELOG"
cat $PARTIAL_CHANGELOG
echo "::endgroup::"

- name: Generate token
if: github.event_name == 'workflow_dispatch'
uses: actions/[email protected]
id: app-token
with:
app-id: ${{ vars.AUTOMATION_APP_ID }}
private-key: ${{ secrets.AUTOMATION_PRIVATE_KEY }}

- name: Create the rollback release
if: github.event_name == 'workflow_dispatch'
env:
PARTIAL_CHANGELOG: "${{ runner.temp }}/partial_changelog.md"
VERSION: "${{ needs.prepare.outputs.version }}"
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
# Do not mark this release as latest. The most recent CLI release must be marked as latest.
# Set as a draft to give us an opportunity to review the rollback release.
gh release create \
"$VERSION" \
--latest=false \
--draft \
--title "$VERSION" \
--notes-file "$PARTIAL_CHANGELOG"

- name: Create mergeback branch and PR
uses: ./.github/actions/prepare-mergeback-branch
with:
base: "main"
head: ""
branch: "${{ steps.mergeback-branch.outputs.new-branch }}"
version: "${{ needs.prepare.outputs.version }}"
token: "${{ secrets.GITHUB_TOKEN }}"
# Setting this to `true` for non-workflow_dispatch events will
# still push the `branch`, but won't create a corresponding PR
dry-run: "${{ github.event_name != 'workflow_dispatch' }}"

2 changes: 1 addition & 1 deletion .github/workflows/script/prepare_changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def extract_changelog_snippet(changelog_file, version_tag):
output = EMPTY_CHANGELOG

else:
with open('CHANGELOG.md', 'r') as f:
with open(changelog_file, 'r') as f:
lines = f.readlines()

# Include everything up to, but excluding the second heading
Expand Down
Loading
Loading