Skip to content

Commit 8e4af79

Browse files
ci: Do release tagging in CI and add --draft support (#5652)
* ci: Do release tagging in CI and add --draft support * Add -h, --help to release.sh * Add -h, --help to increment_version_tag.sh * Limit release concurrency * Add automatic release watching * ci: Add git config, tag as "GitHub Actions Bot" Co-authored-by: Dean Sheather <dean@deansheather.com>
1 parent e72a2ad commit 8e4af79

File tree

6 files changed

+358
-91
lines changed

6 files changed

+358
-91
lines changed

.github/workflows/release.yaml

+60-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
# GitHub release workflow.
2-
name: release
2+
name: Release
33
on:
4-
push:
5-
tags:
6-
- "v*"
74
workflow_dispatch:
85
inputs:
6+
increment:
7+
description: Preferred version increment (release script may promote e.g. patch to minor depending on changes).
8+
type: choice
9+
required: true
10+
default: patch
11+
options:
12+
- patch
13+
- minor
14+
- major
15+
draft:
16+
description: Create a draft release (for manually editing release notes before publishing).
17+
type: boolean
18+
required: true
919
snapshot:
1020
description: Force a dev version to be generated, implies dry_run.
1121
type: boolean
@@ -25,9 +35,13 @@ permissions:
2535

2636
env:
2737
CODER_RELEASE: ${{ github.event.inputs.snapshot && 'false' || 'true' }}
38+
DRY_RUN: ${{ (github.event.inputs.dry_run || github.event.inputs.snapshot) && 'true' || 'false' }}
39+
40+
concurrency: ${{ github.workflow }}-${{ github.ref }}
2841

2942
jobs:
3043
release:
44+
name: Create and publish
3145
runs-on: ${{ github.repository_owner == 'coder' && 'ubuntu-latest-16-cores' || 'ubuntu-latest' }}
3246
env:
3347
# Necessary for Docker manifest
@@ -45,6 +59,12 @@ jobs:
4559
- name: Fetch git tags
4660
run: git fetch --tags --force
4761

62+
# Configure git user name/email for creating annotated version tag.
63+
- name: Setup git config
64+
run: |
65+
git config user.name "GitHub Actions Bot"
66+
git config user.email ""
67+
4868
- name: Docker Login
4969
uses: docker/login-action@v2
5070
with:
@@ -100,6 +120,38 @@ jobs:
100120
AC_CERTIFICATE_PASSWORD: ${{ secrets.AC_CERTIFICATE_PASSWORD }}
101121
AC_APIKEY_P8_BASE64: ${{ secrets.AC_APIKEY_P8_BASE64 }}
102122

123+
- name: Create release tag and release notes
124+
run: |
125+
set -euo pipefail
126+
ref=HEAD
127+
old_version="$(git describe --abbrev=0 "$ref^1")"
128+
129+
if [[ $DRY_RUN == true ]]; then
130+
# Allow dry-run of branches to pass.
131+
export CODER_IGNORE_MISSING_COMMIT_METADATA=1
132+
fi
133+
134+
# Cache commit metadata.
135+
. ./scripts/release/check_commit_metadata.sh "$old_version" "$ref"
136+
137+
# Create new release tag.
138+
version="$(
139+
./scripts/release/tag_version.sh \
140+
${{ (github.event.inputs.dry_run || github.event.inputs.snapshot) && '--dry-run' }} \
141+
--ref "$ref" \
142+
--${{ github.event.inputs.increment }}
143+
)"
144+
145+
# Generate notes.
146+
release_notes_file="$(mktemp -t release_notes.XXXXXX)"
147+
./scripts/release/generate_release_notes.sh --old-version "$old_version" --new-version "$version" --ref "$ref" >> "$release_notes_file"
148+
echo CODER_RELEASE_NOTES_FILE="$release_notes_file" >> $GITHUB_ENV
149+
150+
- name: Echo release notes
151+
run: |
152+
set -euo pipefail
153+
cat "$CODER_RELEASE_NOTES_FILE"
154+
103155
- name: Build binaries
104156
run: |
105157
set -euo pipefail
@@ -157,8 +209,11 @@ jobs:
157209

