From 4fa510031a73884d1637b2c037d3fc56f2b41670 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 10 Jan 2023 12:59:45 +0000 Subject: [PATCH 01/16] ci: Do release tagging in CI and add --draft support --- .github/workflows/release.yaml | 46 ++++++++- scripts/release.sh | 105 ++++++++++++------- scripts/release/check_commit_metadata.sh | 7 ++ scripts/release/generate_release_notes.sh | 6 +- scripts/release/increment_version_tag.sh | 119 ++++++++++++++++++++++ scripts/release/publish.sh | 40 ++++---- 6 files changed, 263 insertions(+), 60 deletions(-) create mode 100755 scripts/release/increment_version_tag.sh diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 3b5c1638da018..2a237ef293c94 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,11 +1,21 @@ # GitHub release workflow. -name: release +name: New Release on: - push: - tags: - - "v*" workflow_dispatch: inputs: + increment: + description: Preferred version increment (release script may promote e.g. patch to minor depending on changes). + type: choice + required: true + default: patch + options: + - patch + - minor + - major + draft: + description: Create a draft release (for manually editing release notes before publishing). + type: boolean + required: true snapshot: description: Force a dev version to be generated, implies dry_run. type: boolean @@ -100,6 +110,31 @@ jobs: AC_CERTIFICATE_PASSWORD: ${{ secrets.AC_CERTIFICATE_PASSWORD }} AC_APIKEY_P8_BASE64: ${{ secrets.AC_APIKEY_P8_BASE64 }} + - name: Create release tag and release notes + run: | + ref=HEAD + old_version="$(git describe --abbrev=0 "$ref^1")" + + # Cache commit metadata. + . ./scripts/release/check_commit_metadata.sh "$old_version" "$ref" + + # Create new release tag. + version="$( + ./scripts/release/increment_version_tag.sh \ + ${{ (github.event.inputs.dry_run || github.event.inputs.snapshot) && '--dry-run' }} \ + --ref "$ref" \ + --${{ github.event.inputs.increment }} + )" + + # Generate notes. + release_notes="$(./scripts/release/generate_release_notes.sh --old-version "$old_version" --new-version "$version" --ref "$ref")" + echo 'CODER_RELEASE_NOTES<> $GITHUB_ENV + echo "$release_notes" >> $GITHUB_ENV + echo 'RN_EOF' >> $GITHUB_ENV + + - name: Echo release notes + run: echo "$CODER_RELEASE_NOTES" + - name: Build binaries run: | set -euo pipefail @@ -158,7 +193,9 @@ jobs: - name: Publish release run: | ./scripts/release/publish.sh \ + ${{ github.event.inputs.draft && '--draft' }} \ ${{ (github.event.inputs.dry_run || github.event.inputs.snapshot) && '--dry-run' }} \ + --release-notes "$CODER_RELEASE_NOTES" \ ./build/*_installer.exe \ ./build/*.zip \ ./build/*.tar.gz \ @@ -195,6 +232,7 @@ jobs: with: name: release-artifacts path: | + ./build/*_installer.exe ./build/*.zip ./build/*.tar.gz ./build/*.tgz diff --git a/scripts/release.sh b/scripts/release.sh index 83c1ea01fde4d..e82c2c814a45f 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -12,33 +12,61 @@ # be tagged at, otherwise the latest commit will be used. # # Set --minor to force a minor version bump, even when there are no breaking -# changes. +# changes. Likewise for --major. By default a patch version will be created. +# +# Set --dry-run to run the release workflow in CI as a dry-run (no release will +# be created). # # To mark a release as containing breaking changes, the commit title should # either contain a known prefix with an exclamation mark ("feat!:", # "feat(api)!:") or the PR that was merged can be tagged with the # "release/breaking" label. # -# Usage: ./release.sh [--ref ] [--minor] +# To test changes to this script, you can set `--branch `, which will +# run the release workflow in CI as a dry-run and use the latest commit on the +# specified branch as the release commit. This will also set --dry-run. +# +# Usage: ./release.sh [--branch ] [--draft] [--dry-run] [--ref ] [--major | --minor | --patch] set -euo pipefail # shellcheck source=scripts/lib.sh source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" cdroot +branch=main +draft=0 +dry_run=0 ref= -minor=0 +increment= -args="$(getopt -o n -l ref:,minor -- "$@")" +args="$(getopt -o n -l branch:,draft,dry-run,ref:,major,minor,patch -- "$@")" eval set -- "$args" while true; do case "$1" in + --branch) + branch="$2" + log "Using branch $branch, implies DRYRUN and CODER_IGNORE_MISSING_COMMIT_METADATA." + dry_run=1 + export CODER_IGNORE_MISSING_COMMIT_METADATA=1 + shift 2 + ;; + --draft) + draft=1 + shift + ;; + --dry-run) + dry_run=1 + shift + ;; --ref) ref="$2" shift 2 ;; - --minor) - minor=1 + --major | --minor | --patch) + if [[ -n $increment ]]; then + error "Cannot specify multiple version increments." + fi + increment=${1#--} shift ;; --) @@ -54,15 +82,20 @@ done # Check dependencies. dependencies gh sort +if [[ -z $increment ]]; then + # Default to patch versions. + increment="patch" +fi + # Make sure the repository is up-to-date before generating release notes. -log "Fetching main and tags from origin..." -git fetch --quiet --tags origin main +log "Fetching $branch and tags from origin..." +git fetch --quiet --tags origin "$branch" # Resolve to the latest ref on origin/main unless otherwise specified. -ref=$(git rev-parse --short "${ref:-origin/main}") +ref=$(git rev-parse --short "${ref:-origin/$branch}") # Make sure that we're running the latest release script. -if [[ -n $(git diff --name-status origin/main -- ./scripts/release.sh) ]]; then +if [[ -n $(git diff --name-status origin/"$branch" -- ./scripts/release.sh) ]]; then error "Release script is out-of-date. Please check out the latest version and try again." fi @@ -71,28 +104,10 @@ fi 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) old_version=${versions[0]} -log "Checking commit metadata for changes since $old_version..." # shellcheck source=scripts/release/check_commit_metadata.sh source "$SCRIPT_DIR/release/check_commit_metadata.sh" "$old_version" "$ref" -mapfile -d . -t version_parts <<<"$old_version" -if [[ $minor == 1 ]] || [[ $COMMIT_METADATA_BREAKING == 1 ]]; then - if [[ $COMMIT_METADATA_BREAKING == 1 ]]; then - log "Breaking change detected, incrementing minor version..." - else - log "Forcing minor version bump..." - fi - version_parts[1]=$((version_parts[1] + 1)) - version_parts[2]=0 -else - log "No breaking changes detected, incrementing patch version..." - version_parts[2]=$((version_parts[2] + 1)) -fi -new_version="${version_parts[0]}.${version_parts[1]}.${version_parts[2]}" - -log "Old version: ${old_version}" -log "New version: ${new_version}" - +new_version="$(execrelative ./release/increment_version_tag.sh --dry-run --ref "$ref" --"$increment")" release_notes="$(execrelative ./release/generate_release_notes.sh --old-version "$old_version" --new-version "$new_version" --ref "$ref")" echo @@ -102,11 +117,31 @@ if [[ $show_reply =~ ^[Yy]$ ]]; then echo -e "$release_notes\n" fi -read -p "Create release? (y/n) " -n 1 -r create +create_message="Create release" +if ((draft)); then + create_message="Create draft release" +fi +if ((dry_run)); then + create_message+=" (DRYRUN)" +fi +read -p "$create_message? (y/n) " -n 1 -r create echo -if [[ $create =~ ^[Yy]$ ]]; then - log "Tagging commit $ref as $new_version..." - git tag -a "$new_version" -m "$new_version" "$ref" - log "Pushing tag to origin..." - git push -u origin "$new_version" +if ! [[ $create =~ ^[Yy]$ ]]; then + exit 0 fi + +args=() +if ((draft)); then + args+=(-F draft=true) +fi +if ((dry_run)); then + args+=(-F dry_run=true) +fi + +gh workflow run release.yaml \ + --ref "$branch" \ + -F increment="$increment" \ + -F snapshot=false \ + "${args[@]}" + +log "Release process started, you can watch the release via: gh run watch --exit-status " diff --git a/scripts/release/check_commit_metadata.sh b/scripts/release/check_commit_metadata.sh index 76e781dd20cbf..4bce8542595da 100755 --- a/scripts/release/check_commit_metadata.sh +++ b/scripts/release/check_commit_metadata.sh @@ -34,6 +34,10 @@ dependencies gh COMMIT_METADATA_BREAKING=0 declare -A COMMIT_METADATA_TITLE COMMIT_METADATA_CATEGORY +# This environment variable can be set to 1 to ignore missing commit metadata, +# useful for dry-runs. +ignore_missing_metadata=${CODER_IGNORE_MISSING_COMMIT_METADATA:-0} + main() { # Match a commit prefix pattern, e.g. feat: or feat(site):. prefix_pattern="^([a-z]+)(\([a-z]*\))?:" @@ -137,6 +141,9 @@ export_commit_metadata() { if [[ ${_COMMIT_METADATA_CACHE:-} == "${range}:"* ]]; then eval "${_COMMIT_METADATA_CACHE#*:}" else + if [[ $ignore_missing_metadata == 1 ]]; then + log "WARNING: Ignoring missing commit metadata, breaking changes may be missed." + fi main fi diff --git a/scripts/release/generate_release_notes.sh b/scripts/release/generate_release_notes.sh index c52b9e9a6cfe4..fcb161a223eed 100755 --- a/scripts/release/generate_release_notes.sh +++ b/scripts/release/generate_release_notes.sh @@ -59,10 +59,10 @@ if [[ -z $ref ]]; then fi # shellcheck source=scripts/release/check_commit_metadata.sh -source "$SCRIPT_DIR/release/check_commit_metadata.sh" "${old_version}" "${ref}" +source "$SCRIPT_DIR/release/check_commit_metadata.sh" "$old_version" "$ref" # Sort commits by title prefix, then by date, only return sha at the end. -mapfile -t commits < <(git log --no-merges --pretty=format:"%ct %h %s" "${old_version}..${ref}" | sort -k3,3 -k1,1n | cut -d' ' -f2) +mapfile -t commits < <(git log --no-merges --pretty=format:"%ct %h %s" "$old_version..$ref" | sort -k3,3 -k1,1n | cut -d' ' -f2) # From: https://github.com/commitizen/conventional-commit-types # NOTE(mafredri): These need to be supported in check_commit_metadata.sh as well. @@ -140,7 +140,7 @@ image_tag="$(execrelative ./image_tag.sh --version "$new_version")" echo -e "## Changelog $changelog -Compare: [\`${old_version}...${new_version}\`](https://github.com/coder/coder/compare/${old_version}...${new_version}) +Compare: [\`$old_version...$new_version\`](https://github.com/coder/coder/compare/$old_version...$new_version) ## Container image diff --git a/scripts/release/increment_version_tag.sh b/scripts/release/increment_version_tag.sh new file mode 100755 index 0000000000000..8bdf7bc456f34 --- /dev/null +++ b/scripts/release/increment_version_tag.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash + +# This script should be called to tag a new release. It will take the suggested +# increment (major, minor, patch) and optionally promote e.g. patch -> minor if +# there are breaking changes between the previous version and the given --ref +# (or HEAD). +# +# Usage: ./increment_version_tag.sh [--dry-run] [--ref ] <--major | --minor | --patch> +# +# This script will create a git tag, so it should only be run in CI (or via +# --dry-run). + +set -euo pipefail +# shellcheck source=scripts/lib.sh +source "$(dirname "$(dirname "${BASH_SOURCE[0]}")")/lib.sh" +cdroot + +dry_run=0 +ref=HEAD +increment= + +args="$(getopt -o n -l dry-run,ref:,major,minor,patch -- "$@")" +eval set -- "$args" +while true; do + case "$1" in + --dry-run) + dry_run=1 + shift + ;; + --ref) + ref="$2" + shift 2 + ;; + --major | --minor | --patch) + if [[ -n $increment ]]; then + error "Cannot specify multiple version increments." + fi + increment=${1#--} + shift + ;; + --) + shift + break + ;; + *) + error "Unrecognized option: $1" + ;; + esac +done + +# Check dependencies. +dependencies git + +if [[ -z $increment ]]; then + error "No version increment provided." +fi + +if [[ $dry_run != 1 ]] && [[ ${CI:-} == "" ]]; then + error "This script must be run in CI or with --dry-run." +fi + +old_version="$(git describe --abbrev=0 "$ref^1")" +cur_tag="$(git describe --abbrev=0 "$ref")" +if [[ $old_version != "$cur_tag" ]]; then + message="Ref \"$ref\" is already tagged with a release ($cur_tag)" + if ! ((dry_run)); then + error "$message." + fi + log "DRYRUN: $message, echoing current tag." + echo "$cur_tag" + exit 0 +fi +ref=$(git rev-parse --short "$ref") + +log "Checking commit metadata for changes since $old_version..." +# shellcheck source=scripts/release/check_commit_metadata.sh +source "$SCRIPT_DIR/release/check_commit_metadata.sh" "$old_version" "$ref" + +if ((COMMIT_METADATA_BREAKING == 1)); then + prev_increment=$increment + if [[ $increment == patch ]]; then + increment=minor + fi + if [[ $prev_increment != "$increment" ]]; then + log "Breaking change detected, changing version increment from \"$prev_increment\" to \"$increment\"." + else + log "Breaking change detected, provided increment is sufficient, using \"$increment\" increment." + fi +else + log "No breaking changes detected, using \"$increment\" increment." +fi + +mapfile -d . -t version_parts <<<"${old_version#v}" +case "$increment" in +patch) + version_parts[2]=$((version_parts[2] + 1)) + ;; +minor) + version_parts[1]=$((version_parts[1] + 1)) + version_parts[2]=0 + ;; +major) + version_parts[0]=$((version_parts[0] + 1)) + version_parts[1]=0 + version_parts[2]=0 + ;; +*) + error "Unrecognized version increment." + ;; +esac + +new_version="v${version_parts[0]}.${version_parts[1]}.${version_parts[2]}" + +log "Old version: $old_version" +log "New version: $new_version" +maybedryrun "$dry_run" git tag -a "$new_version" -m "Release $new_version" "$ref" +maybedryrun "$dry_run" git push --quiet origin "$new_version" + +echo "$new_version" diff --git a/scripts/release/publish.sh b/scripts/release/publish.sh index 8b592212bf3f6..863dc07e3f8bb 100755 --- a/scripts/release/publish.sh +++ b/scripts/release/publish.sh @@ -34,9 +34,11 @@ if [[ "${CI:-}" == "" ]]; then fi version="" +release_notes="" +draft=0 dry_run=0 -args="$(getopt -o "" -l version:,dry-run -- "$@")" +args="$(getopt -o "" -l version:,release-notes:,draft,dry-run -- "$@")" eval set -- "$args" while true; do case "$1" in @@ -44,6 +46,14 @@ while true; do version="$2" shift 2 ;; + --release-notes) + release_notes="$2" + shift 2 + ;; + --draft) + draft=1 + shift + ;; --dry-run) dry_run=1 shift @@ -67,6 +77,10 @@ if [[ "$version" == "" ]]; then version="$(execrelative ./version.sh)" fi +if [[ -z $release_notes ]]; then + error "No release notes specified, use --release-notes." +fi + # realpath-ify all input files so we can cdroot below. files=() for f in "$@"; do @@ -96,22 +110,6 @@ if [[ "$(git describe --always)" != "$new_tag" ]]; then log "The provided version does not match the current git tag, but --dry-run was supplied so continuing..." fi -# This returns the tag before the current tag. -old_tag="$(git describe --abbrev=0 HEAD^1)" - -# For dry-run builds we want to use the SHA instead of the tag, because the new -# tag probably doesn't exist. -new_ref="$new_tag" -if [[ "$dry_run" == 1 ]]; then - new_ref="$(git rev-parse --short HEAD)" -fi - -# shellcheck source=scripts/release/check_commit_metadata.sh -source "$SCRIPT_DIR/release/check_commit_metadata.sh" "$old_tag" "$new_ref" - -# Craft the release notes. -release_notes="$(execrelative ./release/generate_release_notes.sh --old-version "$old_tag" --new-version "$new_tag" --ref "$new_ref")" - release_notes_file="$(mktemp)" echo "$release_notes" >"$release_notes_file" @@ -127,7 +125,7 @@ pushd "$temp_dir" sha256sum ./* | sed -e 's/\.\///' - >"coder_${version}_checksums.txt" popd -log "--- Creating release $new_tag" +log "--- Publishing release $new_tag on GitHub" log log "Description:" echo "$release_notes" | sed -e 's/^/\t/' - 1>&2 @@ -139,11 +137,17 @@ popd log log +args=() +if ((draft)); then + args+=(--draft) +fi + # We pipe `true` into `gh` so that it never tries to be interactive. true | maybedryrun "$dry_run" gh release create \ --title "$new_tag" \ --notes-file "$release_notes_file" \ + "${args[@]}" \ "$new_tag" \ "$temp_dir"/* From b7fc031c565bff432afe816903d45cf0f8e725ac Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 10 Jan 2023 13:33:51 +0000 Subject: [PATCH 02/16] Ignore metadata to test workflow --- .github/workflows/release.yaml | 6 ++++++ scripts/release/check_commit_metadata.sh | 17 ++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 2a237ef293c94..9cae92750cfc2 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -35,6 +35,7 @@ permissions: env: CODER_RELEASE: ${{ github.event.inputs.snapshot && 'false' || 'true' }} + DRY_RUN: ${{ (github.event.inputs.dry_run || github.event.inputs.snapshot) && 'true' || 'false' }} jobs: release: @@ -115,6 +116,11 @@ jobs: ref=HEAD old_version="$(git describe --abbrev=0 "$ref^1")" + if [[ $DRY_RUN == true ]]; then + # Allow dry-run of branches to pass. + export CODER_IGNORE_MISSING_COMMIT_METADATA=1 + fi + # Cache commit metadata. . ./scripts/release/check_commit_metadata.sh "$old_version" "$ref" diff --git a/scripts/release/check_commit_metadata.sh b/scripts/release/check_commit_metadata.sh index 4bce8542595da..c481e2cbd6e57 100755 --- a/scripts/release/check_commit_metadata.sh +++ b/scripts/release/check_commit_metadata.sh @@ -26,6 +26,11 @@ if [[ -z $to_ref ]]; then error "No to_ref specified" fi +ignore_missing_metadata=${CODER_IGNORE_MISSING_COMMIT_METADATA:-0} +if [[ $ignore_missing_metadata == 1 ]]; then + log "WARNING: Ignoring missing commit metadata, breaking changes may be missed." +fi + range="$from_ref..$to_ref" # Check dependencies. @@ -91,9 +96,11 @@ main() { commit_sha_long=${parts[1]} commit_prefix=${parts[2]} - # Safety-check, guarantee all commits had their metadata fetched. - if [[ ! -v labels[$commit_sha_long] ]]; then - error "Metadata missing for commit $commit_sha_short" + if [[ $ignore_missing_metadata != 1 ]]; then + # Safety-check, guarantee all commits had their metadata fetched. + if [[ ! -v labels[$commit_sha_long] ]]; then + error "Metadata missing for commit $commit_sha_short" + fi fi # Store the commit title for later use. @@ -103,11 +110,11 @@ main() { # First, check the title for breaking changes. This avoids doing a # GH API request if there's a match. - if [[ $commit_prefix =~ $breaking_title ]] || [[ ${labels[$commit_sha_long]} = *"label:$breaking_label"* ]]; then + if [[ $commit_prefix =~ $breaking_title ]] || [[ ${labels[$commit_sha_long]:-} = *"label:$breaking_label"* ]]; then COMMIT_METADATA_CATEGORY[$commit_sha_short]=$breaking_category COMMIT_METADATA_BREAKING=1 continue - elif [[ ${labels[$commit_sha_long]} = *"label:$security_label"* ]]; then + elif [[ ${labels[$commit_sha_long]:-} = *"label:$security_label"* ]]; then COMMIT_METADATA_CATEGORY[$commit_sha_short]=$security_label continue fi From 368786f7ebe20725418571140565350660e4cd48 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 10 Jan 2023 16:01:11 +0000 Subject: [PATCH 03/16] Add -h, --help to release.sh --- scripts/release.sh | 66 +++++++++++++----------- scripts/release/increment_version_tag.sh | 2 +- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/scripts/release.sh b/scripts/release.sh index e82c2c814a45f..4df75d2388605 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -1,45 +1,49 @@ #!/usr/bin/env bash -# This script should be called to create a new release. -# -# When run, this script will display the new version number and optionally a -# preview of the release notes. The new version will be selected automatically -# based on if the release contains breaking changes or not. If the release -# contains breaking changes, a new minor version will be created. Otherwise, a -# new patch version will be created. -# -# Set --ref if you need to specify a specific commit that the new version will -# be tagged at, otherwise the latest commit will be used. -# -# Set --minor to force a minor version bump, even when there are no breaking -# changes. Likewise for --major. By default a patch version will be created. -# -# Set --dry-run to run the release workflow in CI as a dry-run (no release will -# be created). -# -# To mark a release as containing breaking changes, the commit title should -# either contain a known prefix with an exclamation mark ("feat!:", -# "feat(api)!:") or the PR that was merged can be tagged with the -# "release/breaking" label. -# -# To test changes to this script, you can set `--branch `, which will -# run the release workflow in CI as a dry-run and use the latest commit on the -# specified branch as the release commit. This will also set --dry-run. -# -# Usage: ./release.sh [--branch ] [--draft] [--dry-run] [--ref ] [--major | --minor | --patch] - set -euo pipefail # shellcheck source=scripts/lib.sh source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" cdroot +usage() { + cat <] [--draft] [--dry-run] [--ref ] [--major | --minor | --patch] + +This script should be called to create a new release. + +When run, this script will display the new version number and optionally a +preview of the release notes. The new version will be selected automatically +based on if the release contains breaking changes or not. If the release +contains breaking changes, a new minor version will be created. Otherwise, a +new patch version will be created. + +Set --ref if you need to specify a specific commit that the new version will +be tagged at, otherwise the latest commit will be used. + +Set --minor to force a minor version bump, even when there are no breaking +changes. Likewise for --major. By default a patch version will be created. + +Set --dry-run to run the release workflow in CI as a dry-run (no release will +be created). + +To mark a release as containing breaking changes, the commit title should +either contain a known prefix with an exclamation mark ("feat!:", +"feat(api)!:") or the PR that was merged can be tagged with the +"release/breaking" label. + +To test changes to this script, you can set --branch , which will +run the release workflow in CI as a dry-run and use the latest commit on the +specified branch as the release commit. This will also set --dry-run. +EOH +} + branch=main draft=0 dry_run=0 ref= increment= -args="$(getopt -o n -l branch:,draft,dry-run,ref:,major,minor,patch -- "$@")" +args="$(getopt -o h -l branch:,draft,dry-run,help,ref:,major,minor,patch -- "$@")" eval set -- "$args" while true; do case "$1" in @@ -58,6 +62,10 @@ while true; do dry_run=1 shift ;; + -h | --help) + usage + exit 0 + ;; --ref) ref="$2" shift 2 diff --git a/scripts/release/increment_version_tag.sh b/scripts/release/increment_version_tag.sh index 8bdf7bc456f34..7f4ea254c0c14 100755 --- a/scripts/release/increment_version_tag.sh +++ b/scripts/release/increment_version_tag.sh @@ -19,7 +19,7 @@ dry_run=0 ref=HEAD increment= -args="$(getopt -o n -l dry-run,ref:,major,minor,patch -- "$@")" +args="$(getopt -o '' -l dry-run,ref:,major,minor,patch -- "$@")" eval set -- "$args" while true; do case "$1" in From 2449a80a31e89098a58de815716c7e4bfa5cf546 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 10 Jan 2023 16:10:01 +0000 Subject: [PATCH 04/16] Add -h, --help to increment_version_tag.sh --- scripts/release/increment_version_tag.sh | 30 +++++++++++++++--------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/scripts/release/increment_version_tag.sh b/scripts/release/increment_version_tag.sh index 7f4ea254c0c14..4960de8964452 100755 --- a/scripts/release/increment_version_tag.sh +++ b/scripts/release/increment_version_tag.sh @@ -1,25 +1,29 @@ #!/usr/bin/env bash -# This script should be called to tag a new release. It will take the suggested -# increment (major, minor, patch) and optionally promote e.g. patch -> minor if -# there are breaking changes between the previous version and the given --ref -# (or HEAD). -# -# Usage: ./increment_version_tag.sh [--dry-run] [--ref ] <--major | --minor | --patch> -# -# This script will create a git tag, so it should only be run in CI (or via -# --dry-run). - set -euo pipefail # shellcheck source=scripts/lib.sh source "$(dirname "$(dirname "${BASH_SOURCE[0]}")")/lib.sh" cdroot +usage() { + cat <] <--major | --minor | --patch> + +This script should be called to tag a new release. It will take the suggested +increment (major, minor, patch) and optionally promote e.g. patch -> minor if +there are breaking changes between the previous version and the given --ref +(or HEAD). + +This script will create a git tag, so it should only be run in CI (or via +--dry-run). +EOH +} + dry_run=0 ref=HEAD increment= -args="$(getopt -o '' -l dry-run,ref:,major,minor,patch -- "$@")" +args="$(getopt -o h -l dry-run,help,ref:,major,minor,patch -- "$@")" eval set -- "$args" while true; do case "$1" in @@ -31,6 +35,10 @@ while true; do ref="$2" shift 2 ;; + -h | --help) + usage + exit 0 + ;; --major | --minor | --patch) if [[ -n $increment ]]; then error "Cannot specify multiple version increments." From c5a6ead2bf934f5cb2d224b9b5d7a891b0816c54 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 10 Jan 2023 18:47:20 +0200 Subject: [PATCH 05/16] Update .github/workflows/release.yaml Co-authored-by: Dean Sheather --- .github/workflows/release.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 9cae92750cfc2..b3230dafa8ac5 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -113,6 +113,7 @@ jobs: - name: Create release tag and release notes run: | + set -eu ref=HEAD old_version="$(git describe --abbrev=0 "$ref^1")" From 73813839e9e03f83f8ceb3a7afe43c0deaf3cfab Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 10 Jan 2023 17:14:22 +0000 Subject: [PATCH 06/16] Use release notes file --- .github/workflows/release.yaml | 16 +++++++++------- scripts/release/publish.sh | 13 +++++-------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b3230dafa8ac5..f29a1fe049629 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -113,7 +113,7 @@ jobs: - name: Create release tag and release notes run: | - set -eu + set -euo pipefail ref=HEAD old_version="$(git describe --abbrev=0 "$ref^1")" @@ -134,13 +134,14 @@ jobs: )" # Generate notes. - release_notes="$(./scripts/release/generate_release_notes.sh --old-version "$old_version" --new-version "$version" --ref "$ref")" - echo 'CODER_RELEASE_NOTES<> $GITHUB_ENV - echo "$release_notes" >> $GITHUB_ENV - echo 'RN_EOF' >> $GITHUB_ENV + release_notes_file="$(mktemp -t release_notes.XXXXXX)" + ./scripts/release/generate_release_notes.sh --old-version "$old_version" --new-version "$version" --ref "$ref" >> "release_notes_file" + echo CODER_RELEASE_NOTES_FILE="$release_notes_file" >> $GITHUB_ENV - name: Echo release notes - run: echo "$CODER_RELEASE_NOTES" + run: | + set -euo pipefail + cat "$CODER_RELEASE_NOTES_FILE" - name: Build binaries run: | @@ -199,10 +200,11 @@ jobs: - name: Publish release run: | + set -euo pipefail ./scripts/release/publish.sh \ ${{ github.event.inputs.draft && '--draft' }} \ ${{ (github.event.inputs.dry_run || github.event.inputs.snapshot) && '--dry-run' }} \ - --release-notes "$CODER_RELEASE_NOTES" \ + --release-notes-file "$CODER_RELEASE_NOTES_FILE" \ ./build/*_installer.exe \ ./build/*.zip \ ./build/*.tar.gz \ diff --git a/scripts/release/publish.sh b/scripts/release/publish.sh index 863dc07e3f8bb..e755ee0a41ef9 100755 --- a/scripts/release/publish.sh +++ b/scripts/release/publish.sh @@ -34,11 +34,11 @@ if [[ "${CI:-}" == "" ]]; then fi version="" -release_notes="" +release_notes_file="" draft=0 dry_run=0 -args="$(getopt -o "" -l version:,release-notes:,draft,dry-run -- "$@")" +args="$(getopt -o "" -l version:,release-notes-file:,draft,dry-run -- "$@")" eval set -- "$args" while true; do case "$1" in @@ -46,8 +46,8 @@ while true; do version="$2" shift 2 ;; - --release-notes) - release_notes="$2" + --release-notes-file) + release_notes_file="$2" shift 2 ;; --draft) @@ -110,9 +110,6 @@ if [[ "$(git describe --always)" != "$new_tag" ]]; then log "The provided version does not match the current git tag, but --dry-run was supplied so continuing..." fi -release_notes_file="$(mktemp)" -echo "$release_notes" >"$release_notes_file" - # Create temporary release folder so we can generate checksums. Both the # sha256sum and gh binaries support symlinks as input files so this works well. temp_dir="$(mktemp -d)" @@ -128,7 +125,7 @@ popd log "--- Publishing release $new_tag on GitHub" log log "Description:" -echo "$release_notes" | sed -e 's/^/\t/' - 1>&2 +sed -e 's/^/\t/' - <"$release_notes_file" 1>&2 log log "Contents:" pushd "$temp_dir" From e538ec42159546df2757dd9ebc4089435187f234 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 10 Jan 2023 17:14:30 +0000 Subject: [PATCH 07/16] Delay git tag push --- scripts/release/increment_version_tag.sh | 1 - scripts/release/publish.sh | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/release/increment_version_tag.sh b/scripts/release/increment_version_tag.sh index 4960de8964452..309ecfa2e7683 100755 --- a/scripts/release/increment_version_tag.sh +++ b/scripts/release/increment_version_tag.sh @@ -122,6 +122,5 @@ new_version="v${version_parts[0]}.${version_parts[1]}.${version_parts[2]}" log "Old version: $old_version" log "New version: $new_version" maybedryrun "$dry_run" git tag -a "$new_version" -m "Release $new_version" "$ref" -maybedryrun "$dry_run" git push --quiet origin "$new_version" echo "$new_version" diff --git a/scripts/release/publish.sh b/scripts/release/publish.sh index e755ee0a41ef9..fe6babfc05dc3 100755 --- a/scripts/release/publish.sh +++ b/scripts/release/publish.sh @@ -134,6 +134,9 @@ popd log log +log "Pushing git tag" +maybedryrun "$dry_run" git push --quiet origin "$new_tag" + args=() if ((draft)); then args+=(--draft) From 3b4ffa00ba4639fe41529a1a36d0abcb194faf56 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 10 Jan 2023 17:14:44 +0000 Subject: [PATCH 08/16] Limit release concurrency --- .github/workflows/release.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f29a1fe049629..f7ef2a3441b6b 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -37,6 +37,8 @@ env: CODER_RELEASE: ${{ github.event.inputs.snapshot && 'false' || 'true' }} DRY_RUN: ${{ (github.event.inputs.dry_run || github.event.inputs.snapshot) && 'true' || 'false' }} +concurrency: ci-release + jobs: release: runs-on: ${{ github.repository_owner == 'coder' && 'ubuntu-latest-16-cores' || 'ubuntu-latest' }} From e21166058eca381b7d82aaa55292c2c901e41182 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 10 Jan 2023 19:59:18 +0000 Subject: [PATCH 09/16] Remove code from bad fixup --- scripts/release/check_commit_metadata.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/release/check_commit_metadata.sh b/scripts/release/check_commit_metadata.sh index c481e2cbd6e57..bfdd3b33ba98c 100755 --- a/scripts/release/check_commit_metadata.sh +++ b/scripts/release/check_commit_metadata.sh @@ -26,11 +26,6 @@ if [[ -z $to_ref ]]; then error "No to_ref specified" fi -ignore_missing_metadata=${CODER_IGNORE_MISSING_COMMIT_METADATA:-0} -if [[ $ignore_missing_metadata == 1 ]]; then - log "WARNING: Ignoring missing commit metadata, breaking changes may be missed." -fi - range="$from_ref..$to_ref" # Check dependencies. From 29a33be99198a99bf8af07b2dfcef85630a150a0 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 10 Jan 2023 20:01:56 +0000 Subject: [PATCH 10/16] Rename as version_tag.sh (review feedback) --- .github/workflows/release.yaml | 2 +- scripts/release.sh | 2 +- scripts/release/{increment_version_tag.sh => tag_version.sh} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename scripts/release/{increment_version_tag.sh => tag_version.sh} (96%) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f7ef2a3441b6b..661d689acc94b 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -129,7 +129,7 @@ jobs: # Create new release tag. version="$( - ./scripts/release/increment_version_tag.sh \ + ./scripts/release/tag_version.sh \ ${{ (github.event.inputs.dry_run || github.event.inputs.snapshot) && '--dry-run' }} \ --ref "$ref" \ --${{ github.event.inputs.increment }} diff --git a/scripts/release.sh b/scripts/release.sh index 4df75d2388605..56421a8ef7143 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -115,7 +115,7 @@ old_version=${versions[0]} # shellcheck source=scripts/release/check_commit_metadata.sh source "$SCRIPT_DIR/release/check_commit_metadata.sh" "$old_version" "$ref" -new_version="$(execrelative ./release/increment_version_tag.sh --dry-run --ref "$ref" --"$increment")" +new_version="$(execrelative ./release/tag_version.sh --dry-run --ref "$ref" --"$increment")" release_notes="$(execrelative ./release/generate_release_notes.sh --old-version "$old_version" --new-version "$new_version" --ref "$ref")" echo diff --git a/scripts/release/increment_version_tag.sh b/scripts/release/tag_version.sh similarity index 96% rename from scripts/release/increment_version_tag.sh rename to scripts/release/tag_version.sh index 309ecfa2e7683..f5e47785a0048 100755 --- a/scripts/release/increment_version_tag.sh +++ b/scripts/release/tag_version.sh @@ -7,7 +7,7 @@ cdroot usage() { cat <] <--major | --minor | --patch> +Usage: ./version_tag.sh [--dry-run] [--ref ] <--major | --minor | --patch> This script should be called to tag a new release. It will take the suggested increment (major, minor, patch) and optionally promote e.g. patch -> minor if From 7055b1d965626bf8d72460c971d11018db38aecf Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 10 Jan 2023 20:18:31 +0000 Subject: [PATCH 11/16] Fix release notes check --- scripts/release/publish.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/release/publish.sh b/scripts/release/publish.sh index fe6babfc05dc3..e76bcdacb32c9 100755 --- a/scripts/release/publish.sh +++ b/scripts/release/publish.sh @@ -77,8 +77,8 @@ if [[ "$version" == "" ]]; then version="$(execrelative ./version.sh)" fi -if [[ -z $release_notes ]]; then - error "No release notes specified, use --release-notes." +if [[ -z $release_notes_file ]]; then + error "No release notes files specified, use --release-notes-file." fi # realpath-ify all input files so we can cdroot below. From 9953d9bd1a2ad71307c07e171f575b9f895be62b Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 10 Jan 2023 20:18:48 +0000 Subject: [PATCH 12/16] Add automatic release watching --- scripts/release.sh | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/scripts/release.sh b/scripts/release.sh index 56421a8ef7143..f0c293e676a0d 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -152,4 +152,28 @@ gh workflow run release.yaml \ -F snapshot=false \ "${args[@]}" -log "Release process started, you can watch the release via: gh run watch --exit-status " +read -p "Watch release? (y/n)" -n 1 -r watch +if ! [[ $watch =~ ^[Yy]$ ]]; then + exit 0 +fi + +log 'Waiting for job to become "in_progress"...' + +# Wait at most 3 minutes (3*60)/3 = 60 for the job to start. +for _ in $(seq 1 60); do + mapfile -t run < <( + # 3886828508 in_progress + gh run list -w release.yaml \ + --limit 1 \ + --json status,databaseId \ + --jq '.[] | (.databaseId | tostring) + " " + .status' + ) + if [[ ${run[1]} != "in_progress" ]]; then + sleep 3 + continue + fi + gh run watch --exit-status "${run[0]}" + exit 0 +done + +error "Waiting for job to start timed out." From cf2c523d0f9394f430b4a8243e974e9089095a22 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 10 Jan 2023 20:22:32 +0000 Subject: [PATCH 13/16] Fix watch --- scripts/release.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/release.sh b/scripts/release.sh index f0c293e676a0d..4f1e73e111c7b 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -162,11 +162,13 @@ log 'Waiting for job to become "in_progress"...' # Wait at most 3 minutes (3*60)/3 = 60 for the job to start. for _ in $(seq 1 60); do mapfile -t run < <( - # 3886828508 in_progress + # Output: + # 3886828508 + # in_progress gh run list -w release.yaml \ --limit 1 \ --json status,databaseId \ - --jq '.[] | (.databaseId | tostring) + " " + .status' + --jq '.[] | (.databaseId | tostring), .status' ) if [[ ${run[1]} != "in_progress" ]]; then sleep 3 From 61b6a9096f73a60501a871da94c29bb6900fb18c Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 10 Jan 2023 20:34:11 +0000 Subject: [PATCH 14/16] Fix notes --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 661d689acc94b..db0d0e6dd0bfa 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -137,7 +137,7 @@ jobs: # Generate notes. release_notes_file="$(mktemp -t release_notes.XXXXXX)" - ./scripts/release/generate_release_notes.sh --old-version "$old_version" --new-version "$version" --ref "$ref" >> "release_notes_file" + ./scripts/release/generate_release_notes.sh --old-version "$old_version" --new-version "$version" --ref "$ref" >> "$release_notes_file" echo CODER_RELEASE_NOTES_FILE="$release_notes_file" >> $GITHUB_ENV - name: Echo release notes From 21080568cb1ab8e9896001085c7266bbcaef7007 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 10 Jan 2023 21:21:42 +0000 Subject: [PATCH 15/16] Prettify output --- scripts/release.sh | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/release.sh b/scripts/release.sh index 4f1e73e111c7b..bb2bb37d2685e 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -118,9 +118,9 @@ source "$SCRIPT_DIR/release/check_commit_metadata.sh" "$old_version" "$ref" new_version="$(execrelative ./release/tag_version.sh --dry-run --ref "$ref" --"$increment")" release_notes="$(execrelative ./release/generate_release_notes.sh --old-version "$old_version" --new-version "$new_version" --ref "$ref")" -echo +log read -p "Preview release notes? (y/n) " -n 1 -r show_reply -echo +log if [[ $show_reply =~ ^[Yy]$ ]]; then echo -e "$release_notes\n" fi @@ -133,7 +133,7 @@ if ((dry_run)); then create_message+=" (DRYRUN)" fi read -p "$create_message? (y/n) " -n 1 -r create -echo +log if ! [[ $create =~ ^[Yy]$ ]]; then exit 0 fi @@ -146,13 +146,16 @@ if ((dry_run)); then args+=(-F dry_run=true) fi +log gh workflow run release.yaml \ --ref "$branch" \ -F increment="$increment" \ -F snapshot=false \ "${args[@]}" +log -read -p "Watch release? (y/n)" -n 1 -r watch +read -p "Watch release? (y/n) " -n 1 -r watch +log if ! [[ $watch =~ ^[Yy]$ ]]; then exit 0 fi From c559c8a3f1021ed164a95de0ba0d535229ad476a Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 11 Jan 2023 11:21:05 +0000 Subject: [PATCH 16/16] ci: Add git config, tag as "GitHub Actions Bot" --- .github/workflows/release.yaml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index db0d0e6dd0bfa..db49a8f5a32a0 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,5 +1,5 @@ # GitHub release workflow. -name: New Release +name: Release on: workflow_dispatch: inputs: @@ -37,10 +37,11 @@ env: CODER_RELEASE: ${{ github.event.inputs.snapshot && 'false' || 'true' }} DRY_RUN: ${{ (github.event.inputs.dry_run || github.event.inputs.snapshot) && 'true' || 'false' }} -concurrency: ci-release +concurrency: ${{ github.workflow }}-${{ github.ref }} jobs: release: + name: Create and publish runs-on: ${{ github.repository_owner == 'coder' && 'ubuntu-latest-16-cores' || 'ubuntu-latest' }} env: # Necessary for Docker manifest @@ -58,6 +59,12 @@ jobs: - name: Fetch git tags run: git fetch --tags --force + # Configure git user name/email for creating annotated version tag. + - name: Setup git config + run: | + git config user.name "GitHub Actions Bot" + git config user.email "" + - name: Docker Login uses: docker/login-action@v2 with: