Files
dokku/contrib/release

361 lines
12 KiB
Bash
Executable File

#!/usr/bin/env bash
set -eo pipefail
[[ $TRACE ]] && set -x
readonly ROOT_DIR="$(cd "$(dirname "$(dirname "${BASH_SOURCE[0]}")")" && pwd)"
readonly TMP_WORK_DIR="$(mktemp -d "/tmp/dokku-release.XXXX")"
readonly DOKKU_GIT_REV="$(git rev-parse HEAD)"
trap 'rm -rf "$TMP_WORK_DIR" > /dev/null' RETURN INT TERM EXIT
log-info() {
# shellcheck disable=SC2034
declare desc="Log info formatter"
echo "$*"
}
log-error() {
# shellcheck disable=SC2034
declare desc="Log error formatter"
echo "! $*" 1>&2
}
log-fail() {
# shellcheck disable=SC2034
declare desc="Log fail formatter"
log-error "$*"
exit -1
}
fn-build-dokku() {
declare desc="Builds dokku packages within a docker container"
declare IS_RELEASE="$1" DOKKU_VERSION="$2"
local NAME GOLANG_VERSION
if [[ -z "$DOKKU_VERSION" ]]; then
log-error "Invalid deb file specified"
return 1
fi
NAME="dokku:build"
[[ "$IS_RELEASE" == "true" ]] && NAME="dokku:build-release"
[[ -n "$DOKKU_VERSION" ]] || DOKKU_VERSION="master"
pushd "$ROOT_DIR" >/dev/null
GOLANG_VERSION="$(grep BUILD_IMAGE common.mk | cut -d' ' -f3 | cut -d':' -f2)"
docker build \
--build-arg DOKKU_VERSION="$DOKKU_VERSION" \
--build-arg DOKKU_GIT_REV="$DOKKU_GIT_REV" \
--build-arg IS_RELEASE="$IS_RELEASE" \
--build-arg PLUGIN_MAKE_TARGET="build" \
--build-arg GOLANG_VERSION="$GOLANG_VERSION" \
-f "contrib/build.Dockerfile" \
-t "$NAME" .
return "$?"
}
fn-extract-package() {
declare desc="Extract packages from a docker container to the root directory"
declare IS_RELEASE="$1" PACKAGE_NAME="$2"
local NAME
if [[ -z "$PACKAGE_NAME" ]]; then
log-error "Invalid deb file specified"
return 1
fi
NAME="dokku:build"
[[ "$IS_RELEASE" == "true" ]] && NAME="dokku:build-release"
log-info "(extract-package) writing ${PACKAGE_NAME} to correct path"
docker run --rm --entrypoint cat "$NAME" "/data/${PACKAGE_NAME}" > "${ROOT_DIR}/${PACKAGE_NAME}"
return "$?"
}
fn-in-array() {
declare desc="return true if value ($1) is in list (all other arguments)"
local e
for e in "${@:2}"; do
[[ "$e" == "$1" ]] && return 0
done
return 1
}
fn-is-release() {
declare desc="Checks if a given run is a release run"
declare RELEASE="$1"
local IS_RELEASE=false
if [[ "$RELEASE" == "major" ]] || [[ "$RELEASE" == "minor" ]] || [[ "$RELEASE" == "patch" ]]; then
IS_RELEASE=true
fi
echo "$IS_RELEASE"
}
fn-version-current() {
declare desc="Retrieves the current version of dokku"
pushd "$ROOT_DIR" >/dev/null
grep Version debian/control | cut -d' ' -f2
}
fn-version-next() {
declare desc="Calculates the next version name of dokku"
declare CURRENT_VERSION="$1" RELEASE="$2"
local major minor patch short_object
major=$(echo "$CURRENT_VERSION" | awk '{split($0,a,"."); print a[1]}')
minor=$(echo "$CURRENT_VERSION" | awk '{split($0,a,"."); print a[2]}')
patch=$(echo "$CURRENT_VERSION" | awk '{split($0,a,"."); print a[3]}')
if [[ "$RELEASE" == "major" ]]; then
major=$((major + 1))
minor="0"
patch="0"
elif [[ "$RELEASE" == "minor" ]]; then
minor=$((minor + 1))
patch="0"
elif [[ "$RELEASE" == "patch" ]]; then
patch=$((patch + 1))
else
short_object=$(git log --oneline | tail -n 1 | cut -d' ' -f1)
echo "${CURRENT_VERSION}build+$(echo "$DOKKU_GIT_REV" | cut -c1-${#short_object})"
return 0
fi
echo "${major}.${minor}.${patch}"
}
fn-publish-package() {
declare desc="Publishes a package to packagecloud"
declare IS_RELEASE="$1" RELEASE_TYPE="$2" PACKAGE_NAME="$3"
local REPOSITORY=dokku/dokku-betafish DIST=ubuntu EXIT_CODE=0
local OS_ID ex
[[ "$IS_RELEASE" == "true" ]] && REPOSITORY=dokku/dokku
[[ "$RELEASE_TYPE" == "rpm" ]] && DIST=el/7
if [[ "$DIST" == "ubuntu" ]]; then
OS_IDS=("trusty" "utopic" "vivid" "wily" "xenial" "yakkety" "zesty" "artful" "bionic")
for OS_ID in "${OS_IDS[@]}"; do
log-info "(release-dokku) pushing ${RELEASE_TYPE} to packagecloud.com/${REPOSITORY}/${DIST}"
package_cloud push "${REPOSITORY}/${DIST}/${OS_ID}" "$PACKAGE_NAME"
ex="$?"
if [[ "$ex" -ne "0" ]]; then
EXIT_CODE="$ex"
fi
done
DIST=debian
OS_IDS=("wheezy" "jessie" "stretch" "buster")
for OS_ID in "${OS_IDS[@]}"; do
log-info "(release-dokku) pushing ${RELEASE_TYPE} to packagecloud.com/${REPOSITORY}/${DIST}"
package_cloud push "${REPOSITORY}/${DIST}/${OS_ID}" "$PACKAGE_NAME"
ex="$?"
if [[ "$ex" -ne "0" ]]; then
EXIT_CODE="$ex"
fi
done
else
log-info "(release-dokku) pushing ${RELEASE_TYPE} to packagecloud.com/${REPOSITORY}/${DIST}"
package_cloud push "${REPOSITORY}/${DIST}" "$PACKAGE_NAME"
EXIT_CODE="$?"
fi
return "$EXIT_CODE"
}
fn-replace-version() {
declare desc="Replaces the version within a file"
local CURRENT_VERSION="$1"
local NEXT_VERSION="$2"
local FILENAME="$3"
sed -i.bak "s/${CURRENT_VERSION//./\.}/$NEXT_VERSION/g" "$FILENAME" && rm "$FILENAME.bak"
git add "$FILENAME"
}
fn-repo-merged-requests() {
declare desc="Retrieves all pull request ids since a given release of dokku"
declare TAG="$1"
git log "v${TAG}..HEAD" --oneline | grep "Merge pull request" | cut -d' ' -f 5 | cut -d '#' -f2
}
fn-repo-push-tags() {
declare desc="Pushes tags and the repository to github"
declare IS_RELEASE="$1"
if [[ "$IS_RELEASE" == "false" ]]; then
return
fi
pushd "$ROOT_DIR" >/dev/null
git push --tags origin master
}
fn-repo-update() {
declare desc="Updates files within the repository for a given release"
declare IS_RELEASE="$1" CURRENT_VERSION="$2" NEXT_VERSION="$3"
log-info "(release-dokku) Replacing ${CURRENT_VERSION} with ${NEXT_VERSION}"
for f in plugins/*/plugin.toml; do
fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" "$f"
done
if [[ "$IS_RELEASE" == "false" ]]; then
return
fi
fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" contrib/dokku-installer.py
fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" debian/control
fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/advanced-usage/plugin-management.md
fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/assets/favicons/browserconfig.xml
fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/assets/favicons/manifest.json
fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/assets/style.css
fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/development/release-process.md
fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/home.html
fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/getting-started/installation.md
fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/template.html
fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" README.md
if [[ "$RELEASE" == 'patch' ]]; then
fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/assets/versions.json
else
versions=$(python -c 'import json,sys;d=json.load(sys.stdin);d["max-versions"].append("'"${NEXT_VERSION}"'"); print json.dumps(d, indent=2, sort_keys=True)' <docs/assets/versions.json)
echo "$versions" >docs/assets/versions.json
git add docs/assets/versions.json
fi
if [[ "$IS_RELEASE" == "false" ]]; then
return
fi
log-info "(release-dokku) Adding HISTORY.md, committing and tagging"
fn-repo-update-history-and-commit "$CURRENT_VERSION" "$NEXT_VERSION"
git tag "v${NEXT_VERSION}"
}
fn-repo-update-history-and-commit() {
declare desc="Updates the history file and commits the changes"
declare CURRENT_VERSION="$1" NEXT_VERSION="$2"
local PULL_REQUEST_ID TITLE AUTHOR TYPE CHANGELOG_TEXT
local COMMIT_MESSAGE HISTORY HISTORY_CONTENTS HISTORY_DOCUMENTATION HISTORY_ENHANCEMENT HISTORY_BUG HISTORY_OTHER ISSUE_FILE
pushd "$ROOT_DIR" >/dev/null
mkdir -p /tmp/github-data
for PULL_REQUEST_ID in $(fn-repo-merged-requests "$CURRENT_VERSION"); do
ISSUE_FILE="/tmp/github-data/${PULL_REQUEST_ID}.json"
if [[ ! -f "$ISSUE_FILE" ]]; then
curl -sS "https://api.github.com/repos/dokku/dokku/issues/${PULL_REQUEST_ID}" >"$ISSUE_FILE"
fi
TITLE="$(jq -r '.title' "$ISSUE_FILE")"
AUTHOR="$(jq -r '.user.login' "$ISSUE_FILE")"
TYPE="$(jq -r '.labels[] | select( .name | contains("type") ) | .name' "$ISSUE_FILE" | cut -d' ' -f2)"
CHANGELOG_TEXT="- #${PULL_REQUEST_ID}: @${AUTHOR} ${TITLE}"
if [[ "$TYPE" == "bug" ]]; then
HISTORY_BUG="${HISTORY_BUG}"$'\n'"$CHANGELOG_TEXT"
elif [[ "$TYPE" == "documentation" ]]; then
HISTORY_DOCUMENTATION="${HISTORY_DOCUMENTATION}"$'\n'"$CHANGELOG_TEXT"
elif [[ "$TYPE" == "enhancement" ]] || [[ "$TYPE" == "refactor" ]]; then
HISTORY_ENHANCEMENT="${HISTORY_ENHANCEMENT}"$'\n'"$CHANGELOG_TEXT"
else
HISTORY_OTHER="${HISTORY_OTHER}"$'\n'"$CHANGELOG_TEXT"
fi
done
HISTORY="# History"
HISTORY="${HISTORY}"$'\n\n'"## ${NEXT_VERSION}"
HISTORY="${HISTORY}"$'\n\n'"Install/update via the bootstrap script:"
HISTORY="${HISTORY}"$'\n\n'"\`\`\`shell"
HISTORY="${HISTORY}"$'\n'"wget https://raw.githubusercontent.com/dokku/dokku/v${NEXT_VERSION}/bootstrap.sh"
HISTORY="${HISTORY}"$'\n'"sudo DOKKU_TAG=v${NEXT_VERSION} bash bootstrap.sh"
HISTORY="${HISTORY}"$'\n'"\`\`\`"
if [[ "$HISTORY_BUG" ]]; then
HISTORY="${HISTORY}"$'\n\n'"### Bug Fixes"
HISTORY="${HISTORY}"$'\n'"$HISTORY_BUG"
fi
if [[ "$HISTORY_ENHANCEMENT" ]]; then
HISTORY="${HISTORY}"$'\n\n'"### New Features"
HISTORY="${HISTORY}"$'\n'"$HISTORY_ENHANCEMENT"
fi
if [[ "$HISTORY_DOCUMENTATION" ]]; then
HISTORY="${HISTORY}"$'\n\n'"### Documentation"
HISTORY="${HISTORY}"$'\n'"$HISTORY_DOCUMENTATION"
fi
if [[ "$HISTORY_OTHER" ]]; then
HISTORY="${HISTORY}"$'\n\n'"### Other"
HISTORY="${HISTORY}"$'\n'"$HISTORY_OTHER"
fi
sed -i.bak '1d' HISTORY.md && rm HISTORY.md.bak
HISTORY_CONTENTS="$(cat HISTORY.md)"
echo -e "$HISTORY\n$HISTORY_CONTENTS" >HISTORY.md
git add HISTORY.md
COMMIT_MESSAGE="Release ${NEXT_VERSION}"$'\n\n'"${HISTORY}"
git commit -m "${COMMIT_MESSAGE}"
}
fn-require-bin() {
declare desc="Checks that a binary exists"
declare BINARY="$1"
if ! command -v "$BINARY" &>/dev/null; then
log-fail "Missing ${BINARY}, please install it"
fi
}
main() {
declare RELEASE="$1"
local CURRENT_VERSION NEXT_VERSION IS_RELEASE
local VALID_RELEASE_LEVELS=("major" "minor" "patch" "betafish")
if [[ "$RELEASE" == '--trace' ]]; then
shift 1
RELEASE="$1"
TRACE=1 && set -x
fi
if [[ -z "$RELEASE" ]]; then
log-fail "Argument 1 must be one of [major, minor, patch, betafish], none given"
fi
if ! fn-in-array "$RELEASE" "${VALID_RELEASE_LEVELS[@]}"; then
log-fail "Argument 1 must be one of [major, minor, patch, betafish], '${RELEASE}' given"
fi
fn-require-bin "docker"
fn-require-bin "git"
fn-require-bin "package_cloud"
[[ -n "$PACKAGECLOUD_API_TOKEN" ]] || log-fail "Missing PACKAGECLOUD_API_TOKEN environment variable"
CURRENT_VERSION="$(fn-version-current)"
NEXT_VERSION="$(fn-version-next "$CURRENT_VERSION" "$RELEASE")"
IS_RELEASE="$(fn-is-release "$RELEASE")"
fn-repo-update "$IS_RELEASE" "$CURRENT_VERSION" "$NEXT_VERSION"
fn-build-dokku "$IS_RELEASE" "$NEXT_VERSION" || log-fail "Error building package"
fn-extract-package "$IS_RELEASE" "dokku_${NEXT_VERSION}_amd64.deb" || log-fail "Error extracting deb package"
fn-extract-package "$IS_RELEASE" "dokku-${NEXT_VERSION}-1.x86_64.rpm" || log-fail "Error extracting rpm package"
fn-publish-package "$IS_RELEASE" "deb" "dokku_${NEXT_VERSION}_amd64.deb" || log-fail "Error publishing deb package"
fn-publish-package "$IS_RELEASE" "rpm" "dokku-${NEXT_VERSION}-1.x86_64.rpm" || log-fail "Error publishing rpm package"
fn-repo-push-tags "$IS_RELEASE"
if [[ "$IS_RELEASE" != "true" ]]; then
git reset -q HEAD plugins/*/plugin.toml
git checkout -- plugins/*/plugin.toml
fi
}
main "$@"