158210
- name: Publish release
159211
run: |
212+
set -euo pipefail
160213
./scripts/release/publish.sh \
214+
${{ github.event.inputs.draft && '--draft' }} \
161215
${{ (github.event.inputs.dry_run || github.event.inputs.snapshot) && '--dry-run' }} \
216+
--release-notes-file "$CODER_RELEASE_NOTES_FILE" \
162217
./build/*_installer.exe \
163218
./build/*.zip \
164219
./build/*.tar.gz \
@@ -195,6 +250,7 @@ jobs:
195250
with:
196251
name: release-artifacts
197252
path: |
253+
./build/*_installer.exe
198254
./build/*.zip
199255
./build/*.tar.gz
200256
./build/*.tgz

scripts/release.sh

+129-57
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,80 @@
11
#!/usr/bin/env bash
22

3-
# This script should be called to create a new release.
4-
#
5-
# When run, this script will display the new version number and optionally a
6-
# preview of the release notes. The new version will be selected automatically
7-
# based on if the release contains breaking changes or not. If the release
8-
# contains breaking changes, a new minor version will be created. Otherwise, a
9-
# new patch version will be created.
10-
#
11-
# Set --ref if you need to specify a specific commit that the new version will
12-
# be tagged at, otherwise the latest commit will be used.
13-
#
14-
# Set --minor to force a minor version bump, even when there are no breaking
15-
# changes.
16-
#
17-
# To mark a release as containing breaking changes, the commit title should
18-
# either contain a known prefix with an exclamation mark ("feat!:",
19-
# "feat(api)!:") or the PR that was merged can be tagged with the
20-
# "release/breaking" label.
21-
#
22-
# Usage: ./release.sh [--ref <ref>] [--minor]
23-
243
set -euo pipefail
254
# shellcheck source=scripts/lib.sh
265
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
276
cdroot
287

8+
usage() {
9+
cat <<EOH
10+
Usage: ./release.sh [--branch <name>] [--draft] [--dry-run] [--ref <ref>] [--major | --minor | --patch]
11+
12+
This script should be called to create a new release.
13+
14+
When run, this script will display the new version number and optionally a
15+
preview of the release notes. The new version will be selected automatically
16+
based on if the release contains breaking changes or not. If the release
17+
contains breaking changes, a new minor version will be created. Otherwise, a
18+
new patch version will be created.
19+
20+
Set --ref if you need to specify a specific commit that the new version will
21+
be tagged at, otherwise the latest commit will be used.
22+
23+
Set --minor to force a minor version bump, even when there are no breaking
24+
changes. Likewise for --major. By default a patch version will be created.
25+
26+
Set --dry-run to run the release workflow in CI as a dry-run (no release will
27+
be created).
28+
29+
To mark a release as containing breaking changes, the commit title should
30+
either contain a known prefix with an exclamation mark ("feat!:",
31+
"feat(api)!:") or the PR that was merged can be tagged with the
32+
"release/breaking" label.
33+
34+
To test changes to this script, you can set --branch <my-branch>, which will
35+
run the release workflow in CI as a dry-run and use the latest commit on the
36+
specified branch as the release commit. This will also set --dry-run.
37+
EOH
38+
}
39+
40+
branch=main
41+
draft=0
42+
dry_run=0
2943
ref=
30-
minor=0
44+
increment=
3145

32-
args="$(getopt -o n -l ref:,minor -- "$@")"
46+
args="$(getopt -o h -l branch:,draft,dry-run,help,ref:,major,minor,patch -- "$@")"
3347
eval set -- "$args"
3448
while true; do
3549
case "$1" in
50+
--branch)
51+
branch="$2"
52+
log "Using branch $branch, implies DRYRUN and CODER_IGNORE_MISSING_COMMIT_METADATA."
53+
dry_run=1
54+
export CODER_IGNORE_MISSING_COMMIT_METADATA=1
55+
shift 2
56+
;;
57+
--draft)
58+
draft=1
59+
shift
60+
;;
61+
--dry-run)
62+
dry_run=1
63+
shift
64+
;;
65+
-h | --help)
66+
usage
67+
exit 0
68+
;;
3669
--ref)
3770
ref="$2"
3871
shift 2
3972
;;
40-
--minor)
41-
minor=1
73+
--major | --minor | --patch)
74+
if [[ -n $increment ]]; then
75+
error "Cannot specify multiple version increments."
76+
fi
77+
increment=${1#--}
4278
shift
4379
;;
4480
--)
@@ -54,15 +90,20 @@ done
5490
# Check dependencies.
5591
dependencies gh sort
5692

93+
if [[ -z $increment ]]; then
94+
# Default to patch versions.
95+
increment="patch"
96+
fi
97+
5798
# Make sure the repository is up-to-date before generating release notes.
58-
log "Fetching main and tags from origin..."
59-
git fetch --quiet --tags origin main
99+
log "Fetching $branch and tags from origin..."
100+
git fetch --quiet --tags origin "$branch"
60101

61102
# Resolve to the latest ref on origin/main unless otherwise specified.
62-
ref=$(git rev-parse --short "${ref:-origin/main}")
103+
ref=$(git rev-parse --short "${ref:-origin/$branch}")
63104

64105
# Make sure that we're running the latest release script.
65-
if [[ -n $(git diff --name-status origin/main -- ./scripts/release.sh) ]]; then
106+
if [[ -n $(git diff --name-status origin/"$branch" -- ./scripts/release.sh) ]]; then
66107
error "Release script is out-of-date. Please check out the latest version and try again."
67108
fi
68109

@@ -71,42 +112,73 @@ fi
71112
mapfile -t versions < <(gh api -H "Accept: application/vnd.github+json" /repos/coder/coder/git/refs/tags -q '.[].ref | split("/") | .[2]' | grep '^v' | sort -r -V)
72113
old_version=${versions[0]}
73114

74-
log "Checking commit metadata for changes since $old_version..."
75115
# shellcheck source=scripts/release/check_commit_metadata.sh
76116
source "$SCRIPT_DIR/release/check_commit_metadata.sh" "$old_version" "$ref"
77117

78-
mapfile -d . -t version_parts <<<"$old_version"
79-
if [[ $minor == 1 ]] || [[ $COMMIT_METADATA_BREAKING == 1 ]]; then
80-
if [[ $COMMIT_METADATA_BREAKING == 1 ]]; then
81-
log "Breaking change detected, incrementing minor version..."
82-
else
83-
log "Forcing minor version bump..."
84-
fi
85-
version_parts[1]=$((version_parts[1] + 1))
86-
version_parts[2]=0
87-
else
88-
log "No breaking changes detected, incrementing patch version..."
89-
version_parts[2]=$((version_parts[2] + 1))
90-
fi
91-
new_version="${version_parts[0]}.${version_parts[1]}.${version_parts[2]}"
92-
93-
log "Old version: ${old_version}"
94-
log "New version: ${new_version}"
95-
118+
new_version="$(execrelative ./release/tag_version.sh --dry-run --ref "$ref" --"$increment")"
96119
release_notes="$(execrelative ./release/generate_release_notes.sh --old-version "$old_version" --new-version "$new_version" --ref "$ref")"
97120

98-
echo
121+
log
99122
read -p "Preview release notes? (y/n) " -n 1 -r show_reply
100-
echo
123+
log
101124
if [[ $show_reply =~ ^[Yy]$ ]]; then
102125
echo -e "$release_notes\n"
103126
fi
104127

105-
read -p "Create release? (y/n) " -n 1 -r create
106-
echo
107-
if [[ $create =~ ^[Yy]$ ]]; then
108-
log "Tagging commit $ref as $new_version..."
109-
git tag -a "$new_version" -m "$new_version" "$ref"
110-
log "Pushing tag to origin..."
111-
git push -u origin "$new_version"
128+
create_message="Create release"
129+
if ((draft)); then
130+
create_message="Create draft release"
131+
fi
132+
if ((dry_run)); then
133+
create_message+=" (DRYRUN)"
134+
fi
135+
read -p "$create_message? (y/n) " -n 1 -r create
136+
log
137+
if ! [[ $create =~ ^[Yy]$ ]]; then
138+
exit 0
139+
fi
140+
141+
args=()
142+
if ((draft)); then
143+
args+=(-F draft=true)
144+
fi
145+
if ((dry_run)); then
146+
args+=(-F dry_run=true)
112147
fi
148+
149+
log
150+
gh workflow run release.yaml \
151+
--ref "$branch" \
152+
-F increment="$increment" \
153+
-F snapshot=false \
154+
"${args[@]}"
155+
log
156+
157+
read -p "Watch release? (y/n) " -n 1 -r watch
158+
log
159+
if ! [[ $watch =~ ^[Yy]$ ]]; then
160+
exit 0
161+
fi
162+
163+
log 'Waiting for job to become "in_progress"...'
164+
165+
# Wait at most 3 minutes (3*60)/3 = 60 for the job to start.
166+
for _ in $(seq 1 60); do
167+
mapfile -t run < <(
168+
# Output:
169+
# 3886828508
170+
# in_progress
171+
gh run list -w release.yaml \
172+
--limit 1 \
173+
--json status,databaseId \
174+
--jq '.[] | (.databaseId | tostring), .status'
175+
)
176+
if [[ ${run[1]} != "in_progress" ]]; then
177+
sleep 3
178+
continue
179+
fi
180+
gh run watch --exit-status "${run[0]}"
181+
exit 0
182+
done
183+
184+
error "Waiting for job to start timed out."

scripts/release/check_commit_metadata.sh

+14-5
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ dependencies gh
3434
COMMIT_METADATA_BREAKING=0
3535
declare -A COMMIT_METADATA_TITLE COMMIT_METADATA_CATEGORY
3636

37+
# This environment variable can be set to 1 to ignore missing commit metadata,
38+
# useful for dry-runs.
39+
ignore_missing_metadata=${CODER_IGNORE_MISSING_COMMIT_METADATA:-0}
40+
3741
main() {
3842
# Match a commit prefix pattern, e.g. feat: or feat(site):.
3943
prefix_pattern="^([a-z]+)(\([a-z]*\))?:"
@@ -87,9 +91,11 @@ main() {
8791
commit_sha_long=${parts[1]}
8892
commit_prefix=${parts[2]}
8993

90-
# Safety-check, guarantee all commits had their metadata fetched.
91-
if [[ ! -v labels[$commit_sha_long] ]]; then
92-
error "Metadata missing for commit $commit_sha_short"
94+
if [[ $ignore_missing_metadata != 1 ]]; then
95+
# Safety-check, guarantee all commits had their metadata fetched.
96+
if [[ ! -v labels[$commit_sha_long] ]]; then
97+
error "Metadata missing for commit $commit_sha_short"
98+
fi
9399
fi
94100

95101
# Store the commit title for later use.
@@ -99,11 +105,11 @@ main() {
99105

100106
# First, check the title for breaking changes. This avoids doing a
101107
# GH API request if there's a match.
102-
if [[ $commit_prefix =~ $breaking_title ]] || [[ ${labels[$commit_sha_long]} = *"label:$breaking_label"* ]]; then
108+
if [[ $commit_prefix =~ $breaking_title ]] || [[ ${labels[$commit_sha_long]:-} = *"label:$breaking_label"* ]]; then
103109
COMMIT_METADATA_CATEGORY[$commit_sha_short]=$breaking_category
104110
COMMIT_METADATA_BREAKING=1
105111
continue
106-
elif [[ ${labels[$commit_sha_long]} = *"label:$security_label"* ]]; then
112+
elif [[ ${labels[$commit_sha_long]:-} = *"label:$security_label"* ]]; then
107113
COMMIT_METADATA_CATEGORY[$commit_sha_short]=$security_label
108114
continue
109115
fi
@@ -137,6 +143,9 @@ export_commit_metadata() {
137143
if [[ ${_COMMIT_METADATA_CACHE:-} == "${range}:"* ]]; then
138144
eval "${_COMMIT_METADATA_CACHE#*:}"
139145
else
146+
if [[ $ignore_missing_metadata == 1 ]]; then
147+
log "WARNING: Ignoring missing commit metadata, breaking changes may be missed."
148+
fi
140149
main
141150
fi
142151

0 commit comments

Comments
 (0)