mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00

The bucket is already fully public so new releases don't need to run this command.
This will prevent significant terminal spam during the release process as there are thousands of objects in the bucket.
Backports f0246a9f84
Signed-off-by: James Blair <mail@jamesblair.net>
334 lines
12 KiB
Bash
Executable File
334 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -o errexit
|
|
set -o nounset
|
|
set -o pipefail
|
|
|
|
source ./scripts/test_lib.sh
|
|
|
|
DRY_RUN=${DRY_RUN:-true}
|
|
|
|
help() {
|
|
echo "$(basename "$0") [version]"
|
|
echo "Release etcd using the same approach as the etcd-release-runbook (https://goo.gl/Gxwysq)"
|
|
echo ""
|
|
echo "WARNING: This does not perform the 'Add API capabilities', 'Performance testing' "
|
|
echo " or 'Documentation' steps. These steps must be performed manually BEFORE running this tool."
|
|
echo ""
|
|
echo "WARNING: This script does not sign releases, publish releases to github or sent announcement"
|
|
echo " emails. These steps must be performed manually AFTER running this tool."
|
|
echo ""
|
|
echo " args:"
|
|
echo " version: version of etcd to release, e.g. '3.2.18'"
|
|
echo " flags:"
|
|
echo " --no-upload: skip gs://etcd binary artifact uploads."
|
|
echo " --no-docker-push: skip docker image pushes."
|
|
echo " --in-place: build binaries using current branch."
|
|
echo ""
|
|
}
|
|
|
|
main() {
|
|
VERSION=$1
|
|
if [[ ! "${VERSION}" =~ [0-9]+.[0-9]+.[0-9]+ ]]; then
|
|
echo "Expected 'version' param of the form '<major-version>.<minor-version>.<patch-version>' but got '${VERSION}'"
|
|
exit 1
|
|
fi
|
|
RELEASE_VERSION="v${VERSION}"
|
|
MINOR_VERSION=$(echo "${VERSION}" | cut -d. -f 1-2)
|
|
|
|
if [ "${IN_PLACE}" == 1 ]; then
|
|
# Trigger release in current branch
|
|
REPOSITORY=$(pwd)
|
|
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
else
|
|
REPOSITORY=${REPOSITORY:-"https://github.com/etcd-io/etcd.git"}
|
|
BRANCH=${BRANCH:-"release-${MINOR_VERSION}"}
|
|
fi
|
|
|
|
log_warning "DRY_RUN=${DRY_RUN}"
|
|
log_callout "RELEASE_VERSION=${RELEASE_VERSION}"
|
|
log_callout "MINOR_VERSION=${MINOR_VERSION}"
|
|
log_callout "BRANCH=${BRANCH}"
|
|
log_callout ""
|
|
|
|
if ! command -v docker >/dev/null; then
|
|
log_error "cannot find docker"
|
|
exit 1
|
|
fi
|
|
|
|
# Expected umask for etcd release artifacts
|
|
umask 022
|
|
|
|
# Set up release directory.
|
|
local reldir="/tmp/etcd-release-${VERSION}"
|
|
log_callout "Preparing temporary directory: ${reldir}"
|
|
if [ ! -d "${reldir}/etcd" ] && [ "${IN_PLACE}" == 0 ]; then
|
|
mkdir -p "${reldir}"
|
|
cd "${reldir}"
|
|
git clone "${REPOSITORY}" --branch "${BRANCH}" --depth 1
|
|
cd "${reldir}/etcd" || exit 2
|
|
git checkout "${BRANCH}" || exit 2
|
|
git pull origin
|
|
fi
|
|
|
|
# If a release version tag already exists, use it.
|
|
log_callout "Checking tag: ${RELEASE_VERSION}"
|
|
local remote_tag_exists
|
|
remote_tag_exists=$(git ls-remote origin "refs/tags/${RELEASE_VERSION}" | grep -c "${RELEASE_VERSION}" || true)
|
|
if [ "${remote_tag_exists}" -gt 0 ]; then
|
|
log_callout "Release version tag exists on remote. Checking out refs/tags/${RELEASE_VERSION}"
|
|
git checkout -q "tags/${RELEASE_VERSION}"
|
|
fi
|
|
|
|
# Check go version.
|
|
log_callout "Checking go version"
|
|
local go_version current_go_version
|
|
go_version="go$(cat .go-version)"
|
|
current_go_version=$(go version | awk '{ print $3 }')
|
|
if [[ "${current_go_version}" != "${go_version}" ]]; then
|
|
log_error "Current go version is ${current_go_version}, but etcd ${RELEASE_VERSION} requires ${go_version} (see .go-version)."
|
|
exit 1
|
|
fi
|
|
|
|
# If the release tag does not already exist remotely, create it.
|
|
log_callout "Create tag if not present"
|
|
if [ "${remote_tag_exists}" -eq 0 ]; then
|
|
# Bump version/version.go to release version.
|
|
local source_version
|
|
source_version=$(grep -E "\s+Version\s*=" version/version.go | sed -e "s/.*\"\(.*\)\".*/\1/g")
|
|
if [[ "${source_version}" != "${VERSION}" ]]; then
|
|
source_minor_version=$(echo "${source_version}" | cut -d. -f 1-2)
|
|
if [[ "${source_minor_version}" != "${MINOR_VERSION}" ]]; then
|
|
log_error "Wrong etcd minor version in version/version.go. Expected ${MINOR_VERSION} but got ${source_minor_version}. Aborting."
|
|
exit 1
|
|
fi
|
|
echo "Updating version from ${source_version} to ${VERSION} in version/version.go"
|
|
sed -i "s/${source_version}/${VERSION}/g" version/version.go
|
|
fi
|
|
|
|
log_callout "Building etcd and checking --version output"
|
|
./build
|
|
local etcd_version
|
|
etcd_version=$(bin/etcd --version | grep "etcd Version" | awk '{ print $3 }')
|
|
if [[ "${etcd_version}" != "${VERSION}" ]]; then
|
|
log_error "Wrong etcd version in version/version.go. Expected ${etcd_version} but got ${VERSION}. Aborting."
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -n $(git status -s) ]]; then
|
|
log_callout "Committing version/version.go update."
|
|
git add version/version.go
|
|
git diff --staged | cat
|
|
git commit --signoff -m "version: bump up to ${VERSION}"
|
|
git diff --staged | cat
|
|
fi
|
|
|
|
# Push the version change if it's not already been pushed.
|
|
if [ "$DRY_RUN" != "true" ] && [ "$(git rev-list --count "origin/${BRANCH}..${BRANCH}")" -gt 0 ]; then
|
|
read -p "Push version bump up to ${VERSION} to github.com/etcd-io/etcd [y/N]? " -r confirm
|
|
[[ "${confirm,,}" == "y" ]] || exit 1
|
|
git push
|
|
fi
|
|
|
|
# Tag release.
|
|
if [ "$(git tag --list | grep -c "${RELEASE_VERSION}")" -gt 0 ]; then
|
|
log_callout "Skipping tag step. git tag ${RELEASE_VERSION} already exists."
|
|
else
|
|
log_callout "Tagging release..."
|
|
gitemail=$(git config --get user.email)
|
|
KEYID=$(gpg --list-keys --with-colons "${gitemail}" | awk -F: '/^pub:/ { print $5 }')
|
|
if [[ -z "${KEYID}" ]]; then
|
|
log_error "Failed to load gpg key. Is gpg set up correctly for etcd releases?"
|
|
exit 1
|
|
fi
|
|
git tag --local-user "${KEYID}" --sign "${RELEASE_VERSION}" --message "${RELEASE_VERSION}"
|
|
fi
|
|
|
|
# Verify the latest commit has the version tag
|
|
# shellcheck disable=SC2155
|
|
local tag="$(git describe --exact-match HEAD)"
|
|
if [ "${tag}" != "${RELEASE_VERSION}" ]; then
|
|
log_error "Error: Expected HEAD to be tagged with ${RELEASE_VERSION}, but 'git describe --exact-match HEAD' reported: ${tag}"
|
|
exit 1
|
|
fi
|
|
|
|
if [ "${IN_PLACE}" == 0 ]; then
|
|
# Tried with `local branch=$(git branch -a --contains tags/"${RELEASE_VERSION}")`
|
|
# so as to work with both current branch and main/release-3.X.
|
|
# But got error below on current branch mode,
|
|
# Error: Git tag v3.6.99 should be on branch '* (HEAD detached at pull/14860/merge)' but is on '* (HEAD detached from pull/14860/merge)'
|
|
#
|
|
# Verify the version tag is on the right branch
|
|
# shellcheck disable=SC2155
|
|
local branch=$(git for-each-ref --contains "${RELEASE_VERSION}" --format="%(refname)" 'refs/heads' | cut -d '/' -f 3)
|
|
if [ "${branch}" != "${BRANCH}" ]; then
|
|
log_error "Error: Git tag ${RELEASE_VERSION} should be on branch '${BRANCH}' but is on '${branch}'"
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
log_callout "Verify the latest commit has the version tag"
|
|
# Verify the latest commit has the version tag
|
|
# shellcheck disable=SC2155
|
|
local tag="$(git describe --exact-match HEAD)"
|
|
if [ "${tag}" != "${RELEASE_VERSION}" ]; then
|
|
log_error "Error: Expected HEAD to be tagged with ${RELEASE_VERSION}, but 'git describe --exact-match HEAD' reported: ${tag}"
|
|
exit 1
|
|
fi
|
|
|
|
log_callout "Verify the work space is clean"
|
|
# Verify the clean working tree
|
|
# shellcheck disable=SC2155
|
|
local diff="$(git diff HEAD --stat)"
|
|
if [[ "${diff}" != '' ]]; then
|
|
log_error "Error: Expected clean working tree, but 'git diff --stat' reported: ${diff}"
|
|
exit 1
|
|
fi
|
|
|
|
|
|
# Build release.
|
|
# TODO: check the release directory for all required build artifacts.
|
|
if [ -d release ]; then
|
|
log_warning "Skipping release build step. /release directory already exists."
|
|
else
|
|
log_callout "Building release..."
|
|
# Check for old and new names of the release build script.
|
|
# TODO: Move the release script into this on as a function?
|
|
REPOSITORY=$(pwd) ./scripts/build-release.sh "${RELEASE_VERSION}"
|
|
fi
|
|
|
|
# Sanity checks.
|
|
"./release/etcd-${RELEASE_VERSION}-$(go env GOOS)-amd64/etcd" --version | grep -q "etcd Version: ${VERSION}" || true
|
|
"./release/etcd-${RELEASE_VERSION}-$(go env GOOS)-amd64/etcdctl" version | grep -q "etcdctl version: ${VERSION}" || true
|
|
|
|
# Generate SHA256SUMS
|
|
log_callout "Generating sha256sums of release artifacts."
|
|
pushd ./release
|
|
# shellcheck disable=SC2010
|
|
ls . | grep -E '\.tar.gz$|\.zip$' | xargs shasum -a 256 > ./SHA256SUMS
|
|
popd
|
|
if [ -s ./release/SHA256SUMS ]; then
|
|
cat ./release/SHA256SUMS
|
|
else
|
|
log_error "sha256sums is not valid. Aborting."
|
|
exit 1
|
|
fi
|
|
|
|
# Upload artifacts.
|
|
if [ "$DRY_RUN" = "true" ] || [ "${NO_UPLOAD}" == 1 ]; then
|
|
log_callout "Skipping artifact upload to gs://etcd. --no-upload flat is set or DRY_RUN is true."
|
|
else
|
|
read -p "Upload etcd ${RELEASE_VERSION} release artifacts to gs://etcd [y/N]? " -r confirm
|
|
[[ "${confirm,,}" == "y" ]] || exit 1
|
|
gsutil -m cp ./release/SHA256SUMS "gs://etcd/${RELEASE_VERSION}/"
|
|
gsutil -m cp ./release/*.zip "gs://etcd/${RELEASE_VERSION}/"
|
|
gsutil -m cp ./release/*.tar.gz "gs://etcd/${RELEASE_VERSION}/"
|
|
gsutil -m acl ch -u allUsers:R -r "gs://etcd/${RELEASE_VERSION}/"
|
|
fi
|
|
|
|
# Push images.
|
|
if [ "$DRY_RUN" = "true" ] || [ "${NO_DOCKER_PUSH}" == 1 ]; then
|
|
log_callout "Skipping docker push. --no-docker-push flat is set or DRY_RUN is true."
|
|
else
|
|
read -p "Publish etcd ${RELEASE_VERSION} docker images to quay.io [y/N]? " -r confirm
|
|
[[ "${confirm,,}" == "y" ]] || exit 1
|
|
|
|
echo "Pushing container images to quay.io ${RELEASE_VERSION}"
|
|
docker push "quay.io/coreos/etcd:${RELEASE_VERSION}"
|
|
|
|
echo "Pushing container images to gcr.io ${RELEASE_VERSION}"
|
|
docker push "gcr.io/etcd-development/etcd:${RELEASE_VERSION}"
|
|
|
|
for TARGET_ARCH in "-arm64" "-ppc64le"; do
|
|
log_callout "Pushing container images to quay.io ${RELEASE_VERSION}${TARGET_ARCH}"
|
|
docker push "quay.io/coreos/etcd:${RELEASE_VERSION}${TARGET_ARCH}"
|
|
|
|
log_callout "Pushing container images to gcr.io ${RELEASE_VERSION}${TARGET_ARCH}"
|
|
docker push "gcr.io/etcd-development/etcd:${RELEASE_VERSION}${TARGET_ARCH}"
|
|
done
|
|
fi
|
|
|
|
### Release validation
|
|
mkdir -p downloads
|
|
|
|
# Check image versions
|
|
for IMAGE in "quay.io/coreos/etcd:${RELEASE_VERSION}" "gcr.io/etcd-development/etcd:${RELEASE_VERSION}"; do
|
|
# shellcheck disable=SC2155
|
|
local image_version=$(docker run --rm "${IMAGE}" etcd --version | grep "etcd Version" | awk -F: '{print $2}' | tr -d '[:space:]')
|
|
if [ "${image_version}" != "${VERSION}" ]; then
|
|
log_error "Check failed: etcd --version output for ${IMAGE} is incorrect: ${image_version}"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# Check gsutil binary versions
|
|
# shellcheck disable=SC2155
|
|
local BINARY_TGZ="etcd-${RELEASE_VERSION}-$(go env GOOS)-amd64.tar.gz"
|
|
if [ "${DRY_RUN}" == "true" ] || [ "${NO_UPLOAD}" == 1 ]; then
|
|
cp "./release/${BINARY_TGZ}" downloads
|
|
else
|
|
gsutil cp "gs://etcd/${RELEASE_VERSION}/${BINARY_TGZ}" downloads
|
|
fi
|
|
tar -zx -C downloads -f "downloads/${BINARY_TGZ}"
|
|
# shellcheck disable=SC2155
|
|
local binary_version=$("./downloads/etcd-${RELEASE_VERSION}-$(go env GOOS)-amd64/etcd" --version | grep "etcd Version" | awk -F: '{print $2}' | tr -d '[:space:]')
|
|
if [ "${binary_version}" != "${VERSION}" ]; then
|
|
log_error "Check failed: etcd --version output for ${BINARY_TGZ} from gs://etcd/${RELEASE_VERSION} is incorrect: ${binary_version}"
|
|
exit 1
|
|
fi
|
|
|
|
# TODO: signing process
|
|
log_warning ""
|
|
log_warning "WARNING: The release has not been signed and published to github. This must be done manually."
|
|
log_warning ""
|
|
log_success "Success."
|
|
exit 0
|
|
}
|
|
|
|
POSITIONAL=()
|
|
NO_UPLOAD=0
|
|
NO_DOCKER_PUSH=0
|
|
IN_PLACE=0
|
|
|
|
while test $# -gt 0; do
|
|
case "$1" in
|
|
-h|--help)
|
|
shift
|
|
help
|
|
exit 0
|
|
;;
|
|
--in-place)
|
|
IN_PLACE=1
|
|
shift
|
|
;;
|
|
--no-upload)
|
|
NO_UPLOAD=1
|
|
shift
|
|
;;
|
|
--no-docker-push)
|
|
NO_DOCKER_PUSH=1
|
|
shift
|
|
;;
|
|
*)
|
|
POSITIONAL+=("$1") # save it in an array for later
|
|
shift # past argument
|
|
;;
|
|
esac
|
|
done
|
|
set -- "${POSITIONAL[@]}" # restore positional parameters
|
|
|
|
if [[ ! $# -eq 1 ]]; then
|
|
help
|
|
exit 1
|
|
fi
|
|
|
|
# Note that we shouldn't upload artifacts in --in-place mode, so it
|
|
# must be called with DRY_RUN=true
|
|
if [ "${DRY_RUN}" != "true" ] && [ "${IN_PLACE}" == 1 ]; then
|
|
log_error "--in-place should only be called with DRY_RUN=true"
|
|
exit 1
|
|
fi
|
|
|
|
main "$1"
|