blob: 5bbca613724cd7f3142fb47156ae8588a031834c [file] [log] [blame]
#! /usr/bin/env bash
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
cd "$(dirname "$0")/.." || exit 1
scriptname=$(basename "$0")
export tlpName=accumulo
export projName="$tlpName"
export projNameLong="Apache ${projName^}"
export stagingRepoPrefix="https://repository.apache.org/content/repositories/orgapache$tlpName"
export srcQualifier="src"
export relTestingUrl="https://$tlpName.apache.org/contributor/verifying-release"
export tagPrefix="rel/"
# check if running in a color terminal
terminalSupportsColor() {
local c; c=$(tput colors 2>/dev/null) || c=-1
[[ -t 1 ]] && [[ $c -ge 8 ]]
}
terminalSupportsColor && doColor=1 || doColor=0
color() { local c; c=$1; shift; [[ $doColor -eq 1 ]] && echo -e "\\e[0;${c}m${*}\\e[0m" || echo "$@"; }
red() { color 31 "$@"; }
green() { color 32 "$@"; }
yellow() { color 33 "$@"; }
fail() { echo -e ' ' "$@"; exit 1; }
runLog() { local o; o=$1 && shift && echo "$(green Running) $(yellow "$@" '>>' "$o")" && echo Running "$@" >> "$o" && eval "$@" >> "$o"; }
run() { echo "$(green Running) $(yellow "$@")" && eval "$@"; }
currentBranch() { local b; b=$(git symbolic-ref -q HEAD) && echo "${b##refs/heads/}"; }
cacheGPG() {
# make sure gpg agent has key cached
# first clear cache, to reset timeouts (best attempt)
{ hash gpg-connect-agent && gpg-connect-agent reloadagent /bye; } &>/dev/null
# determine fingerprint to use
until selectFingerprint; do
red 'ERROR - Invalid selection'
done
local TESTFILE; TESTFILE=$(mktemp --tmpdir "${USER}-gpgTestFile-XXXXXXXX.txt")
[[ -r $TESTFILE ]] && gpg --local-user "$SELECTED_FINGERPRINT" --sign "$TESTFILE" && rm -f "$TESTFILE" "$TESTFILE.gpg"
}
prompter() {
# $1 description; $2 pattern to validate against
local x
read -r -p "Enter the $1: " x
until eval "[[ \$x =~ ^$2\$ ]]"; do
echo " $(red "$x") is not a proper $1" 1>&2
read -r -p "Enter the $1: " x
done
echo "$x"
}
pretty() { local f; f=$1; shift; git log "--pretty=tformat:$f" "$@"; }
gitCommits() { pretty %H "$@"; }
gitCommit() { gitCommits -n1 "$@"; }
gitSubject() { pretty %s "$@"; }
selectFingerprint() {
local f fingerprints=()
if [[ $SELECTED_FINGERPRINT =~ ^[0-9a-fA-F]{40}$ ]]; then
# it's already set, don't set it again
return 0
fi
mapfile -t fingerprints < <(gpg --list-secret-keys --with-colons --with-fingerprint 2>/dev/null | awk -F: '$1 == "fpr" {print $10}')
fingerprints+=('Other')
echo
echo "Select the $(green gpg) key to use:"
COLUMNS=1
select f in "${fingerprints[@]}"; do
if [[ -z $f ]]; then
return 1
elif [[ $f == 'Other' ]]; then
SELECTED_FINGERPRINT=$(prompter "$(green gpg) key's $(green fingerprint)" '[0-9a-fA-F]{40}')
return 0
else
SELECTED_FINGERPRINT="$f"
return 0
fi
done
}
createEmail() {
echo
yellow "IMPORTANT!! IMPORTANT!! IMPORTANT!! IMPORTANT!! IMPORTANT!! IMPORTANT!!"
echo
echo " Release candidate will be staged at:"
echo " $(yellow 'https://repository.apache.org/#stagingRepositories')"
echo
echo " $(green 'DO') click $(green 'Close') to complete the staging process!"
echo " $(red 'DO *NOT*') click $(red 'Release') until after the vote has been approved!"
echo
yellow "IMPORTANT!! IMPORTANT!! IMPORTANT!! IMPORTANT!! IMPORTANT!! IMPORTANT!!"
echo
# $1 version (optional); $2 rc sequence num (optional); $3 staging repo num (optional)
local ver; [[ -n "$1" ]] && ver=$1 || ver=$(prompter 'version to be released (eg. x.y.z)' '[0-9]+[.][0-9]+[.][0-9]+')
local rc; [[ -n "$2" ]] && rc=$2 || rc=$(prompter 'release candidate sequence number (eg. 1, 2, etc.)' '[0-9]+')
local stagingrepo; [[ -n "$3" ]] && stagingrepo=$3 || stagingrepo=$(prompter 'staging repository number from https://repository.apache.org/#stagingRepositories' '[0-9]+')
local srcSha; [[ -n "$4" ]] && srcSha=$4 || srcSha=$(prompter 'SHA512 for source tarball' '[0-9a-f]{128}')
local binSha; [[ -n "$5" ]] && binSha=$5 || binSha=$(prompter 'SHA512 for binary tarball' '[0-9a-f]{128}')
local branch; branch=$ver-rc$rc
local commit; commit=$(gitCommit "$branch") || exit 1
local tag; tag=$tagPrefix$ver
echo
yellow "IMPORTANT!! IMPORTANT!! IMPORTANT!! IMPORTANT!! IMPORTANT!! IMPORTANT!!"
echo
echo " Don't forget to make the staged branch available for review by"
echo " pushing a branch named $(yellow "$branch") with its head at"
echo " $(yellow "$commit"):"
echo
echo " # replace $(yellow "\$origin") with your upstream remote name "
echo " $(green "git push") $(yellow "\$origin") $(green "$commit:refs/heads/$branch")"
echo
echo " Remember, $(red DO NOT PUSH) the $(red "$tag") tag until after the vote"
echo " passes and the tag is re-made with a gpg signature using:"
echo " $(red "git tag -f -s -m '$projNameLong $ver' $tag") \\"
echo " $(red "$commit")"
echo
yellow "IMPORTANT!! IMPORTANT!! IMPORTANT!! IMPORTANT!! IMPORTANT!! IMPORTANT!!"
echo
read -r -s -p 'Press Enter to generate the [VOTE] email...'
echo 1>&2
# determine fingerprint to use
until selectFingerprint; do
red 'ERROR - Invalid selection'
done
# compute the date with a buffer of 30 minutes
local votedate; votedate=$(date -d "+3 days 30 minutes" "+%s")
# round back to the previous half-hour
local halfhour; halfhour=$((votedate - (votedate % 1800)))
votedate=$(date -u -d"1970-01-01 $halfhour seconds UTC")
export TZ="America/New_York"
local edtvotedate; edtvotedate=$(date -d"1970-01-01 $halfhour seconds UTC")
export TZ="America/Los_Angeles"
local pdtvotedate; pdtvotedate=$(date -d"1970-01-01 $halfhour seconds UTC")
cat <<EOF
$(yellow '============================================================')
Subject: $(green [VOTE] "$projNameLong $branch")
$(yellow '============================================================')
${tlpName^} Developers,
Please consider the following candidate for $projNameLong $(green "$ver").
Git Commit:
$(green "$commit")
Branch:
$(green "$branch")
If this vote passes, a gpg-signed tag will be created using:
$(green "git tag -f -s -m '$projNameLong $ver' $tag") \\
$(green "$commit")
Staging repo: $(green "$stagingRepoPrefix-$stagingrepo")
Source (official release artifact): $(green "$stagingRepoPrefix-$stagingrepo/org/apache/$tlpName/$projName/$ver/$projName-$ver-$srcQualifier.tar.gz")
Binary: $(green "$stagingRepoPrefix-$stagingrepo/org/apache/$tlpName/$projName/$ver/$projName-$ver-bin.tar.gz")
Append ".asc" to download the cryptographic signature for a given artifact.
(You can also append ".sha1" or ".md5" instead in order to verify the checksums
generated by Maven to verify the integrity of the Nexus repository staging area.)
Signing keys are available at https://www.apache.org/dist/$tlpName/KEYS
(Expected fingerprint: $(green "$SELECTED_FINGERPRINT"))
In addition to the tarballs and their signatures, the following checksum
files will be added to the dist/release SVN area after release:
$(yellow "$projName-$ver-$srcQualifier.tar.gz.sha512") will contain:
SHA512 ($(green "$projName-$ver-$srcQualifier.tar.gz")) = $(yellow "$srcSha")
$(yellow "$projName-$ver-bin.tar.gz.sha512") will contain:
SHA512 ($(green "$projName-$ver-bin.tar.gz")) = $(yellow "$binSha")
Release notes (in progress) can be found at: $(green "https://$tlpName.staged.apache.org/release/$projName-$ver")
Release testing instructions: $relTestingUrl
Please vote one of:
[ ] +1 - I have verified and accept...
[ ] +0 - I have reservations, but not strong enough to vote against...
[ ] -1 - Because..., I do not accept...
... these artifacts as the $(green "$ver") release of $projNameLong.
This vote will remain open until at least $(green "$votedate").
($(green "$edtvotedate") / $(green "$pdtvotedate"))
Voting can continue after this deadline until the release manager
sends an email ending the vote.
Thanks!
P.S. Hint: download the whole staging repo with
wget -erobots=off -r -l inf -np -nH \\
$(green "$stagingRepoPrefix-$stagingrepo/")
# note the trailing slash is needed
$(yellow '============================================================')
EOF
}
cleanUpAndFail() {
# $1 command; $2 log; $3 original branch; $4 next branch
echo " Failure in $(red "$1")!"
echo " Check output in $(yellow "$2")"
echo " Initiating clean up steps..."
run git checkout "$3"
# pre-populate branches with expected next branch; de-duplicate later
local branches; branches=("$4")
local tags; tags=()
local x; local y
for x in $(gitCommits "${cBranch}..${nBranch}"); do
for y in $(git branch --contains "$x" | cut -c3-); do
branches=("${branches[@]}" "$y")
done
for y in $(git tag --contains "$x"); do
tags=("${tags[@]}" "$y")
done
done
# de-duplicate branches
local a
local tmpArray; tmpArray=("${branches[@]}")
IFS=$'\n' read -d '' -r -a branches < <(printf '%s\n' "${tmpArray[@]}" | sort -u)
for x in "${branches[@]}"; do
echo "Do you wish to clean up (delete) the branch $(yellow "$x")?"
a=$(prompter "letter 'y' or 'n'" '[yn]')
[[ $a == 'y' ]] && git branch -D "$x"
done
for x in "${tags[@]}"; do
echo "Do you wish to clean up (delete) the tag $(yellow "$x")?"
a=$(prompter "letter 'y' or 'n'" '[yn]')
[[ $a == 'y' ]] && git tag -d "$x"
done
exit 1
}
selectRemote() {
local r remotes=()
for r in $(git remote); do
remotes+=("$r ($(git config "remote.$r.url"))")
done
remotes+=('Skip')
echo "Select a $(green remote) (you will be prompted again before pushing):"
COLUMNS=1
select r in "${remotes[@]}"; do
if [[ -z $r ]]; then
return 1
elif [[ $r == 'Skip' ]]; then
SELECTED_REMOTE=$r
return 0
else
SELECTED_REMOTE="${r%% *}"
SELECTED_REMOTE_URL=$(git config "remote.${SELECTED_REMOTE}.url")
return 0
fi
done
}
createReleaseCandidate() {
echo
yellow "WARNING!! WARNING!! WARNING!! WARNING!! WARNING!! WARNING!!"
echo
echo " Don't forget to $(yellow 'Set up your development environment')!"
echo " For details, see the section by that name at:"
echo " $(green 'https://infra.apache.org/publishing-maven-artifacts.html')"
echo
echo " This will modify your local git repository by creating"
echo " branches and tags. Afterwards, you may need to perform"
echo " some manual steps to complete the release or to rollback"
echo " in the case of failure."
echo
echo " Release candidate will be staged at:"
echo " $(yellow 'https://repository.apache.org/#stagingRepositories')"
echo
yellow "WARNING!! WARNING!! WARNING!! WARNING!! WARNING!! WARNING!!"
echo
if [[ ${#@} -ne 0 ]]; then
red "CAUTION!! Unrecognized arguments!!"
fail "You added '$*'"
fi
local ver
ver=$(xmllint --shell pom.xml <<<'xpath /*[local-name()="project"]/*[local-name()="version"]/text()' | grep content= | cut -f2 -d=)
ver=${ver%%-SNAPSHOT}
echo "Building release candidate for version: $(green "$ver")"
local tag; tag=$tagPrefix$ver
local cBranch; cBranch=$(currentBranch) || fail "$(red Failure)" to get current branch from git
local rc; rc=$(prompter 'release candidate sequence number (eg. 1, 2, etc.)' '[0-9]+')
local tmpNextVer; tmpNextVer="${ver%.*}.$((${ver##*.}+1))"
local nextVer; nextVer=$(prompter "next snapshot version to be released [$tmpNextVer]" '([0-9]+[.][0-9]+[.][0-9]+)?')
[[ -n $nextVer ]] || nextVer=$tmpNextVer
local rcBranch; rcBranch=$ver-rc$rc
local nBranch; nBranch=$rcBranch-next
cacheGPG || fail "Unable to cache GPG credentials into gpg-agent"
# create working branch
run git checkout -b "$nBranch" "$cBranch" || \
fail "Unable to create working branch $(red "$nBranch") from $(red "$cBranch")!"
# create a release candidate from a branch
local oFile; oFile=$(mktemp --tmpdir "$projName-build-$rcBranch-XXXXXXXX.log")
{
[[ -w $oFile ]] && runLog "$oFile" mvn clean release:clean
} || cleanUpAndFail 'mvn clean release:clean' "$oFile" "$cBranch" "$nBranch"
runLog "$oFile" mvn -B release:prepare -DdevelopmentVersion="${nextVer}-SNAPSHOT" "-Dgpg.keyname=$SELECTED_FINGERPRINT" || \
cleanUpAndFail "mvn -B release:prepare -DdevelopmentVersion=${nextVer}-SNAPSHOT" "$oFile" "$cBranch" "$nBranch"
runLog "$oFile" mvn release:perform "-Dgpg.keyname=$SELECTED_FINGERPRINT" || \
cleanUpAndFail "mvn release:perform" "$oFile" "$cBranch" "$nBranch"
# switch back to original branch
run git checkout "${cBranch}"
# verify the next branch contains both expected log messages and no more
{
[[ $(gitCommits "${cBranch}..${nBranch}" | wc -l) -eq 2 ]] && \
[[ $(gitCommit "${nBranch}~2") == $(gitCommit "${cBranch}") ]] && \
[[ $(gitSubject "${nBranch}") =~ ^\[maven-release-plugin\]\ prepare\ for\ next ]] && \
[[ $(gitSubject "${nBranch}~1") =~ ^\[maven-release-plugin\]\ prepare\ release\ rel[/] ]]
} || cleanUpAndFail "verifying that $nBranch contains only logs from release plugin"
# verify the tag is one behind $nBranch and one ahead of $cBranch
[[ $(gitCommit "${nBranch}~1") == $(gitCommit "refs/tags/$tag") ]] || \
cleanUpAndFail "verifying that ${nBranch}~1 == refs/tags/$tag"
# remove tag which was created
run git tag -d "$tag" || \
cleanUpAndFail "removing unused git tag $tag"
# create release candidate branch to vote on
run git branch "$rcBranch" "${nBranch}~1" || \
cleanUpAndFail "creating branch $rcBranch"
# determine remote to use
until selectRemote; do
red 'ERROR - Invalid selection'
done
# push branches (ask first)
if [[ $SELECTED_REMOTE == 'Skip' ]]; then
red "Did not push branches; you'll need to perform this step manually."
else
echo "Do you wish to push the following branches to $SELECTED_REMOTE ($(green "$SELECTED_REMOTE_URL"))?"
echo " $(yellow "$rcBranch") (for others to examine for the vote)"
echo " $(yellow "$nBranch") (for merging into $cBranch if vote passes)"
local a; a=$(prompter "letter 'y' or 'n'" '[yn]')
{
[[ $a == 'y' ]] && \
run git push -u "$SELECTED_REMOTE" "refs/heads/$nBranch" "refs/heads/$rcBranch"
} || red "Did not push branches; you'll need to perform this step manually."
fi
local numSrc; numSrc=$(find target/checkout/ -type f -name "$projName-$ver-source-release.tar.gz" | wc -l)
local numBin; numBin=$(find target/checkout/ -type f -name "$projName-$ver-bin.tar.gz" | wc -l)
shopt -s globstar
local srcSha; srcSha=""
local binSha; binSha=""
[[ $numSrc = "1" ]] && srcSha=$(sha512sum target/checkout/**/"$projName-$ver-source-release.tar.gz" | cut -f1 -d" ")
[[ $numBin = "1" ]] && binSha=$(sha512sum target/checkout/**/"$projName-$ver-bin.tar.gz" | cut -f1 -d" ")
# continue to creating email notification
echo "$(red Running)" "$(yellow "$scriptname" --create-email "$ver" "$rc")"
createEmail "$ver" "$rc" "" "$srcSha" "$binSha"
}
SELECTED_FINGERPRINT=""
if [[ $1 == '--create-release-candidate' ]]; then
shift
createReleaseCandidate "$@"
elif [[ $1 == '--create-email' ]]; then
shift
createEmail "$@"
else
fail "Missing one of: $(red --create-release-candidate), $(red --create-email)"
fi