#!/bin/bash # Script to: ## 1) create and checkout a new branch with the latest tag name ## 2) update NTH versions ## 3) commit release prep changes to new branch ## 4) create a PR from the new branch to upstream/main set -euo pipefail REPO_ROOT_PATH="$( cd "$(dirname "$0")"; cd ../; pwd -P )" MAKEFILE_PATH=$REPO_ROOT_PATH/Makefile LATEST_VERSION=$(make -s -f $MAKEFILE_PATH latest-release-tag | cut -b 2- ) PREVIOUS_VERSION=$(make -s -f $MAKEFILE_PATH previous-release-tag | cut -b 2- ) MAJOR_INC=false MINOR_INC=false PATCH_INC=false os=$(uname) # files with versions, to update REPO_README=$REPO_ROOT_PATH/README.md CHART=$REPO_ROOT_PATH/config/helm/aws-node-termination-handler/Chart.yaml CURR_CHART_VERSION=$(cat $CHART | grep 'version:' | xargs | cut -d' ' -f2 | tr -d '[:space:]') UPDATED_CHART_VERSION="" CHART_VALUES=$REPO_ROOT_PATH/config/helm/aws-node-termination-handler/values.yaml FILES=("$REPO_README" "$CHART" "$CHART_VALUES") FILES_CHANGED=() # release prep LATEST_TAG="v$LATEST_VERSION" NEW_BRANCH="pr/$LATEST_TAG-release" COMMIT_MESSAGE="πŸ₯‘πŸ€– $LATEST_TAG release prep πŸ€–πŸ₯‘" RELEASE_PREP=true # PR details DEFAULT_REPO_FULL_NAME=$(make -s -f $MAKEFILE_PATH repo-full-name) PR_BASE=main # target PR_TITLE="πŸ₯‘πŸ€– $LATEST_TAG release prep" PR_BODY="πŸ₯‘πŸ€– Auto-generated PR for $LATEST_TAG release. Updating release versions in repo." PR_LABEL_1="release-prep" PR_LABEL_2="πŸ€– auto-generatedπŸ€–" EC2_BOT_USER="ec2-bot πŸ€–" EC2_BOT_EMAIL="ec2-bot@users.noreply.github.com" EC2_BOT_SET=false HELP=$(cat << 'EOM' Update repo with the new release version and create a pr from a new release prep branch. This script prompts the user with complete details about the PR before pushing the new local branch to remote and creating the PR. The new release version is the latest local git tag. Note: The local tag creation for a new release is separated from this script. A new tag must be created before this script is run which is automated when this script is run via make targets. Usage: prepare-for-release [options] Options: -d create a draft pr -r target repo full name for the pr (default: aws/aws-node-termination-handler) -h help Examples: prepare-for-release -d update release version in repo and create a draft pr against aws/aws-node-termination-handler prepare-for-release -r username/aws-node-termination-handler update release version in repo and create a pr against username/aws-node-termination-handler EOM ) DRAFT=false REPO_FULL_NAME="" NEED_ROLLBACK=true process_args() { while getopts "hdr:" opt; do case ${opt} in h ) echo -e "$HELP" 1>&2 exit 0 ;; d ) DRAFT=true ;; r ) # todo: validate $REPO_FULL_NAME REPO_FULL_NAME="${OPTARG}" ;; \? ) echo "$HELP" 1>&2 exit 0 ;; esac done # set repo full name to the default value if unset if [ -z $REPO_FULL_NAME ]; then REPO_FULL_NAME=$DEFAULT_REPO_FULL_NAME fi } # output formatting export TERM="xterm" RED=$(tput setaf 1) MAGENTA=$(tput setaf 5) RESET_FMT=$(tput sgr 0) BOLD=$(tput bold) determine_increment() { prev=$1 updated=$2 prev_major_v=$(echo $prev | tr '.' '\n' | head -1) prev_minor_v=$(echo $prev | tr '.' '\n' | head -2 | tail -1) prev_patch_v=$(echo $prev | tr '.' '\n' | tail -1) curr_major_v=$(echo $updated | tr '.' '\n' | head -1) curr_minor_v=$(echo $updated | tr '.' '\n' | head -2 | tail -1) curr_patch_v=$(echo $updated | tr '.' '\n' | tail -1) if [[ "$prev_major_v" -ne "$curr_major_v" ]]; then MAJOR_INC=true elif [[ "$prev_minor_v" -ne "$curr_minor_v" ]]; then MINOR_INC=true elif [[ "$prev_patch_v" -ne "$curr_patch_v" ]]; then PATCH_INC=true fi } increment_chart() { curr_chart_version=$1 curr_major_v=$(echo $curr_chart_version | tr '.' '\n' | head -1) curr_minor_v=$(echo $curr_chart_version | tr '.' '\n' | head -2 | tail -1) curr_patch_v=$(echo $curr_chart_version | tr '.' '\n' | tail -1) if [[ $MAJOR_INC == true ]]; then new_major_v=$(echo $(($curr_major_v + 1))) UPDATED_CHART_VERSION=$(echo $new_major_v.0.0) elif [[ $MINOR_INC == true ]]; then new_minor_v=$(echo $(($curr_minor_v + 1))) UPDATED_CHART_VERSION=$(echo $curr_major_v.$new_minor_v.0) elif [[ $PATCH_INC == true ]]; then new_patch_v=$(echo $(($curr_patch_v + 1))) UPDATED_CHART_VERSION=$(echo $curr_major_v.$curr_minor_v.$new_patch_v) fi echo $UPDATED_CHART_VERSION } # verify origin tracking before creating and pushing new branches verify_origin_tracking() { origin=$(git remote get-url origin 2>&1) || true if [[ $origin == "fatal: No such remote 'origin'" ]] || [[ $origin == "https://github.com/aws/aws-node-termination-handler.git" ]]; then echo -e "❌ ${RED}Expected remote 'origin' to be tracking fork but found \"$origin\". Set it up before running this script again.${RESET_FMT}" NEED_ROLLBACK=false exit 1 fi } create_release_branch() { exists=$(git checkout -b $NEW_BRANCH 2>&1) || true if [[ $exists == "fatal: A branch named '$NEW_BRANCH' already exists." ]]; then echo -e "❌ ${RED}$exists${RESET_FMT}" NEED_ROLLBACK=false exit 1 fi echo -e "βœ… ${BOLD}Created new release branch $NEW_BRANCH\n\n${RESET_FMT}" } update_versions() { # update release version for release prep echo -e "πŸ₯‘ Attempting to update NTH release version in preparation for a new release." UPDATED_CHART_VERSION=$(increment_chart $CURR_CHART_VERSION) for f in "${FILES[@]}"; do # do not exit if grep doesn't find $PREVIOUS_VERSION; continue to exit on all other exit codes has_incorrect_version=$(cat $f | grep $PREVIOUS_VERSION || [[ $? == 1 ]]) chart_should_increment=$(cat $f | grep $CURR_CHART_VERSION || [[ $? == 1 ]]) if [[ ! -z $has_incorrect_version ]] || [[ ! -z $chart_should_increment ]]; then if [[ "${os}" = "Linux" ]]; then sed -i "s/$PREVIOUS_VERSION/$LATEST_VERSION/g" $f sed -i "s/$CURR_CHART_VERSION/$UPDATED_CHART_VERSION/g" $f elif [[ "${os}" = "Darwin" ]]; then sed -i '' "s/$PREVIOUS_VERSION/$LATEST_VERSION/g" $f sed -i '' "s/$CURR_CHART_VERSION/$UPDATED_CHART_VERSION/g" $f else echo -e "❌ ${RED}Platform ${os} does not support NTH release. Please use: Linux or macOS ${RESET_FMT}" exit 1 fi FILES_CHANGED+=("$f") fi done if [[ ${#FILES_CHANGED[@]} -eq 0 ]]; then echo -e "\nNo files were modified. Either all files already use git the latest release version $LATEST_VERSION or the files don't currently have the previous version $PREVIOUS_VERSION." exit 0 else echo -e "βœ…βœ… ${BOLD}Updated versions from $PREVIOUS_VERSION to $LATEST_VERSION in files: \n$(echo "${FILES_CHANGED[@]}" | tr ' ' '\n')" echo -e "To see changes, run \`git diff HEAD^ HEAD\`${RESET_FMT}" fi echo } commit_changes() { echo -e "\nπŸ₯‘ Adding and committing release version changes." git config user.name "$EC2_BOT_USER" git config user.email "$EC2_BOT_EMAIL" EC2_BOT_SET=true git add "${FILES_CHANGED[@]}" git commit -m "$COMMIT_MESSAGE" echo -e "βœ…βœ…βœ… ${BOLD}Committed release prep changes to new branch $NEW_BRANCH with commit message '$COMMIT_MESSAGE'\n\n${RESET_FMT}" } confirm_with_user_and_create_pr(){ git checkout $NEW_BRANCH # checkout new branch before printing git diff echo -e "\nπŸ₯‘${BOLD}The following PR will be created:\n" cat << EOM PR draft mode: $DRAFT PR target repository: $REPO_FULL_NAME PR source branch: $NEW_BRANCH PR target branch: $REPO_FULL_NAME/$PR_BASE PR title: $PR_TITLE PR body: $PR_BODY PR labels: $PR_LABEL_1, $PR_LABEL_2 Changes in $NEW_BRANCH: ${MAGENTA}$(git diff HEAD^ HEAD)${RESET_FMT} EOM # gh actions cannot respond to prompts if [[ $RELEASE_PREP == true ]]; then while true; do echo -e "πŸ₯‘${BOLD}Do you wish to create the release prep PR? Enter y/n" read -p "" yn case $yn in [Yy]* ) create_pr; break;; [Nn]* ) rollback; exit;; * ) echo "πŸ₯‘Please answer yes or no.";; esac done else create_pr fi echo "${RESET_FMT}" } create_pr() { git push -u origin $NEW_BRANCH # sets source branch for PR to NEW_BRANCH on the fork or origin git checkout $NEW_BRANCH # checkout new branch before creating a pr if [[ $DRAFT == true ]]; then gh pr create \ --repo "$REPO_FULL_NAME" \ --base "$PR_BASE" \ --title "$PR_TITLE" \ --body "$PR_BODY" \ --label "$PR_LABEL_1" --label "$PR_LABEL_2" \ --draft else gh pr create \ --repo "$REPO_FULL_NAME" \ --base "$PR_BASE" \ --title "$PR_TITLE" \ --body "$PR_BODY" \ --label "$PR_LABEL_1" --label "$PR_LABEL_2" fi if [[ $? == 0 ]]; then echo -e "βœ…βœ…βœ…βœ… ${BOLD}Created $LATEST_TAG release prep PR\n${RESET_FMT}" else echo -e "❌ ${RED}PR creation failed.${RESET_FMT}❌" exit 1 fi } # rollback partial changes to make this script atomic, iff the current execution of the script created a new branch and made changes rollback() { if [[ $NEED_ROLLBACK == true ]]; then echo "πŸ₯‘${BOLD}Rolling back" # checkout of current branch to main git checkout main # delete local and remote release branch only if current execution of the script created them git branch -D $NEW_BRANCH git push origin --delete $NEW_BRANCH # if multiple user.name are set, then only the latest(ec2-bot) will be removed if [[ $EC2_BOT_SET == true ]]; then echo "πŸ₯‘${BOLD}Rolling back ec2-bot git config" git config --unset user.name git config --unset user.email fi fi echo "${RESET_FMT}" } handle_errors() { # error handling if [ $1 != "0" ]; then FAILED_COMMAND=${*:2} echo -e "\n❌ ${RED}Error occurred while running command '$FAILED_COMMAND'.${RESET_FMT}❌" rollback exit 1 fi exit $1 } sync_local_tags_from_remote() { # setup remote upstream tracking to fetch tags git remote add the-real-upstream https://github.com/aws/aws-node-termination-handler.git &> /dev/null || true git fetch the-real-upstream # delete all local tags git tag -l | xargs git tag -d # fetch remote tags git fetch the-real-upstream --tags # clean up tracking git remote remove the-real-upstream } main() { process_args "$@" trap 'handle_errors $? $BASH_COMMAND' EXIT verify_origin_tracking sync_local_tags_from_remote # if previous and latest version are equal, then the previous previous release versions may be present if [[ $PREVIOUS_VERSION == "$LATEST_VERSION" ]]; then PREVIOUS_VERSION=$(git tag -l --sort=-v:refname | sed -n '2 p' | cut -b 2-) fi determine_increment $PREVIOUS_VERSION $LATEST_VERSION echo -e "πŸ₯‘ Attempting to create a release prep branch and PR with release version updates.\n Previous version: $PREVIOUS_VERSION ---> Latest version: $LATEST_VERSION" create_release_branch update_versions commit_changes confirm_with_user_and_create_pr } main "$@"