Last active
August 14, 2018 03:24
-
-
Save matthewadams/a3eeecde6b4c4289af7520dde8dd0b6f to your computer and use it in GitHub Desktop.
release-helm-chart.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# This script implements the release branch workflow for helm charts. | |
# | |
# Requirements: | |
# git | |
# docker | |
# TODO: port this from bash to plain ol' sh (regex matching needs to be ported) | |
usage() { | |
cat<<EOF | |
usage: | |
if on master branch: release pre|rc [<chart>] | |
if on release branch: release major|minor|patch|pre [<chart>] | |
where: | |
<chart> is the name of the chart and its directory, unless it's "." | |
* if "." is given, chart name is assumed to be the name of the directory containing ".", otherwise | |
* the default value is the only dirname in this directory that doesn't start with ".", unless there are multiple | |
EOF | |
} | |
if [ -n "$TEST" ]; then ECHO=echo; fi | |
if [ -n "$DBG" ]; then set -x; fi | |
ORIGIN=${ORIGIN:-origin} | |
MASTER=${MASTER:-master} | |
RELEASE_LEVEL="$1" | |
case "$RELEASE_LEVEL" in | |
major|minor|patch|pre|rc) | |
# ok | |
;; | |
h|he|hel|help) | |
usage | |
exit 0 | |
;; | |
*) | |
echo "ERROR: Specify release level of 'pre', 'patch', 'minor', 'major', or 'rc'" >&2 | |
usage | |
exit 1 | |
;; | |
esac | |
CHART_NAME=${2%/} # drop trailing slash if it's there | |
if [ "$CHART_NAME" == '.' ]; then | |
CHART_NAME="$(basename $(pwd))" | |
CHART_NAME="${CHART_NAME%/}" | |
echo "INFO: using $CHART_NAME as chart name" | |
CHART_DIR=. | |
CHART_FILE="${CHART_FILE:-$CHART_DIR/Chart.yaml}" | |
elif [ -z "$CHART_NAME" ]; then | |
NON_DOT_DIRS="$(find . -type d -depth 1 | sed 's|^\./||g' | grep -v '^\.')" # directories that don't start with '.' | |
if [ $(echo "$NON_DOT_DIRS" | wc -l | xargs) == 1 ]; then # we'll assume this sole dir is chart dir | |
CHART_NAME="${NON_DOT_DIRS%/}" | |
echo "INFO: using $CHART_NAME as chart name" | |
else | |
echo "ERROR: no chart name given & can't guess chart name from child directories in this directory" | |
usage | |
exit 1 | |
fi | |
CHART_DIR="${CHART_DIR:-$CHART_NAME}" | |
CHART_DIR="${CHART_DIR%/}" | |
CHART_FILE="${CHART_FILE:-$CHART_DIR/Chart.yaml}" | |
fi | |
git pull > /dev/null 2>&1 | |
if ! git diff --exit-code --no-patch > /dev/null 2>&1; then | |
echo 'ERROR: You have modified tracked files; only release from clean directories!' >&2 | |
exit 3 | |
fi | |
if ! git diff --cached --exit-code --no-patch > /dev/null 2>&1; then | |
echo 'ERROR: You have cached modified tracked files; only release from clean directories!' >&2 | |
exit 3 | |
fi | |
if [ -n "$(git status -s)" ]; then | |
echo 'ERROR: You have unignored untracked files; only release from clean directories!' >&2 | |
exit 3 | |
fi | |
VERSION="$(cat $CHART_FILE | docker run --rm -i matthewadams12/ymlx this.version)" | |
if [[ ! "$VERSION" =~ \-(pre|rc)\.[0-9]{1,}$ ]]; then | |
echo 'ERROR: repository is in an inconsistent state: version does NOT end in a prerelease suffix!' >&2 | |
exit 3 | |
fi | |
BRANCH="$(git status | head -n 1 | awk '{ print $3 }')" | |
if [[ ! "$BRANCH" =~ ^(master|v[0-9]{1,}\.[0-9]{1,})$ ]]; then # it is not a master or a release branch | |
echo 'ERROR: You can only release from the master branch or release branches (vmajor.minor)!' >&2 | |
exit 3 | |
fi | |
if ! git diff --exit-code -no-patch $BRANCH $ORIGIN/$BRANCH > /dev/null 2>&1; then | |
echo "ERROR: Local branch $BRANCH differs from remote branch $ORIGIN/$BRANCH" >&2 | |
exit 3 | |
fi | |
if [ "$BRANCH" == "$MASTER" ]; then | |
case "$RELEASE_LEVEL" in | |
pre|rc) | |
# ok | |
;; | |
*) | |
echo "ERROR: Only 'pre' or 'rc' releases are permitted from the $MASTER branch." >&2 | |
exit 6 | |
;; | |
esac | |
else # this is a release branch | |
case "$RELEASE_LEVEL" in | |
rc|patch|minor|major) | |
# ok | |
;; | |
*) | |
echo "ERROR: Only 'pre', 'patch', 'minor', or 'major' releases are permitted from a release branch." >&2 | |
exit 7 | |
;; | |
esac | |
fi | |
if [ "$BRANCH" == "$MASTER" ]; then | |
if [[ ! "$VERSION" =~ ^([0-9]{1,})\.([0-9]{1,})\.0\-pre\.([0-9]{1,})$ ]]; then | |
echo "ERROR: The version does not match the format of major.minor.0-pre.n required in the $MASTER branch." >&2 | |
exit 8 | |
fi | |
# create release branch | |
MAJOR=${BASH_REMATCH[1]} | |
MINOR=${BASH_REMATCH[2]} | |
PATCH=0 | |
PRE=${BASH_REMATCH[3]} | |
case "$RELEASE_LEVEL" in | |
rc) # then it's time to create a new release branch | |
NEW_RELEASE_BRANCH="v$MAJOR.$MINOR" | |
git checkout -b $NEW_RELEASE_BRANCH > /dev/null 2>&1 | |
NEW_RELEASE_BRANCH_VERSION="$MAJOR.$MINOR.0-rc.0" | |
CHART_CONTENT="$(cat $CHART_FILE)" | |
echo -n "$CHART_CONTENT" \ | |
| docker run --rm -i matthewadams12/ymlx "it => { it.version = '$NEW_RELEASE_BRANCH_VERSION'; return it; }" \ | |
> $CHART_FILE | |
git add . > /dev/null 2>&1 | |
git commit -m "release $NEW_RELEASE_BRANCH_VERSION" > /dev/null 2>&1 | |
git tag "v$NEW_RELEASE_BRANCH_VERSION" > /dev/null 2>&1 | |
git push -u $ORIGIN $NEW_RELEASE_BRANCH > /dev/null 2>&1 | |
git push --tags > /dev/null 2>&1 | |
# return to master branch | |
git checkout $MASTER > /dev/null 2>&1 | |
git cherry-pick $NEW_RELEASE_BRANCH > /dev/null 2>&1 # cherry pick from release branch to get release candidate commit in master | |
# advance master version | |
NEXT_VERSION="$MAJOR.$(($MINOR+1)).0-pre.0" | |
CHART_CONTENT="$(cat $CHART_FILE)" | |
echo -n "$CHART_CONTENT" \ | |
| docker run --rm -i matthewadams12/ymlx "it => { it.version = '$NEXT_VERSION'; return it; }" \ | |
> $CHART_FILE | |
git add . > /dev/null 2>&1 | |
git commit -m "bump to $NEXT_VERSION [skip ci]" > /dev/null 2>&1 | |
git push > /dev/null 2>&1 | |
# return to release branch & prepare for next prerelease | |
git checkout $NEW_RELEASE_BRANCH > /dev/null 2>&1 | |
NEXT_RELEASE_BRANCH_VERSION="$MAJOR.$MINOR.0-rc.1" | |
CHART_CONTENT="$(cat $CHART_FILE)" | |
echo -n "$CHART_CONTENT" \ | |
| docker run --rm -i matthewadams12/ymlx "it => { it.version = '$NEXT_RELEASE_BRANCH_VERSION'; return it; }" \ | |
> $CHART_FILE | |
git add . > /dev/null 2>&1 | |
git commit -m "bump to $NEXT_RELEASE_BRANCH_VERSION [skip ci]" > /dev/null 2>&1 | |
git push > /dev/null 2>&1 | |
echo "created release branch $NEW_RELEASE_BRANCH and tagged $NEW_RELEASE_BRANCH_VERSION for release" | |
exit 0 | |
;; | |
pre) | |
if git log -1 --pretty=%s | grep -q '\[skip ci\]'; then | |
# force empty commit so that tag doesn't point at a "[skip ci]" commit | |
git commit --allow-empty -m 'trigger ci' > /dev/null 2>&1 | |
fi | |
git tag "v$VERSION" > /dev/null 2>&1 | |
RELEASED_VERSION="$VERSION" | |
NEXT_VERSION=$MAJOR.$MINOR.$PATCH-pre.$((PRE+1)) | |
CHART_CONTENT="$(cat $CHART_FILE)" | |
echo -n "$CHART_CONTENT" \ | |
| docker run --rm -i matthewadams12/ymlx "it => { it.version = '$NEXT_VERSION'; return it; }" \ | |
> $CHART_FILE | |
git add . > /dev/null 2>&1 | |
git commit -m "bump to $NEXT_VERSION [skip ci]" > /dev/null 2>&1 | |
git push > /dev/null 2>&1 | |
git push --tags > /dev/null 2>&1 | |
echo "tagged $RELEASED_VERSION for release" | |
exit 0 | |
;; | |
esac | |
fi | |
# If we get this far, we are releasing something from a release branch. | |
if [[ ! "$VERSION" =~ ^([0-9]{1,})\.([0-9]{1,})\.([0-9]{1,})\-rc\.([0-9]{1,})$ ]]; then | |
echo "ERROR: The version does not match the format of major.minor.patch-rc.n required in the release branch." >&2 | |
exit 8 | |
fi | |
MAJOR=${BASH_REMATCH[1]} | |
MINOR=${BASH_REMATCH[2]} | |
PATCH=${BASH_REMATCH[3]} | |
PRE=${BASH_REMATCH[4]} | |
case "$RELEASE_LEVEL" in | |
major|minor|patch) | |
# NOTE: if RELEASE_LEVEL is 'minor' & we're prepped for a major release, no harm, no foul. | |
# A major release is the same as a minor release, only that the minor version is 0. | |
if [ $RELEASE_LEVEL == major ] && [ $MINOR != 0 ]; then | |
echo "ERROR: This branch is not prepared for a major release because the minor version is $MINOR, not 0." >&2 | |
exit 10 | |
else | |
NEXT_RELEASE_BRANCH_VERSION="$MAJOR.$MINOR.1-rc.0" | |
fi | |
if [ $RELEASE_LEVEL == minor ] && [ $PATCH != 0 ]; then | |
echo "ERROR: A minor release has already been performed in this release branch; only patch releases are allowed here now." >&2 | |
exit 11 | |
else | |
NEXT_RELEASE_BRANCH_VERSION="$MAJOR.$MINOR.1-rc.0" | |
fi | |
if [ $RELEASE_LEVEL == patch ] && [ $PATCH == 0 ]; then | |
echo "ERROR: You must release a minor release before releasing a patch in this release branch." >&2 | |
exit 12 | |
else | |
NEXT_RELEASE_BRANCH_VERSION="$MAJOR.$MINOR.$((PATCH+1))-rc.0" | |
fi | |
RELEASE_VERSION="$MAJOR.$MINOR.$PATCH" | |
CHART_CONTENT="$(cat $CHART_FILE)" | |
echo -n "$CHART_CONTENT" \ | |
| docker run --rm -i matthewadams12/ymlx "it => { it.version = '$RELEASE_VERSION'; return it; }" \ | |
> $CHART_FILE | |
git add . > /dev/null 2>&1 | |
git commit -m "release $RELEASE_VERSION" > /dev/null 2>&1 | |
git tag "v$RELEASE_VERSION" > /dev/null 2>&1 | |
git push > /dev/null 2>&1 | |
git push --tags > /dev/null 2>&1 | |
CHART_CONTENT="$(cat $CHART_FILE)" | |
echo -n "$CHART_CONTENT" \ | |
| docker run --rm -i matthewadams12/ymlx "it => { it.version = '$NEXT_RELEASE_BRANCH_VERSION'; return it; }" \ | |
> $CHART_FILE | |
git add . > /dev/null 2>&1 | |
git commit -m "bump to $NEXT_RELEASE_BRANCH_VERSION [skip ci]" > /dev/null 2>&1 | |
git push > /dev/null 2>&1 | |
echo "tagged $RELEASE_VERSION" | |
exit 0 | |
;; | |
rc) | |
if git log -1 --pretty=%s | grep -q '\[skip ci\]'; then | |
# force empty commit so that tag doesn't point at a "[skip ci]" commit | |
git commit --allow-empty -m 'trigger ci' > /dev/null 2>&1 | |
fi | |
git tag "v$VERSION" > /dev/null 2>&1 | |
NEXT_RELEASE_BRANCH_VERSION="$MAJOR.$MINOR.$PATCH-rc.$((PRE+1))" | |
CHART_CONTENT="$(cat $CHART_FILE)" | |
echo -n "$CHART_CONTENT" \ | |
| docker run --rm -i matthewadams12/ymlx "it => { it.version = '$NEXT_RELEASE_BRANCH_VERSION'; return it; }" \ | |
> $CHART_FILE | |
git add . > /dev/null 2>&1 | |
git commit -m "bump to $NEXT_RELEASE_BRANCH_VERSION [skip ci]" > /dev/null 2>&1 | |
git push > /dev/null 2>&1 | |
git push --tags > /dev/null 2>&1 | |
echo "tagged v$VERSION" | |
exit 0 | |
;; | |
esac |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment