From 8838b15dd21a2a4f02046f82994aa234a2fb7668 Mon Sep 17 00:00:00 2001 From: Samantha Frank Date: Fri, 26 Nov 2021 13:27:14 -0800 Subject: [PATCH 1/6] Add hotfix release script --- test/boulder-tools/hotfix_release.sh | 105 +++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100755 test/boulder-tools/hotfix_release.sh diff --git a/test/boulder-tools/hotfix_release.sh b/test/boulder-tools/hotfix_release.sh new file mode 100755 index 00000000000..907c3209341 --- /dev/null +++ b/test/boulder-tools/hotfix_release.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash + +# -e Stops execution in the instance of a command or pipeline error. +# -u Treat unset variables as an error and exit immediately. +set -eu + +STATUS="FAILURE" + +function exit_msg() { + # complain to STDERR and exit with error + echo "$*" >&2 + exit 2 +} + +function print_outcome() { + if [ "$STATUS" == SUCCESS ] + then + echo + echo -e "\e[32m"$STATUS"\e[0m" + else + echo + echo -e "\e[31m"$STATUS"\e[0m" + fi +} + +function print_heading() { + echo + echo -e "\e[34m\e[1m"$1"\e[0m" +} + +function not_yes_no() { + case "${1}" in + y | Y | YES) create_branch="yes" && return 1 ;; + n | N | NO) create_branch="no" && return 1 ;; + *) return 0 ;; + esac +} + +function get_user_input() { + while [ -z "${create_branch}" ]; do + read -p "${1}" create_branch + if not_yes_no "${create_branch}" + then + echo "Need [yes/no], got [${create_branch}]" + create_branch="" + fi + done +} + +# On EXIT, trap and print outcome. +trap "print_outcome" EXIT + +print_heading "Ensuring main branch is up to date" +git fetch --all +git checkout main +git pull --rebase + +print_heading "Fetching details for the most recent release tag" +latest_tag_sha=$(git show-ref --tags | tail -1 | awk '{print $1}') +latest_tag_name=$(git show-ref --tags | tail -1 | awk '{print $2}' | sed 's|refs\/tags\/||') + +print_heading "Latest tag:" +echo "${latest_tag_name}" +echo "${latest_tag_sha}" + +print_heading "Hotfix release tag:" +if [[ "${latest_tag_name: -1}" =~ '^[0-9]+$' ]] +then + new_tag_name="${latest_tag_name}a" +else + tag_with_last_char_removed=$(echo "${latest_tag_name}" | sed 's|.$||') + next_tag_letter=$(echo "${latest_tag_name: -1}" | tr "0-9a-z" "1-9a-z_") + new_tag_name="${tag_with_last_char_removed}${next_tag_letter}" +fi +echo "${new_tag_name}" + +create_branch="" +get_user_input "Create hotfix release branch: ${new_tag_name}? " +if [ "${create_branch}" = yes ] +then + print_heading "Creating new branch ${new_tag_name} @ ${latest_tag_sha}" + git checkout -b "${new_tag_name}" "${latest_tag_sha}" + git push --set-upstream origin "${new_tag_name}" +fi + +cherry_pick_sha="" +while [ -z "${cherry_pick_sha}" ]; do + read -p "Commit to cherry pick: " cherry_pick_sha + print_heading "Cherry-picking ${cherry_pick_sha} to branch ${new_tag_name}" + git cherry-pick "${cherry_pick_sha}" + git push --force-with-lease + git tag "${new_tag_name}" -s -m "${new_tag_name}" "origin/${new_tag_name}" +done + +print_heading "Complete the following steps:" +echo "- Open https://github.com/letsencrypt/boulder/actions/workflows/boulder-ci.yml" +echo "- Click the 'Run Workflow' button on the right-hand-side of the page" +echo "- Under 'Use workflow' click 'from Branch: main'" +echo "- Under 'select branch' paste: ${new_tag_name} and click 'Run'" +ech0 "- Diff: https://github.com/letsencrypt/boulder/compare/${latest_tag_name}...${new_tag_name}" +echo "- Once the CI pass has succeeded, run: git push origin --tags" + +# Because set -e stops execution in the instance of a command or pipeline error; +# if we got here we assume success +STATUS="SUCCESS" From 5c315e6f7c721d6240601a8dee0d71821f05a30a Mon Sep 17 00:00:00 2001 From: Samantha Frank Date: Sat, 27 Nov 2021 19:57:50 -0800 Subject: [PATCH 2/6] Fix typo --- test/boulder-tools/hotfix_release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/boulder-tools/hotfix_release.sh b/test/boulder-tools/hotfix_release.sh index 907c3209341..cb9ca8b780e 100755 --- a/test/boulder-tools/hotfix_release.sh +++ b/test/boulder-tools/hotfix_release.sh @@ -97,7 +97,7 @@ echo "- Open https://github.com/letsencrypt/boulder/actions/workflows/boulder-ci echo "- Click the 'Run Workflow' button on the right-hand-side of the page" echo "- Under 'Use workflow' click 'from Branch: main'" echo "- Under 'select branch' paste: ${new_tag_name} and click 'Run'" -ech0 "- Diff: https://github.com/letsencrypt/boulder/compare/${latest_tag_name}...${new_tag_name}" +echo "- Diff: https://github.com/letsencrypt/boulder/compare/${latest_tag_name}...${new_tag_name}" echo "- Once the CI pass has succeeded, run: git push origin --tags" # Because set -e stops execution in the instance of a command or pipeline error; From b1e58585743bec3fadc05918051cfabc11476457 Mon Sep 17 00:00:00 2001 From: Samantha Frank Date: Fri, 3 Dec 2021 14:29:02 -0800 Subject: [PATCH 3/6] Addressing comments --- test/boulder-tools/hotfix_release.sh | 61 ++++++++++++++++------------ 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/test/boulder-tools/hotfix_release.sh b/test/boulder-tools/hotfix_release.sh index cb9ca8b780e..039e00253d3 100755 --- a/test/boulder-tools/hotfix_release.sh +++ b/test/boulder-tools/hotfix_release.sh @@ -5,34 +5,38 @@ set -eu STATUS="FAILURE" +RST=$(tput sgr0) +RED=$(tput bold && tput setaf 1) +GRN=$(tput bold && tput setaf 2) +BLU=$(tput bold && tput setaf 4) function exit_msg() { # complain to STDERR and exit with error - echo "$*" >&2 + echo "${*}" >&2 exit 2 } function print_outcome() { - if [ "$STATUS" == SUCCESS ] + if [ "${STATUS}" == SUCCESS ] then echo - echo -e "\e[32m"$STATUS"\e[0m" + echo "${GRN}${STATUS}${RST}" else echo - echo -e "\e[31m"$STATUS"\e[0m" + echo "${RED}${STATUS}${RST}" fi } function print_heading() { echo - echo -e "\e[34m\e[1m"$1"\e[0m" + echo "${BLU}${1}${RST}" } function not_yes_no() { case "${1}" in - y | Y | YES) create_branch="yes" && return 1 ;; - n | N | NO) create_branch="no" && return 1 ;; - *) return 0 ;; + [yY][eE][sS]|[yY]) create_branch="yes" && return 1 ;; + [nN][oO]|[nN]) create_branch="no" && return 1 ;; + *) return 0 ;; esac } @@ -63,13 +67,14 @@ print_heading "Latest tag:" echo "${latest_tag_name}" echo "${latest_tag_sha}" + print_heading "Hotfix release tag:" -if [[ "${latest_tag_name: -1}" =~ '^[0-9]+$' ]] +if [[ "${latest_tag_name: -1}" =~ ^[0-9] ]] then new_tag_name="${latest_tag_name}a" else tag_with_last_char_removed=$(echo "${latest_tag_name}" | sed 's|.$||') - next_tag_letter=$(echo "${latest_tag_name: -1}" | tr "0-9a-z" "1-9a-z_") + next_tag_letter=$(echo "${latest_tag_name: -1}" | tr "0-9a-z" "1-9a-z_") new_tag_name="${tag_with_last_char_removed}${next_tag_letter}" fi echo "${new_tag_name}" @@ -81,24 +86,28 @@ then print_heading "Creating new branch ${new_tag_name} @ ${latest_tag_sha}" git checkout -b "${new_tag_name}" "${latest_tag_sha}" git push --set-upstream origin "${new_tag_name}" + + cherry_pick_sha="" + while [ -z "${cherry_pick_sha}" ]; do + read -p "Commit to cherry pick: " cherry_pick_sha + print_heading "Cherry-picking ${cherry_pick_sha} to branch ${new_tag_name}" + git cherry-pick "${cherry_pick_sha}" + git push --force-with-lease + git tag "${new_tag_name}" -s -m "${new_tag_name}" "origin/${new_tag_name}" + done + + print_heading "Complete the following steps:" + echo "- Open https://github.com/letsencrypt/boulder/actions/workflows/boulder-ci.yml" + echo "- Click the 'Run Workflow' button on the right-hand-side of the page" + echo "- Under 'Use workflow' click 'from Branch: main'" + echo "- Under 'select branch' paste: ${new_tag_name} and click 'Run'" + echo "- Diff: https://github.com/letsencrypt/boulder/compare/${latest_tag_name}...${new_tag_name}" + echo "- Once the CI pass has succeeded, run: git push origin --tags" +else + exit_msg "No hotfix release branch was created, exiting..." fi -cherry_pick_sha="" -while [ -z "${cherry_pick_sha}" ]; do - read -p "Commit to cherry pick: " cherry_pick_sha - print_heading "Cherry-picking ${cherry_pick_sha} to branch ${new_tag_name}" - git cherry-pick "${cherry_pick_sha}" - git push --force-with-lease - git tag "${new_tag_name}" -s -m "${new_tag_name}" "origin/${new_tag_name}" -done - -print_heading "Complete the following steps:" -echo "- Open https://github.com/letsencrypt/boulder/actions/workflows/boulder-ci.yml" -echo "- Click the 'Run Workflow' button on the right-hand-side of the page" -echo "- Under 'Use workflow' click 'from Branch: main'" -echo "- Under 'select branch' paste: ${new_tag_name} and click 'Run'" -echo "- Diff: https://github.com/letsencrypt/boulder/compare/${latest_tag_name}...${new_tag_name}" -echo "- Once the CI pass has succeeded, run: git push origin --tags" + # Because set -e stops execution in the instance of a command or pipeline error; # if we got here we assume success From 66ca4382862d687dda505885998e8425d75cb7b9 Mon Sep 17 00:00:00 2001 From: Samantha Frank Date: Fri, 3 Dec 2021 14:52:24 -0800 Subject: [PATCH 4/6] Addressing comments --- test/boulder-tools/hotfix_release.sh | 30 ++++++++++++---------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/test/boulder-tools/hotfix_release.sh b/test/boulder-tools/hotfix_release.sh index 039e00253d3..ab7f816596b 100755 --- a/test/boulder-tools/hotfix_release.sh +++ b/test/boulder-tools/hotfix_release.sh @@ -56,8 +56,7 @@ trap "print_outcome" EXIT print_heading "Ensuring main branch is up to date" git fetch --all -git checkout main -git pull --rebase +git checkout origin/main print_heading "Fetching details for the most recent release tag" latest_tag_sha=$(git show-ref --tags | tail -1 | awk '{print $1}') @@ -73,42 +72,39 @@ if [[ "${latest_tag_name: -1}" =~ ^[0-9] ]] then new_tag_name="${latest_tag_name}a" else - tag_with_last_char_removed=$(echo "${latest_tag_name}" | sed 's|.$||') next_tag_letter=$(echo "${latest_tag_name: -1}" | tr "0-9a-z" "1-9a-z_") + tag_with_last_char_removed=$(echo "${latest_tag_name}" | sed 's|.$||') new_tag_name="${tag_with_last_char_removed}${next_tag_letter}" fi echo "${new_tag_name}" create_branch="" -get_user_input "Create hotfix release branch: ${new_tag_name}? " +release_branch_name=$(echo "${new_tag_name}" | sed 's|release-|release-branch-|') +get_user_input "Create hotfix release branch: ${release_branch_name}? " if [ "${create_branch}" = yes ] then - print_heading "Creating new branch ${new_tag_name} @ ${latest_tag_sha}" - git checkout -b "${new_tag_name}" "${latest_tag_sha}" - git push --set-upstream origin "${new_tag_name}" + print_heading "Creating new branch ${release_branch_name} @ ${latest_tag_sha}" + git checkout -b "${release_branch_name}" "${latest_tag_sha}" + git push --set-upstream origin "${release_branch_name}" cherry_pick_sha="" while [ -z "${cherry_pick_sha}" ]; do read -p "Commit to cherry pick: " cherry_pick_sha - print_heading "Cherry-picking ${cherry_pick_sha} to branch ${new_tag_name}" + print_heading "Cherry-picking ${cherry_pick_sha} to branch ${release_branch_name}" git cherry-pick "${cherry_pick_sha}" - git push --force-with-lease - git tag "${new_tag_name}" -s -m "${new_tag_name}" "origin/${new_tag_name}" + git push origin ${release_branch_name}:${release_branch_name} + git tag "${new_tag_name}" -s -m "${new_tag_name}" "origin/${release_branch_name}" done print_heading "Complete the following steps:" + echo "- Diff: https://github.com/letsencrypt/boulder/compare/${latest_tag_name}...${release_branch_name}" echo "- Open https://github.com/letsencrypt/boulder/actions/workflows/boulder-ci.yml" - echo "- Click the 'Run Workflow' button on the right-hand-side of the page" - echo "- Under 'Use workflow' click 'from Branch: main'" - echo "- Under 'select branch' paste: ${new_tag_name} and click 'Run'" - echo "- Diff: https://github.com/letsencrypt/boulder/compare/${latest_tag_name}...${new_tag_name}" - echo "- Once the CI pass has succeeded, run: git push origin --tags" + echo "- Ensure that the CI pass for ${release_branch_name} completes successfully" + echo "- Run: git push origin ${new_tag_name}" else exit_msg "No hotfix release branch was created, exiting..." fi - - # Because set -e stops execution in the instance of a command or pipeline error; # if we got here we assume success STATUS="SUCCESS" From b8d6e643db310b303806a5cfb142cbf51b0b0a36 Mon Sep 17 00:00:00 2001 From: Samantha Frank Date: Fri, 3 Dec 2021 17:13:00 -0800 Subject: [PATCH 5/6] Only check remote tags --- test/boulder-tools/hotfix_release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/boulder-tools/hotfix_release.sh b/test/boulder-tools/hotfix_release.sh index ab7f816596b..4f8a257990b 100755 --- a/test/boulder-tools/hotfix_release.sh +++ b/test/boulder-tools/hotfix_release.sh @@ -59,8 +59,8 @@ git fetch --all git checkout origin/main print_heading "Fetching details for the most recent release tag" -latest_tag_sha=$(git show-ref --tags | tail -1 | awk '{print $1}') -latest_tag_name=$(git show-ref --tags | tail -1 | awk '{print $2}' | sed 's|refs\/tags\/||') +latest_tag_sha=$(git ls-remote --refs --tags | tail -1 | awk '{print $1}') +latest_tag_name=$(git ls-remote --refs --tags | tail -1 | awk '{print $2}' | sed 's|refs\/tags\/||') print_heading "Latest tag:" echo "${latest_tag_name}" From 2ac5e14475fdfbbf588fa5a31269fe26ce5038d2 Mon Sep 17 00:00:00 2001 From: Samantha Date: Fri, 18 Mar 2022 22:04:50 -0700 Subject: [PATCH 6/6] Adding getopt --- test/boulder-tools/hotfix_release.sh | 53 +++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/test/boulder-tools/hotfix_release.sh b/test/boulder-tools/hotfix_release.sh index 4f8a257990b..c9d60f09932 100755 --- a/test/boulder-tools/hotfix_release.sh +++ b/test/boulder-tools/hotfix_release.sh @@ -51,6 +51,57 @@ function get_user_input() { done } +function check_arg() { + if [ -z "$OPTARG" ] + then + exit_msg "No arg for --$OPT option, use: -h for help">&2 + fi +} + +function print_usage_exit() { + echo "$USAGE" + exit 0 +} + +USAGE="$(cat -- <<-EOM + +Usage: + +Without no options passed, this tool will execute a regular tag and release. + + -f, --hotfix Executes release as a hotfix. + -c, --cherry-pick ' ...' Each commit SHA will be cherry-picked, in the + order passed (only used with '--hotfix') + -h, --help Shows this help message + +EOM +)" + +RUN=() +COMMITS=() +while getopts hfc:-: OPT; do + if [ "$OPT" = - ]; then # long option: reformulate OPT and OPTARG + OPT="${OPTARG%%=*}" # extract long option name + OPTARG="${OPTARG#$OPT}" # extract long option argument (may be empty) + OPTARG="${OPTARG#=}" # if long option argument, remove assigning `=` + fi + case "$OPT" in + f | hotfix ) RUN+=("hotfix") ;; + c | cherry-pick ) check_arg; COMMITS+=(${OPTARG[@]}) ;; # multiargs have spaces, leave this unquoted + h | help ) print_usage_exit ;; + ??* ) exit_msg "Illegal option --$OPT" ;; # bad long option + ? ) exit 2 ;; # bad short option (error reported via getopts) + esac +done +shift $((OPTIND-1)) # remove parsed options and args from $@ list + +# Validate use of --cherry-pick is valid. +if ! [[ "${RUN[@]}" =~ hotfix ]] +then + exit_msg "Illegal option: (-c, --cherry-pick) without (-f, --hotfix)" +fi +exit + # On EXIT, trap and print outcome. trap "print_outcome" EXIT @@ -78,7 +129,7 @@ else fi echo "${new_tag_name}" -create_branch="" +local create_branch release_branch_name=$(echo "${new_tag_name}" | sed 's|release-|release-branch-|') get_user_input "Create hotfix release branch: ${release_branch_name}? " if [ "${create_branch}" = yes ]