Skip to content

Instantly share code, notes, and snippets.

@matthewadams
Last active August 14, 2018 03:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matthewadams/a3eeecde6b4c4289af7520dde8dd0b6f to your computer and use it in GitHub Desktop.
Save matthewadams/a3eeecde6b4c4289af7520dde8dd0b6f to your computer and use it in GitHub Desktop.
release-helm-chart.sh
#!/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