This is an automated email from the ASF dual-hosted git repository. stoty pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/master by this push: new 6d84d0f PHOENIX-6178 Consider adopting the create-release scripts and process from HBase 6d84d0f is described below commit 6d84d0fcf8b906219af1232f49013938f38f5711 Author: Istvan Toth <st...@apache.org> AuthorDate: Tue Oct 6 10:01:33 2020 +0200 PHOENIX-6178 Consider adopting the create-release scripts and process from HBase * copy and adapt dev/create-release from HBase * remove source assembly * add binary lincenses to binary assembly * use assembly as binary distribution --- dev/create-release/README.txt | 131 ++++ dev/create-release/do-release-docker.sh | 345 ++++++++++ dev/create-release/do-release.sh | 153 +++++ dev/create-release/mac-sshd-gpg-agent/Dockerfile | 101 +++ dev/create-release/phoenix-rm/Dockerfile | 66 ++ dev/create-release/release-build.sh | 302 +++++++++ dev/create-release/release-util.sh | 707 +++++++++++++++++++++ dev/create-release/vote.tmpl | 31 + phoenix-assembly/pom.xml | 19 +- .../src/build/components/all-common-files.xml | 26 +- phoenix-assembly/src/build/src.xml | 127 ---- pom.xml | 3 + 12 files changed, 1854 insertions(+), 157 deletions(-) diff --git a/dev/create-release/README.txt b/dev/create-release/README.txt new file mode 100644 index 0000000..2179ca1 --- /dev/null +++ b/dev/create-release/README.txt @@ -0,0 +1,131 @@ +Entrance script is _do-release-docker.sh_. Requires a local docker; +for example, on macOS, Docker for Desktop installed and running. + +For usage, pass '-h': + + $ ./do-release-docker.sh -h + +./do-release-docker.sh accepts the following options + + -d [path] required. working directory. output will be written to "output" in here. + -f "force" -- actually publish this release. Unless you specify '-f', it will + default to dry run mode, which checks and does local builds, but does not upload anything. + -t [tag] tag for the phoenix-rm docker image to use for building (default: "latest"). + -j [path] path to local JDK installation to use building. By default the script will + use openjdk8 installed in the docker image. + -p [project] project to build, such as 'phoenix' or 'phoenix-connectors'; defaults to phoenix env var + -r [repo] git repo to use for remote git operations. defaults to ASF gitbox for project. + -s [step] runs a single step of the process; valid steps are: tag|publish-dist|publish-release. + If none specified, runs tag, then publish-dist, and then publish-release. + 'publish-snapshot' is also an allowed, less used, option. + -h display usage information + -x debug. do less clean up. (env file, gpg forwarding on mac) + +For example, use the following command do a full dry run build of phoenix-thirdparty: + +./do-release-docker.sh -d /tmp/thirdparty-build -p phoenix-thirdparty + +To run a build w/o invoking docker (not recommended!), use _do_release.sh_. + +Both scripts will query interactively for needed parameters and passphrases. +For explanation of the parameters, execute: + $ release-build.sh --help + +Before starting the RC build, run a reconciliation of what is in +JIRA with what is in the commit log. Make sure they align and that +anomalies are explained up in JIRA. + +See http://hbase.apache.org/book.html#maven.release +(Even though the above documentation is for HBase, we use the same process for Phoenix.) + +Regardless of where your release build will run (locally, locally in docker, on a remote machine, +etc) you will need a local gpg-agent with access to your secret keys. A quick way to tell gpg +to clear out state and start a gpg-agent is via the following command phrase: + +$ gpgconf --kill all && gpg-connect-agent /bye + +Before starting an RC build, make sure your local gpg-agent has configs +to properly handle your credentials, especially if you want to avoid +typing the passphrase to your secret key. + +e.g. if you are going to run and step away, best to increase the TTL +on caching the unlocked secret via ~/.gnupg/gpg-agent.conf + # in seconds, e.g. a day + default-cache-ttl 86400 + max-cache-ttl 86400 + +Running a build on GCE is easy enough. Here are some notes if of use. +Create an instance. 4CPU/15G/10G disk seems to work well enough. +Once up, run the below to make your machine fit for RC building: + +Note that according to https://www.apache.org/legal/release-policy.html#owned-controlled-hardware +Building an Apache release must be done on hardware owned and controlled by the committer. + +# Presuming debian-compatible OS, do these steps on the VM +# your VM username should be your ASF id, because it will show up in build artifacts. +# Follow the docker install guide: https://docs.docker.com/engine/install/debian/ +$ sudo apt-get install -y \ + apt-transport-https \ + ca-certificates \ + curl \ + gnupg2 \ + software-properties-common +$ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add - +$ sudo add-apt-repository -y \ + "deb [arch=amd64] https://download.docker.com/linux/debian \ + $(lsb_release -cs) \ + stable" +$ sudo apt-get update +$ sudo apt-get install -y docker-ce docker-ce-cli containerd.io +# Follow the post installation steps: https://docs.docker.com/engine/install/linux-postinstall/ +$ sudo usermod -aG docker $USER +# LOGOUT and then LOGIN again so $USERID shows as part of docker group +# Test here by running docker's hello world as your build user +$ docker run hello-world + +# Follow the GPG guide for forwarding your gpg-agent from your local machine to the VM +# https://wiki.gnupg.org/AgentForwarding +# On the VM find out the location of the gpg agent socket and extra socket +$ gpgconf --list-dir agent-socket +/run/user/1000/gnupg/S.gpg-agent +$ gpgconf --list-dir agent-extra-socket +/run/user/1000/gnupg/S.gpg-agent.extra +# On the VM configure sshd to remove stale sockets +$ sudo bash -c 'echo "StreamLocalBindUnlink yes" >> /etc/ssh/sshd_config' +$ sudo systemctl restart ssh +# logout of the VM + +# Do these steps on your local machine. +# make sure gpg-agent is running +$ gpg-connect-agent /bye +# Export your public key and copy it to the VM. +# Assuming 'example.gce.host' maps to your VM's external IP (or use the IP) +$ gpg --export exam...@apache.org > ~/gpg.example.apache.pub +$ scp ~/gpg.example.apache.pub example.gce.host: +# ssh into the VM while forwarding the remote gpg socket locations found above to your local +# gpg-agent's extra socket (this will restrict what commands the remote node is allowed to have +# your agent handle. Note that the gpg guide above can help you set this up in your ssh config +# rather than typing it in ssh like this every time. +$ ssh -i ~/.ssh/my_id \ + -R "/run/user/1000/gnupg/S.gpg-agent:$(gpgconf --list-dir agent-extra-socket)" \ + -R "/run/user/1000/gnupg/S.gpg-agent.extra:$(gpgconf --list-dir agent-extra-socket)" \ + example.gce.host + +# now in an SSH session on the VM with the socket forwarding +# import your public key and test signing with the forwarding to your local agent. +$ gpg --no-autostart --import gpg.example.apache.pub +$ echo "foo" > foo.txt +$ gpg --no-autostart --detach --armor --sign foo.txt +$ gpg --no-autostart --verify foo.txt.asc + +# install git and clone the main project on the build machine +$ sudo apt-get install -y git +$ git clone https://github.com/apache/phoenix.git +# finally set up an output folder and launch a dry run. +$ mkdir ~/build +$ cd phoenix +$ ./dev/create-release/do-release-docker.sh -d ~/build + +# for building the main repo specifically you can save an extra download by pointing the build +# to the local clone you just made +$ ./dev/create-release/do-release-docker.sh -d ~/build -r .git diff --git a/dev/create-release/do-release-docker.sh b/dev/create-release/do-release-docker.sh new file mode 100755 index 0000000..4535611 --- /dev/null +++ b/dev/create-release/do-release-docker.sh @@ -0,0 +1,345 @@ +#!/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. +# + +# +# Creates a Phoenix release candidate. The script will update versions, tag the branch, +# build Phoenix binary packages and documentation, and upload maven artifacts to a staging +# repository. There is also a dry run mode where only local builds are performed, and +# nothing is uploaded to the ASF repos. +# +# Run with "-h" for options. For example, running below will do all +# steps above using the 'rm' dir under Downloads as workspace: +# +# $ ./do-release-docker.sh -d ~/Downloads/rm +# +# The scripts in this directory came originally from spark [1]. They were then +# modified to suite the hbase context, which were further adopted to Phoenix. +# These scripts supercedes the old +# ../make_rc.sh script for making release candidates because what is here is more +# comprehensive doing more steps of the RM process as well as running in a +# container so the RM build environment can be a constant. +# +# It: +# * Tags release +# * Sets version to the release version +# * Sets version to next SNAPSHOT version. +# * Builds, signs, and hashes all artifacts. +# * Pushes release tgzs to the dev dir in a apache dist. +# * Pushes to repository.apache.org staging. +# +# The entry point is here, in the do-release-docker.sh script. +# +# 1. https://github.com/apache/spark/tree/master/dev/create-release +# +set -e + +# Set this to build other phoenix repos: e.g. PROJECT=phoenix-connectors +export PROJECT="${PROJECT:-phoenix}" + +SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=SCRIPTDIR/release-util.sh +. "$SELF/release-util.sh" +ORIG_PWD="$(pwd)" + +function usage { + local NAME + NAME="$(basename "${BASH_SOURCE[0]}")" + cat <<EOF +Usage: $NAME [options] + +This script runs the release scripts inside a docker image. + +Options: + + -d [path] required. working directory. output will be written to "output" in here. + -f "force" -- actually publish this release. Unless you specify '-f', it will + default to dry run mode, which checks and does local builds, but does not upload anything. + -t [tag] tag for the phoenix-rm docker image to use for building (default: "latest"). + -j [path] path to local JDK installation to use building. By default the script will + use openjdk8 installed in the docker image. + -p [project] project to build, such as 'phoenix' or 'phoenix-connectors'; defaults to $PROJECT env var + -r [repo] git repo to use for remote git operations. defaults to ASF gitbox for project. + -s [step] runs a single step of the process; valid steps are: tag|publish-dist|publish-release. + If none specified, runs tag, then publish-dist, and then publish-release. + 'publish-snapshot' is also an allowed, less used, option. + -h display usage information + -x debug. do less clean up. (env file, gpg forwarding on mac) +EOF + exit 1 +} + +WORKDIR= +IMGTAG=latest +JAVA= +RELEASE_STEP= +GIT_REPO= +while getopts "d:fhj:p:r:s:t:x" opt; do + case $opt in + d) WORKDIR="$OPTARG" ;; + f) DRY_RUN=0 ;; + t) IMGTAG="$OPTARG" ;; + j) JAVA="$OPTARG" ;; + p) PROJECT="$OPTARG" ;; + r) GIT_REPO="$OPTARG" ;; + s) RELEASE_STEP="$OPTARG" ;; + x) DEBUG=1 ;; + h) usage ;; + ?) error "Invalid option. Run with -h for help." ;; + esac +done +shift $((OPTIND-1)) +if (( $# > 0 )); then + error "Arguments can only be provided with option flags, invalid args: $*" +fi +export DEBUG + +if [ -z "$WORKDIR" ] || [ ! -d "$WORKDIR" ]; then + error "Work directory (-d) must be defined and exist. Run with -h for help." +fi + +if [ -d "$WORKDIR/output" ]; then + read -r -p "Output directory already exists. Overwrite and continue? [y/n] " ANSWER + if [ "$ANSWER" != "y" ]; then + error "Exiting." + fi +fi + +if [ -f "${WORKDIR}/gpg-proxy.ssh.pid" ] || \ + [ -f "${WORKDIR}/gpg-proxy.cid" ] || \ + [ -f "${WORKDIR}/release.cid" ]; then + read -r -p "container/pid files from prior run exists. Overwrite and continue? [y/n] " ANSWER + if [ "$ANSWER" != "y" ]; then + error "Exiting." + fi +fi + +cd "$WORKDIR" +rm -rf "$WORKDIR/output" +rm -rf "${WORKDIR}/gpg-proxy.ssh.pid" "${WORKDIR}/gpg-proxy.cid" "${WORKDIR}/release.cid" +mkdir "$WORKDIR/output" + +banner "Gathering release details." +HOST_OS="$(get_host_os)" +get_release_info + +banner "Setup" + +# Place all RM scripts and necessary data in a local directory that must be defined in the command +# line. This directory is mounted into the image. Its WORKDIR, the arg passed with -d. +for f in "$SELF"/*; do + if [ -f "$f" ]; then + cp "$f" "$WORKDIR" + fi +done + +# We need to import that public key in the container in order to use the private key via the agent. +GPG_KEY_FILE="$WORKDIR/gpg.key.public" +echo "Exporting public key for ${GPG_KEY}" +fcreate_secure "$GPG_KEY_FILE" +$GPG "${GPG_ARGS[@]}" --export "${GPG_KEY}" > "${GPG_KEY_FILE}" + +function cleanup { + local id + banner "Release Cleanup" + if is_debug; then + echo "skipping due to debug run" + return 0 + fi + echo "details in cleanup.log" + if [ -f "${ENVFILE}" ]; then + rm -f "$ENVFILE" + fi + rm -f "$GPG_KEY_FILE" + if [ -f "${WORKDIR}/gpg-proxy.ssh.pid" ]; then + id=$(cat "${WORKDIR}/gpg-proxy.ssh.pid") + echo "Stopping ssh tunnel for gpg-agent at PID ${id}" | tee -a cleanup.log + kill -9 "${id}" >>cleanup.log 2>&1 || true + rm -f "${WORKDIR}/gpg-proxy.ssh.pid" >>cleanup.log 2>&1 + fi + if [ -f "${WORKDIR}/gpg-proxy.cid" ]; then + id=$(cat "${WORKDIR}/gpg-proxy.cid") + echo "Stopping gpg-proxy container with ID ${id}" | tee -a cleanup.log + docker kill "${id}" >>cleanup.log 2>&1 || true + rm -f "${WORKDIR}/gpg-proxy.cid" >>cleanup.log 2>&1 + # TODO we should remove the gpgagent volume? + fi + if [ -f "${WORKDIR}/release.cid" ]; then + id=$(cat "${WORKDIR}/release.cid") + echo "Stopping release container with ID ${id}" | tee -a cleanup.log + docker kill "${id}" >>cleanup.log 2>&1 || true + rm -f "${WORKDIR}/release.cid" >>cleanup.log 2>&1 + fi +} + +trap cleanup EXIT + +echo "Host OS: ${HOST_OS}" +if [ "${HOST_OS}" == "DARWIN" ]; then + run_silent "Building gpg-agent-proxy image with tag ${IMGTAG}..." "docker-proxy-build.log" \ + docker build --build-arg "UID=${UID}" --build-arg "RM_USER=${USER}" \ + --tag "org.apache.phoenix/gpg-agent-proxy:${IMGTAG}" "${SELF}/mac-sshd-gpg-agent" +fi + +run_silent "Building phoenix-rm image with tag $IMGTAG..." "docker-build.log" \ + docker build --tag "org.apache.phoenix/phoenix-rm:$IMGTAG" --build-arg "UID=$UID" \ + --build-arg "RM_USER=${USER}" "$SELF/phoenix-rm" + +banner "Final prep for container launch." +echo "Writing out environment for container." +# Write the release information to a file with environment variables to be used when running the +# image. +ENVFILE="$WORKDIR/env.list" +fcreate_secure "$ENVFILE" + +cat > "$ENVFILE" <<EOF +PROJECT=$PROJECT +DRY_RUN=$DRY_RUN +SKIP_TAG=$SKIP_TAG +RUNNING_IN_DOCKER=1 +GIT_BRANCH=$GIT_BRANCH +NEXT_VERSION=$NEXT_VERSION +RELEASE_VERSION=$RELEASE_VERSION +RELEASE_TAG=$RELEASE_TAG +GIT_REF=$GIT_REF +ASF_USERNAME=$ASF_USERNAME +GIT_NAME=$GIT_NAME +GIT_EMAIL=$GIT_EMAIL +GPG_KEY=$GPG_KEY +ASF_PASSWORD=$ASF_PASSWORD +RELEASE_STEP=$RELEASE_STEP +API_DIFF_TAG=$API_DIFF_TAG +HOST_OS=$HOST_OS +EOF + +JAVA_MOUNT=() +if [ -n "$JAVA" ]; then + echo "JAVA_HOME=/opt/phoenix-java" >> "$ENVFILE" + JAVA_MOUNT=(--mount "type=bind,src=${JAVA},dst=/opt/phoenix-java,readonly") +fi + +#TODO some debug output would be good here +GIT_REPO_MOUNT=() +if [ -n "${GIT_REPO}" ]; then + case "${GIT_REPO}" in + # skip the easy to identify remote protocols + ssh://*|git://*|http://*|https://*|ftp://*|ftps://*) ;; + # for sure local + /*) + GIT_REPO_MOUNT=(--mount "type=bind,src=${GIT_REPO},dst=/opt/phoenix-repo,consistency=delegated") + echo "HOST_GIT_REPO=${GIT_REPO}" >> "${ENVFILE}" + GIT_REPO="/opt/phoenix-repo" + ;; + # on the host but normally git wouldn't use the local optimization + file://*) + echo "[INFO] converted file:// git repo to a local path, which changes git to assume --local." + GIT_REPO_MOUNT=(--mount "type=bind,src=${GIT_REPO#file://},dst=/opt/phoenix-repo,consistency=delegated") + echo "HOST_GIT_REPO=${GIT_REPO}" >> "${ENVFILE}" + GIT_REPO="/opt/phoenix-repo" + ;; + # have to decide if it's a local path or the "scp-ish" remote + *) + declare colon_remove_prefix; + declare slash_remove_prefix; + declare local_path; + colon_remove_prefix="${GIT_REPO#*:}" + slash_remove_prefix="${GIT_REPO#*/}" + if [ "${GIT_REPO}" = "${colon_remove_prefix}" ]; then + # if there was no colon at all, we assume this must be a local path + local_path="no colon at all" + elif [ "${GIT_REPO}" != "${slash_remove_prefix}" ]; then + # if there was a colon and there is no slash, then we assume it must be scp-style host + # and a relative path + + if [ "${#colon_remove_prefix}" -lt "${#slash_remove_prefix}" ]; then + # Given the substrings made by removing everything up to the first colon and slash + # we can determine which comes first based on the longer substring length. + # if the slash is first, then we assume the colon is part of a path name and if the colon + # is first then it is the seperator between a scp-style host name and the path. + local_path="slash happened before a colon" + fi + fi + if [ -n "${local_path}" ]; then + # convert to an absolute path + GIT_REPO="$(cd "$(dirname "${ORIG_PWD}/${GIT_REPO}")"; pwd)/$(basename "${ORIG_PWD}/${GIT_REPO}")" + GIT_REPO_MOUNT=(--mount "type=bind,src=${GIT_REPO},dst=/opt/phoenix-repo,consistency=delegated") + echo "HOST_GIT_REPO=${GIT_REPO}" >> "${ENVFILE}" + GIT_REPO="/opt/phoenix-repo" + fi + ;; + esac + echo "GIT_REPO=${GIT_REPO}" >> "${ENVFILE}" +fi + +GPG_PROXY_MOUNT=() +if [ "${HOST_OS}" == "DARWIN" ]; then + GPG_PROXY_MOUNT=(--mount "type=volume,src=gpgagent,dst=/home/${USER}/.gnupg/") + echo "Setting up GPG agent proxy container needed on OS X." + echo " we should clean this up for you. If that fails the container ID is below and in " \ + "gpg-proxy.cid" + #TODO the key pair used should be configurable + docker run --rm -p 62222:22 \ + --detach --cidfile "${WORKDIR}/gpg-proxy.cid" \ + --mount \ + "type=bind,src=${HOME}/.ssh/id_rsa.pub,dst=/home/${USER}/.ssh/authorized_keys,readonly" \ + "${GPG_PROXY_MOUNT[@]}" \ + "org.apache.phoenix/gpg-agent-proxy:${IMGTAG}" + # gotta trust the container host + ssh-keyscan -p 62222 localhost 2>/dev/null | sort > "${WORKDIR}/gpg-agent-proxy.ssh-keyscan" + sort "${HOME}/.ssh/known_hosts" | comm -1 -3 - "${WORKDIR}/gpg-agent-proxy.ssh-keyscan" \ + > "${WORKDIR}/gpg-agent-proxy.known_hosts" + if [ -s "${WORKDIR}/gpg-agent-proxy.known_hosts" ]; then + echo "Your ssh known_hosts does not include the entries for the gpg-agent proxy container." + echo "The following entry(ies) arre missing:" + sed -e 's/^/ /' "${WORKDIR}/gpg-agent-proxy.known_hosts" + read -r -p "Okay to add these entries to ${HOME}/.ssh/known_hosts? [y/n] " ANSWER + if [ "$ANSWER" != "y" ]; then + error "Exiting." + fi + cat "${WORKDIR}/gpg-agent-proxy.known_hosts" >> "${HOME}/.ssh/known_hosts" + fi + echo "Launching ssh reverse tunnel from the container to gpg agent." + echo " we should clean this up for you. If that fails the PID is in gpg-proxy.ssh.pid" + ssh -p 62222 -R "/home/${USER}/.gnupg/S.gpg-agent:$(gpgconf --list-dir agent-extra-socket)" \ + -i "${HOME}/.ssh/id_rsa" -N -n localhost >gpg-proxy.ssh.log 2>&1 & + echo $! > "${WORKDIR}/gpg-proxy.ssh.pid" +else + # Note that on linux we always directly mount the gpg agent's extra socket to limit what the + # container can ask the gpg-agent to do. + # When working on a remote linux machine you should be sure to forward both the remote machine's + # agent socket and agent extra socket to your local gpg-agent's extra socket. See the README.txt + # for an example. + GPG_PROXY_MOUNT=(--mount \ + "type=bind,src=$(gpgconf --list-dir agent-extra-socket),dst=/home/${USER}/.gnupg/S.gpg-agent") +fi + +banner "Building $RELEASE_TAG; output will be at $WORKDIR/output" +echo "We should clean the container up when we are done. If that fails then the container ID " \ + "is in release.cid" +echo +# Where possible we specifcy "consistency=delegated" when we do not need host access during the +# build run. On Mac OS X specifically this gets us a big perf improvement. +cmd=(docker run --rm -ti \ + --env-file "$ENVFILE" \ + --cidfile "${WORKDIR}/release.cid" \ + --mount "type=bind,src=${WORKDIR},dst=/home/${USER}/phoenix-rm,consistency=delegated" \ + "${JAVA_MOUNT[@]}" \ + "${GIT_REPO_MOUNT[@]}" \ + "${GPG_PROXY_MOUNT[@]}" \ + "org.apache.phoenix/phoenix-rm:$IMGTAG") +echo "${cmd[*]}" +"${cmd[@]}" diff --git a/dev/create-release/do-release.sh b/dev/create-release/do-release.sh new file mode 100755 index 0000000..fd613e2 --- /dev/null +++ b/dev/create-release/do-release.sh @@ -0,0 +1,153 @@ +#!/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. +# + +set -e +# Use the adjacent do-release-docker.sh instead, if you can. +# Otherwise, this runs core of the release creation. +# Will ask you questions on what to build and for logins +# and passwords to use building. +export PROJECT="${PROJECT:-phoenix}" + +SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=SCRIPTDIR/release-util.sh +. "$SELF/release-util.sh" + +while getopts "b:fs:" opt; do + case $opt in + b) export GIT_BRANCH=$OPTARG ;; + f) export DRY_RUN=0 ;; # "force", ie actually publish this release (otherwise defaults to dry run) + s) RELEASE_STEP="$OPTARG" ;; + ?) error "Invalid option: $OPTARG" ;; + esac +done +shift $((OPTIND-1)) +if (( $# > 0 )); then + error "Arguments can only be provided with option flags, invalid args: $*" +fi + +function gpg_agent_help { + cat <<EOF +Trying to sign a test file using your GPG setup failed. + +Please make sure you have a local gpg-agent running with access to your secret keys prior to +starting a release build. If you are creating release artifacts on a remote machine please check +that you have set up ssh forwarding to the gpg-agent extra socket. + +For help on how to do this please see the README file in the create-release directory. +EOF + exit 1 +} + +# If running in docker, import and then cache keys. +if [ "$RUNNING_IN_DOCKER" = "1" ]; then + # when Docker Desktop for mac is running under load there is a delay before the mounted volume + # becomes available. if we do not pause then we may try to use the gpg-agent socket before docker + # has got it ready and we will not think there is a gpg-agent. + if [ "${HOST_OS}" == "DARWIN" ]; then + sleep 5 + fi + # in docker our working dir is set to where all of our scripts are held + # and we want default output to go into the "output" directory that should be in there. + if [ -d "output" ]; then + cd output + fi + echo "GPG Version: $("${GPG}" "${GPG_ARGS[@]}" --version)" + # Inside docker, need to import the GPG key stored in the current directory. + if ! $GPG "${GPG_ARGS[@]}" --import "$SELF/gpg.key.public" ; then + gpg_agent_help + fi + + # We may need to adjust the path since JAVA_HOME may be overridden by the driver script. + if [ -n "$JAVA_HOME" ]; then + echo "Using JAVA_HOME from host." + export PATH="$JAVA_HOME/bin:$PATH" + else + # JAVA_HOME for the openjdk package. + export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/ + fi +else + # Outside docker, need to ask for information about the release. + get_release_info +fi + +GPG_TTY="$(tty)" +export GPG_TTY +echo "Testing gpg signing." +echo "foo" > gpg_test.txt +if ! "${GPG}" "${GPG_ARGS[@]}" --detach --armor --sign gpg_test.txt ; then + gpg_agent_help +fi +# In --batch mode we have to be explicit about what we are verifying +if ! "${GPG}" "${GPG_ARGS[@]}" --verify gpg_test.txt.asc gpg_test.txt ; then + gpg_agent_help +fi + +if [[ -z "$RELEASE_STEP" ]]; then + # If doing all stages, leave out 'publish-snapshot' + RELEASE_STEP="tag_publish-dist_publish-release" + # and use shared maven local repo for efficiency + export REPO="${REPO:-$(pwd)/$(mktemp -d phoenix-repo-XXXXX)}" +fi + +function should_build { + local WHAT=$1 + if [[ -z "$RELEASE_STEP" ]]; then + return 0 + elif [[ "$RELEASE_STEP" == *"$WHAT"* ]]; then + return 0 + else + return 1 + fi +} + +if should_build "tag" && [ "$SKIP_TAG" = 0 ]; then + if [ -z "${YETUS_HOME}" ] && [ "${RUNNING_IN_DOCKER}" != "1" ]; then + declare local_yetus="/opt/apache-yetus/0.12.0/" + if [ "$(get_host_os)" = "DARWIN" ]; then + local_yetus="/usr/local/Cellar/yetus/0.12.0/" + fi + YETUS_HOME="$(read_config "YETUS_HOME not defined. Absolute path to local install of Apache Yetus" "${local_yetus}")" + export YETUS_HOME + fi + run_silent "Creating release tag $RELEASE_TAG..." "tag.log" \ + "$SELF/release-build.sh" tag + if is_dry_run; then + export TAG_SAME_DRY_RUN="true"; + fi +else + echo "Skipping tag creation for $RELEASE_TAG." +fi + +if should_build "publish-dist"; then + run_silent "Publishing distribution packages (tarballs)" "publish-dist.log" \ + "$SELF/release-build.sh" publish-dist +else + echo "Skipping publish-dist step." +fi + +if should_build "publish-snapshot"; then + run_silent "Publishing snapshot" "publish-snapshot.log" \ + "$SELF/release-build.sh" publish-snapshot + +elif should_build "publish-release"; then + run_silent "Publishing release" "publish-release.log" \ + "$SELF/release-build.sh" publish-release +else + echo "Skipping publish-release step." +fi diff --git a/dev/create-release/mac-sshd-gpg-agent/Dockerfile b/dev/create-release/mac-sshd-gpg-agent/Dockerfile new file mode 100644 index 0000000..084688d --- /dev/null +++ b/dev/create-release/mac-sshd-gpg-agent/Dockerfile @@ -0,0 +1,101 @@ +# +# 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. +# + +# Image for use on Mac boxes to get a gpg agent socket available +# within transient release building ocntainers. +# +# Copied directly from HBase +# +# build like: +# +# docker build --build-arg "UID=$UID" --build-arg "RM_USER=$USER" \ +# --tag org.apache.phoenix/gpg-agent-proxy mac-sshd-gpg-agent +# +# run like: +# +# docker run --rm -p 62222:22 \ +# --mount "type=bind,src=${HOME}/.ssh/id_rsa.pub,dst=/home/${USER}/.ssh/authorized_keys,readonly" \ +# --mount "type=volume,src=gpgagent,dst=/home/${USER}/.gnupg/" \ +# org.apache.phoenix/gpg-agent-proxy:latest +# +# test like: +# +# ssh -p 62222 -R "/home/${USER}/.gnupg/S.gpg-agent:$(gpgconf --list-dir agent-extra-socket)" \ +# -i "${HOME}/.ssh/id_rsa" -N -n localhost +# +# launch a docker container to do work that shares the mount for the gpg agent +# expressly does not need to be this same image, but needs to have defined the same user +# +# docker run --rm -it \ +# --mount "type=volume,src=gpgagent,dst=/home/${USER}/.gnupg/" \ +# --mount "type=bind,src=${HOME}/projects/phoenix-releases/KEYS,dst=/home/${USER}/KEYS,readonly" \ +# --entrypoint /bin/bash --user "${USER}" --workdir "/home/${USER}/" \ +# org.apache.phoenix/gpg-agent-proxy:latest +# +# +# Make sure to import the public keys +# +# gpg --no-autostart --import < ${HOME}/KEYS +# Optional? +# gpg --no-autostart --edit-key ${YOUR_KEY} +# trust +# 5 +# y +# quit +#ubu +# gpg --no-autostart --armor --detach --sign foo +# gpg --no-autostart --verify foo.asc +# +# For more info see +# * gpg forwarding over ssh: https://wiki.gnupg.org/AgentForwarding +# * example docker for sshd: https://github.com/hotblac/nginx-ssh +# * why we have to bother with this: https://github.com/docker/for-mac/issues/483 +# +# If the docker image changes then the host key used by sshd will change and you will get a +# nastygram when launching ssh about host identification changing. This is expected. you should +# remove the previous host key. +# +# Tested with +# * Docker Desktop 2.2.0.5 +# * gpg 2.2.20 +# * pinentry-mac 0.9.4 +# * yubikey 5 +# +FROM ubuntu:18.04 + +# This is all in a single "RUN" command so that if anything changes, "apt update" is run to fetch +# the most current package versions (instead of potentially using old versions cached by docker). +# +# We only need gnupg2 here if we want the ability to test out the gpg-agent forwarding by sshing +# into the container rather than launching a new docker container. +RUN DEBIAN_FRONTEND=noninteractive apt-get -qq -y update \ + && DEBIAN_FRONTEND=noninteractive apt-get -qq -y install --no-install-recommends \ + openssh-server=1:7.6* gnupg2=2.2.4* && mkdir /run/sshd \ + && echo "StreamLocalBindUnlink yes" >> /etc/ssh/sshd_config \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* +EXPOSE 22 +# Set up our ssh user +ARG UID +ARG RM_USER +RUN groupadd sshgroup && \ + useradd --create-home --shell /bin/bash --groups sshgroup --uid $UID $RM_USER && \ + mkdir /home/$RM_USER/.ssh /home/$RM_USER/.gnupg && \ + chown -R $RM_USER:sshgroup /home/$RM_USER/ && \ + chmod -R 700 /home/$RM_USER/ +# When we run we run sshd +ENTRYPOINT ["/usr/sbin/sshd", "-D"] diff --git a/dev/create-release/phoenix-rm/Dockerfile b/dev/create-release/phoenix-rm/Dockerfile new file mode 100644 index 0000000..3522760 --- /dev/null +++ b/dev/create-release/phoenix-rm/Dockerfile @@ -0,0 +1,66 @@ +# +# 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. +# + +# Image for building Phoenix releases. Based on Ubuntu 18.04. +# +# Copied directly from HBase +# +# Includes: +# * Java 8 +FROM ubuntu:18.04 + +# Install extra needed repos and refresh. +# +# This is all in a single "RUN" command so that if anything changes, "apt update" is run to fetch +# the most current package versions (instead of potentially using old versions cached by docker). +RUN DEBIAN_FRONTEND=noninteractive apt-get -qq -y update \ + && DEBIAN_FRONTEND=noninteractive apt-get -qq -y install --no-install-recommends \ + curl='7.58.0-*' \ + git='1:2.17.1-*' \ + gnupg='2.2.4-*' \ + libcurl4-openssl-dev='7.58.0-*' \ + libxml2-dev='2.9.4+dfsg1-*' \ + lsof='4.89+dfsg-*' \ + maven='3.6.0-*' \ + openjdk-8-jdk='8*' \ + python-pip='9.0.1-*' \ + subversion='1.9.7-*' \ + wget='1.19.4-*' \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && update-alternatives --set java /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java \ + && pip install \ + python-dateutil==2.8.1 +# Install Apache Yetus +ENV YETUS_VERSION 0.12.0 +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +RUN wget -qO- "https://www.apache.org/dyn/mirrors/mirrors.cgi?action=download&filename=/yetus/${YETUS_VERSION}/apache-yetus-${YETUS_VERSION}-bin.tar.gz" | \ + tar xvz -C /opt +ENV YETUS_HOME /opt/apache-yetus-${YETUS_VERSION} + +ARG UID +ARG RM_USER +RUN groupadd phoenix-rm && \ + useradd --create-home --shell /bin/bash -p phoenix-rm -u $UID $RM_USER && \ + mkdir /home/$RM_USER/.gnupg && \ + chown -R $RM_USER:phoenix-rm /home/$RM_USER && \ + chmod -R 700 /home/$RM_USER + +USER $RM_USER:phoenix-rm +WORKDIR /home/$RM_USER/phoenix-rm/ + +ENTRYPOINT [ "./do-release.sh" ] diff --git a/dev/create-release/release-build.sh b/dev/create-release/release-build.sh new file mode 100755 index 0000000..a1442e4 --- /dev/null +++ b/dev/create-release/release-build.sh @@ -0,0 +1,302 @@ +#!/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. +# + +trap cleanup EXIT + +# Source in utils. +SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=SCRIPTDIR/release-util.sh +. "$SELF/release-util.sh" + +# Print usage and exit. +function exit_with_usage { + cat <<'EOF' +Usage: release-build.sh <tag|publish-dist|publish-snapshot|publish-release> +Creates release deliverables from a tag or commit. +Argument: one of 'tag', 'publish-dist', 'publish-snapshot', or 'publish-release' + tag Prepares for release on specified git branch: Set release version, + update CHANGES and RELEASENOTES, create release tag, + increment version for ongoing dev, and publish to Apache git repo. + publish-dist Build and publish distribution packages (tarballs) to Apache dist repo + publish-snapshot Build and publish maven artifacts snapshot release to Apache snapshots repo + publish-release Build and publish maven artifacts release to Apache release repo, and + construct vote email from template + +All other inputs are environment variables. Please use do-release-docker.sh or +do-release.sh to set up the needed environment variables. This script, release-build.sh, +is not intended to be called stand-alone, and such use is untested. The env variables used are: + +Used for 'tag' and 'publish' stages: + PROJECT - The project to build. No default. + RELEASE_VERSION - Version used in pom files for release (e.g. 2.1.2) + Required for 'tag'; defaults for 'publish' to the version in pom at GIT_REF + RELEASE_TAG - Name of release tag (e.g. 2.1.2RC0), also used by + publish-dist as package version name in dist directory path + ASF_USERNAME - Username of ASF committer account + ASF_PASSWORD - Password of ASF committer account + DRY_RUN - 1:true (default), 0:false. If "1", does almost all the work, but doesn't actually + publish anything to upstream source or object repositories. It defaults to "1", so if you want + to actually publish you have to set '-f' (force) flag in do-release.sh or do-release-docker.sh. + +Used only for 'tag': + YETUS_HOME - installation location for Apache Yetus + GIT_NAME - Name to use with git + GIT_EMAIL - E-mail address to use with git + GIT_BRANCH - Git branch on which to make release. Tag is always placed at HEAD of this branch. + NEXT_VERSION - Development version after release (e.g. 2.1.3-SNAPSHOT) + +Used only for 'publish': + GIT_REF - Release tag or commit to build from (defaults to $RELEASE_TAG; only need to + separately define GIT_REF if RELEASE_TAG is not actually present as a tag at publish time) + If both RELEASE_TAG and GIT_REF are undefined it will default to HEAD of master. + GPG_KEY - GPG key id (usually email addr) used to sign release artifacts + REPO - Set to full path of a directory to use as maven local repo (dependencies cache) + to avoid re-downloading dependencies for each stage. It is automatically set if you + request full sequence of stages (tag, publish-dist, publish-release) in do-release.sh. + +For example: + $ PROJECT="phoenix-connectors" ASF_USERNAME=NAME ASF_PASSWORD=PASSWORD GPG_KEY=st...@apache.org ./release-build.sh publish-dist +EOF + exit 1 +} + +set -e + +function cleanup { + # If REPO was set, then leave things be. Otherwise if we defined a repo clean it out. + if [[ -z "${REPO}" ]] && [[ -n "${MAVEN_LOCAL_REPO}" ]]; then + echo "Cleaning up temp repo in '${MAVEN_LOCAL_REPO}'. Set REPO to reuse downloads." >&2 + rm -f "${MAVEN_SETTINGS_FILE}" &> /dev/null || true + rm -rf "${MAVEN_LOCAL_REPO}" &> /dev/null || true + fi +} + +if [ $# -ne 1 ]; then + exit_with_usage +fi + +if [[ "$*" == *"help"* ]]; then + exit_with_usage +fi + +init_locale +init_java +init_mvn +init_python +# Print out subset of perl version (used in git hooks and japi-compliance-checker) +perl --version | grep 'This is' + +rm -rf "${PROJECT}" + +if is_debug; then + set -x # detailed logging during action +fi + +if [[ "$1" == "tag" ]]; then + init_yetus + # for 'tag' stage + set -o pipefail + check_get_passwords ASF_PASSWORD + check_needed_vars PROJECT RELEASE_VERSION RELEASE_TAG NEXT_VERSION GIT_EMAIL GIT_NAME GIT_BRANCH + if [ -z "${GIT_REPO}" ]; then + check_needed_vars ASF_USERNAME ASF_PASSWORD + fi + git_clone_overwrite + + # 'update_releasenotes' searches the project's Jira for issues where 'Fix Version' matches specified + # $jira_fix_version. For most projects this is same as ${RELEASE_VERSION}. However, the original 'phoenix-*' + # projects share the same PHOENIX jira name. To make this work, by convention, the PHOENIX jira "Fix Version" + # field values have the sub-project name without the phoenix- prefix pre-pended, as in "connectors-6.0.0". + # So, here we prepend the project name to the version, but only for the original phoenix sub-projects. + # phoenix-omid and phoenix-tephra have their own JIRA projects and versioning. + jira_fix_version="${RELEASE_VERSION}" + shopt -s nocasematch + if [[ "${PROJECT}" == "phoenix-queryserver" || "${PROJECT}" == "phoenix-connectors" || "${PROJECT}" == "phoenix-thirdparty" ]]; then + jira_fix_version="${PROJECT#phoenix-}-${RELEASE_VERSION}" + fi + + shopt -u nocasematch + update_releasenotes "$(pwd)/${PROJECT}" "${jira_fix_version}" + + cd "${PROJECT}" + + git config user.name "$GIT_NAME" + git config user.email "$GIT_EMAIL" + + # Create release version + maven_set_version "$RELEASE_VERSION" + git add RELEASENOTES.md CHANGES.md + + git commit -a -m "Preparing ${PROJECT} release $RELEASE_TAG; tagging and updates to CHANGES.md and RELEASENOTES.md" + echo "Creating tag $RELEASE_TAG at the head of $GIT_BRANCH" + git tag "$RELEASE_TAG" + + # Create next version + maven_set_version "$NEXT_VERSION" + + git commit -a -m "Preparing development version $NEXT_VERSION" + + if ! is_dry_run; then + # Push changes + git push origin "$RELEASE_TAG" + git push origin "HEAD:$GIT_BRANCH" + cd .. + rm -rf "${PROJECT}" + else + cd .. + mv "${PROJECT}" "${PROJECT}.tag" + echo "Dry run: Clone with version changes and tag available as ${PROJECT}.tag in the output directory." + fi + exit 0 +fi + +### Below is for 'publish-*' stages ### +check_get_passwords ASF_PASSWORD +check_needed_vars PROJECT ASF_USERNAME ASF_PASSWORD GPG_KEY + +# Commit ref to checkout when building +BASE_DIR=$(pwd) +GIT_REF=${GIT_REF:-master} +if [[ "$PROJECT" =~ ^phoenix ]]; then + RELEASE_STAGING_LOCATION="https://dist.apache.org/repos/dist/dev/phoenix" +else + RELEASE_STAGING_LOCATION="https://dist.apache.org/repos/dist/dev/${PROJECT}" +fi + +# in case of dry run, enable publish steps to chain from tag step +if is_dry_run && [[ "${TAG_SAME_DRY_RUN:-}" == "true" && -d "${PROJECT}.tag" ]]; then + ln -s "${PROJECT}.tag" "${PROJECT}" +else + git_clone_overwrite +fi +cd "${PROJECT}" +git checkout "$GIT_REF" +git_hash="$(git rev-parse --short HEAD)" +echo "Checked out ${PROJECT} at ${GIT_REF} commit $git_hash" + +if [ -z "${RELEASE_VERSION}" ]; then + RELEASE_VERSION="$(maven_get_version)" +fi + +# This is a band-aid fix to avoid the failure of Maven nightly snapshot in some Jenkins +# machines by explicitly calling /usr/sbin/lsof. Please see SPARK-22377 and the discussion +# in its pull request. +LSOF=lsof +if ! hash $LSOF 2>/dev/null; then + LSOF=/usr/sbin/lsof +fi + +package_version_name="$RELEASE_TAG" +if [ -z "$package_version_name" ]; then + package_version_name="${RELEASE_VERSION}-$(date +%Y_%m_%d_%H_%M)-${git_hash}" +fi + +git clean -d -f -x +cd .. + +if [[ "$1" == "publish-dist" ]]; then + # Source and binary tarballs + echo "Packaging release source tarballs" + make_src_release "${PROJECT}" "${RELEASE_VERSION}" + + # we do not have binary tarballs for phoenix-thirdparty + if [[ "${PROJECT}" != "phoenix-thirdparty" ]]; then + make_binary_release "${PROJECT}" "${RELEASE_VERSION}" + fi + + DEST_DIR_NAME="${PROJECT}-${package_version_name}" + svn_target="svn-${PROJECT}" + svn co --depth=empty "$RELEASE_STAGING_LOCATION" "$svn_target" + rm -rf "${svn_target:?}/${DEST_DIR_NAME}" + mkdir -p "$svn_target/${DEST_DIR_NAME}" + + echo "Copying release tarballs" + cp "${PROJECT}"-*.tar.* "$svn_target/${DEST_DIR_NAME}/" + cp "${PROJECT}/CHANGES.md" "$svn_target/${DEST_DIR_NAME}/" + cp "${PROJECT}/RELEASENOTES.md" "$svn_target/${DEST_DIR_NAME}/" + shopt -s nocasematch + # Generate api report only if project is hbase for now. - Not for Phoenix + if [ "${PROJECT}" == "hbase" ]; then + # This script usually reports an errcode along w/ the report. + generate_api_report "./${PROJECT}" "${API_DIFF_TAG}" "${GIT_REF}" || true + cp api*.html "$svn_target/${DEST_DIR_NAME}/" + fi + shopt -u nocasematch + + svn add "$svn_target/${DEST_DIR_NAME}" + + if ! is_dry_run; then + cd "$svn_target" + svn ci --username "$ASF_USERNAME" --password "$ASF_PASSWORD" -m"Apache ${PROJECT} $package_version_name" --no-auth-cache + cd .. + rm -rf "$svn_target" + else + mv "$svn_target/${DEST_DIR_NAME}" "${svn_target}_${DEST_DIR_NAME}.dist" + echo "Dry run: svn-managed 'dist' directory with release tarballs, CHANGES.md and RELEASENOTES.md available as $(pwd)/${svn_target}_${DEST_DIR_NAME}.dist" + rm -rf "$svn_target" + fi + + exit 0 +fi + +if [[ "$1" == "publish-snapshot" ]]; then + ( + cd "${PROJECT}" + mvn_log="${BASE_DIR}/mvn_deploy_snapshot.log" + echo "Publishing snapshot to nexus" + maven_deploy snapshot "$mvn_log" + if ! is_dry_run; then + echo "Snapshot artifacts successfully published to repo." + rm "$mvn_log" + else + echo "Dry run: Snapshot artifacts successfully built, but not published due to dry run." + fi + ) + exit $? +fi + +if [[ "$1" == "publish-release" ]]; then + ( + cd "${PROJECT}" + mvn_log="${BASE_DIR}/mvn_deploy_release.log" + echo "Staging release in nexus" + maven_deploy release "$mvn_log" + declare staged_repo_id="dryrun-no-repo" + if ! is_dry_run; then + staged_repo_id=$(grep -o "Closing staging repository with ID .*" "$mvn_log" \ + | sed -e 's/Closing staging repository with ID "\([^"]*\)"./\1/') + echo "Release artifacts successfully published to repo ${staged_repo_id}" + rm "$mvn_log" + else + echo "Dry run: Release artifacts successfully built, but not published due to dry run." + fi + # Dump out email to send. Where we find vote.tmpl depends + # on where this script is run from + PROJECT_TEXT="${PROJECT//-/ }" #substitute like 's/-/ /g' + export PROJECT_TEXT + eval "echo \"$(< "${SELF}/vote.tmpl")\"" |tee "${BASE_DIR}/vote.txt" + ) + exit $? +fi + +set +x # done with detailed logging +cd .. +rm -rf "${PROJECT}" +echo "ERROR: expects to be called with 'tag', 'publish-dist', 'publish-release', or 'publish-snapshot'" >&2 +exit_with_usage diff --git a/dev/create-release/release-util.sh b/dev/create-release/release-util.sh new file mode 100755 index 0000000..442f375 --- /dev/null +++ b/dev/create-release/release-util.sh @@ -0,0 +1,707 @@ +#!/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. +# +DRY_RUN=${DRY_RUN:-1} #default to dry run +DEBUG=${DEBUG:-0} +GPG=${GPG:-gpg} +GPG_ARGS=(--no-autostart --batch) +if [ -n "${GPG_KEY}" ]; then + GPG_ARGS=("${GPG_ARGS[@]}" --local-user "${GPG_KEY}") +fi +# Maven Profiles for publishing snapshots and release to Maven Central and Dist +PUBLISH_PROFILES=("-P" "apache-release,release") + +set -e + +function error { + echo "Error: $*" >&2 + exit 1 +} + +function read_config { + local PROMPT="$1" + local DEFAULT="$2" + local REPLY= + + read -r -p "$PROMPT [$DEFAULT]: " REPLY + local RETVAL="${REPLY:-$DEFAULT}" + if [ -z "$RETVAL" ]; then + error "$PROMPT must be provided." + fi + echo "$RETVAL" +} + +function parse_version { + # Heuristics to skip the parent version + grep -e '<version>.*\..*</version>' | \ + head -n 1 | tail -n 1 | cut -d'>' -f2 | cut -d '<' -f1 +} + +function banner { + local msg="$1" + echo "========================" + echo "=== ${msg}" + echo +} + +# current number of seconds since epoch +function get_ctime { + date +"%s" +} + +function run_silent { + local BANNER="$1" + local LOG_FILE="$2" + shift 2 + local -i start_time + local -i stop_time + + banner "${BANNER}" + echo "Command: $*" + echo "Log file: $LOG_FILE" + start_time="$(get_ctime)" + + if ! "$@" 1>"$LOG_FILE" 2>&1; then + echo "Command FAILED. Check full logs for details." + tail "$LOG_FILE" + exit 1 + fi + stop_time="$(get_ctime)" + echo "=== SUCCESS ($((stop_time - start_time)) seconds)" +} + +function fcreate_secure { + local FPATH="$1" + rm -f "$FPATH" + touch "$FPATH" + chmod 600 "$FPATH" +} + +# API compare version. +function get_api_diff_version { + local version="$1" + local rev + local api_diff_tag + rev=$(echo "$version" | cut -d . -f 3) + if [ "$rev" != 0 ]; then + local short_version + short_version="$(echo "$version" | cut -d . -f 1-2)" + api_diff_tag="rel/${short_version}.$((rev - 1))" + else + local major minor + major="$(echo "$version" | cut -d . -f 1)" + minor="$(echo "$version" | cut -d . -f 2)" + if [ "$minor" != 0 ]; then + api_diff_tag="rel/${major}.$((minor - 1)).0" + else + api_diff_tag="rel/$((major - 1)).0.0" + fi + fi + api_diff_tag="$(read_config "api_diff_tag" "$api_diff_tag")" + echo "$api_diff_tag" +} + +# Get all branches that begin with 'branch-', the hbase convention for +# release branches, sort them and then pop off the most recent. +function get_release_info { + PROJECT="$(read_config "PROJECT" "$PROJECT")" + export PROJECT + + if [[ -z "${ASF_REPO}" ]]; then + ASF_REPO="https://gitbox.apache.org/repos/asf/${PROJECT}.git" + fi + if [[ -z "${ASF_REPO_WEBUI}" ]]; then + ASF_REPO_WEBUI="https://gitbox.apache.org/repos/asf?p=${PROJECT}.git" + fi + if [[ -z "${ASF_GITHUB_REPO}" ]]; then + ASF_GITHUB_REPO="https://github.com/apache/${PROJECT}" + fi + if [ -z "$GIT_BRANCH" ]; then + # If no branch is specified, find out the latest branch from the repo. + GIT_BRANCH="$(git ls-remote --heads "$ASF_REPO" | + grep refs/heads/branch- | + awk '{print $2}' | + sort -r | + head -n 1 | + cut -d/ -f3)" + fi + + GIT_BRANCH="$(read_config "GIT_BRANCH" "$GIT_BRANCH")" + export GIT_BRANCH + + # Find the current version for the branch. + # FIXME this only works with gitbox + local version + version="$(curl -s "$ASF_REPO_WEBUI;a=blob_plain;f=pom.xml;hb=refs/heads/$GIT_BRANCH" | + parse_version)" + echo "Current branch VERSION is $version." + + NEXT_VERSION="$version" + RELEASE_VERSION="" + SHORT_VERSION="$(echo "$version" | cut -d . -f 1-2)" + if [[ ! "$version" =~ .*-SNAPSHOT ]]; then + RELEASE_VERSION="$version" + else + RELEASE_VERSION="${version/-SNAPSHOT/}" + fi + + local REV + REV="$(echo "${RELEASE_VERSION}" | cut -d . -f 3)" + + # Find out what RC is being prepared. + # - If the current version is "x.y.0", then this is RC0 of the "x.y.0" release. + # - If not, need to check whether the previous version has been already released or not. + # - If it has, then we're building RC0 of the current version. + # - If it has not, we're building the next RC of the previous version. + local RC_COUNT + if [ "$REV" != 0 ]; then + local PREV_REL_REV=$((REV - 1)) + PREV_REL_TAG="rel/${SHORT_VERSION}.${PREV_REL_REV}" + if git ls-remote --tags "$ASF_REPO" "$PREV_REL_TAG" | grep -q "refs/tags/${PREV_REL_TAG}$" ; then + RC_COUNT=0 + REV=$((REV + 1)) + NEXT_VERSION="${SHORT_VERSION}.${REV}-SNAPSHOT" + else + RELEASE_VERSION="${SHORT_VERSION}.${PREV_REL_REV}" + RC_COUNT="$(git ls-remote --tags "$ASF_REPO" "${RELEASE_VERSION}RC*" | wc -l)" + # This makes a 'number' of it. + RC_COUNT=$((RC_COUNT)) + fi + else + REV=$((REV + 1)) + NEXT_VERSION="${SHORT_VERSION}.${REV}-SNAPSHOT" + RC_COUNT=0 + fi + + RELEASE_VERSION="$(read_config "RELEASE_VERSION" "$RELEASE_VERSION")" + NEXT_VERSION="$(read_config "NEXT_VERSION" "$NEXT_VERSION")" + export RELEASE_VERSION NEXT_VERSION + + RC_COUNT="$(read_config "RC_COUNT" "$RC_COUNT")" + RELEASE_TAG="${RELEASE_VERSION}RC${RC_COUNT}" + RELEASE_TAG="$(read_config "RELEASE_TAG" "$RELEASE_TAG")" + + # Check if the RC already exists, and if re-creating the RC, skip tag creation. + SKIP_TAG=0 + if git ls-remote --tags "$ASF_REPO" "$RELEASE_TAG" | grep -q "refs/tags/${RELEASE_TAG}$" ; then + read -r -p "$RELEASE_TAG already exists. Continue anyway [y/n]? " ANSWER + if [ "$ANSWER" != "y" ]; then + echo "Exiting." + exit 1 + fi + SKIP_TAG=1 + fi + + export RELEASE_TAG SKIP_TAG + + GIT_REF="$RELEASE_TAG" + if is_dry_run; then + echo "This is a dry run. If tag does not actually exist, please confirm the ref that will be built for testing." + GIT_REF="$(read_config "GIT_REF" "$GIT_REF")" + fi + export GIT_REF + + if [ "${PROJECT}" == "hbase" ]; then + API_DIFF_TAG="$(get_api_diff_version "$RELEASE_VERSION")" + fi + + # Gather some user information. + ASF_USERNAME="$(read_config "ASF_USERNAME" "$LOGNAME")" + + GIT_NAME="$(git config user.name || echo "")" + GIT_NAME="$(read_config "GIT_NAME" "$GIT_NAME")" + + GIT_EMAIL="$asf_usern...@apache.org" + GPG_KEY="$(read_config "GPG_KEY" "$GIT_EMAIL")" + if ! GPG_KEY_ID=$("${GPG}" "${GPG_ARGS[@]}" --keyid-format 0xshort --list-public-key "${GPG_KEY}" | grep "\[S\]" | grep -o "0x[0-9A-F]*") || + [ -z "${GPG_KEY_ID}" ] ; then + GPG_KEY_ID=$("${GPG}" "${GPG_ARGS[@]}" --keyid-format 0xshort --list-public-key "${GPG_KEY}" | head -n 1 | grep -o "0x[0-9A-F]*" || true) + fi + read -r -p "We think the key '${GPG_KEY}' corresponds to the key id '${GPG_KEY_ID}'. Is this correct [y/n]? " ANSWER + if [ "$ANSWER" = "y" ]; then + GPG_KEY="${GPG_KEY_ID}" + fi + export API_DIFF_TAG ASF_USERNAME GIT_NAME GIT_EMAIL GPG_KEY + + cat <<EOF +================ +Release details: +GIT_BRANCH: $GIT_BRANCH +RELEASE_VERSION: $RELEASE_VERSION +NEXT_VERSION: $NEXT_VERSION +RELEASE_TAG: $RELEASE_TAG $([[ "$GIT_REF" != "$RELEASE_TAG" ]] && printf "\n%s\n" "GIT_REF: $GIT_REF") +API_DIFF_TAG: $API_DIFF_TAG +ASF_USERNAME: $ASF_USERNAME +GPG_KEY: $GPG_KEY +GIT_NAME: $GIT_NAME +GIT_EMAIL: $GIT_EMAIL +DRY_RUN: $(is_dry_run && echo "yes" || echo "NO, THIS BUILD WILL BE PUBLISHED!") +================ +EOF + + read -r -p "Is this info correct [y/n]? " ANSWER + if [ "$ANSWER" != "y" ]; then + echo "Exiting." + exit 1 + fi + GPG_ARGS=("${GPG_ARGS[@]}" --local-user "${GPG_KEY}") + + if ! is_dry_run; then + if [ -z "$ASF_PASSWORD" ]; then + stty -echo && printf "ASF_PASSWORD: " && read -r ASF_PASSWORD && printf '\n' && stty echo + fi + else + ASF_PASSWORD="***INVALID***" + fi + + export ASF_PASSWORD +} + +function is_dry_run { + [[ "$DRY_RUN" = 1 ]] +} + +function is_debug { + [[ "${DEBUG}" = 1 ]] +} + +function check_get_passwords { + for env in "$@"; do + if [ -z "${!env}" ]; then + echo "The environment variable $env is not set. Please enter the password or passphrase." + echo + # shellcheck disable=SC2229 + stty -echo && printf "%s : " "$env" && read -r "$env" && printf '\n' && stty echo + fi + # shellcheck disable=SC2163 + export "$env" + done +} + +function check_needed_vars { + local missing=0 + for env in "$@"; do + if [ -z "${!env}" ]; then + echo "$env must be set to run this script" + (( missing++ )) + else + # shellcheck disable=SC2163 + export "$env" + fi + done + (( missing > 0 )) && exit_with_usage + return 0 +} + +function init_locale { + local locale_value + OS="$(uname -s)" + case "${OS}" in + Darwin*) locale_value="en_US.UTF-8";; + Linux*) locale_value="C.UTF-8";; + *) error "unknown OS";; + esac + export LC_ALL="$locale_value" + export LANG="$locale_value" +} + +# Initializes JAVA_VERSION to the version of the JVM in use. +function init_java { + if [ -z "$JAVA_HOME" ]; then + error "JAVA_HOME is not set." + fi + JAVA_VERSION=$("${JAVA_HOME}"/bin/javac -version 2>&1 | cut -d " " -f 2) + echo "java version: $JAVA_VERSION" + export JAVA_VERSION +} + +function init_python { + if ! [ -x "$(command -v python2)" ]; then + error 'python2 needed by yetus. Install or add link? E.g: sudo ln -sf /usr/bin/python2.7 /usr/local/bin/python2' + fi + echo "python version: $(python2 --version)" +} + +# Set MVN +function init_mvn { + if [ -n "$MAVEN_HOME" ]; then + MVN=("${MAVEN_HOME}/bin/mvn") + elif [ "$(type -P mvn)" ]; then + MVN=(mvn) + else + error "MAVEN_HOME is not set nor is mvn on the current path." + fi + # Add batch mode. + MVN=("${MVN[@]}" -B) + export MVN + echo -n "mvn version: " + "${MVN[@]}" --version + configure_maven +} + +function init_yetus { + declare YETUS_VERSION + if [ -z "${YETUS_HOME}" ]; then + error "Missing Apache Yetus." + fi + # Work around yetus bug by asking test-patch for the version instead of rdm. + YETUS_VERSION=$("${YETUS_HOME}/bin/test-patch" --version) + echo "Apache Yetus version ${YETUS_VERSION}" +} + +function configure_maven { + # Add timestamps to mvn logs. + MAVEN_OPTS="-Dorg.slf4j.simpleLogger.showDateTime=true -Dorg.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss ${MAVEN_OPTS}" + # Suppress gobs of "Download from central:" messages + MAVEN_OPTS="-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn ${MAVEN_OPTS}" + MAVEN_LOCAL_REPO="${REPO:-$(pwd)/$(mktemp -d hbase-repo-XXXXX)}" + [[ -d "$MAVEN_LOCAL_REPO" ]] || mkdir -p "$MAVEN_LOCAL_REPO" + MAVEN_SETTINGS_FILE="${MAVEN_LOCAL_REPO}/tmp-settings.xml" + MVN=("${MVN[@]}" --settings "${MAVEN_SETTINGS_FILE}") + export MVN MAVEN_OPTS MAVEN_SETTINGS_FILE MAVEN_LOCAL_REPO + export ASF_USERNAME ASF_PASSWORD + # reference passwords from env rather than storing in the settings.xml file. + cat <<'EOF' > "$MAVEN_SETTINGS_FILE" +<?xml version="1.0" encoding="UTF-8"?> +<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> + <localRepository>/${env.MAVEN_LOCAL_REPO}</localRepository> + <servers> + <server><id>apache.snapshots.https</id><username>${env.ASF_USERNAME}</username> + <password>${env.ASF_PASSWORD}</password></server> + <server><id>apache.releases.https</id><username>${env.ASF_USERNAME}</username> + <password>${env.ASF_PASSWORD}</password></server> + </servers> + <profiles> + <profile> + <activation> + <activeByDefault>true</activeByDefault> + </activation> + <properties> + <gpg.keyname>${env.GPG_KEY}</gpg.keyname> + </properties> + </profile> + </profiles> +</settings> +EOF +} + +# clone of the repo, deleting anything that exists in the working directory named after the project. +# optionally with auth details for pushing. +function git_clone_overwrite { + local asf_repo + if [ -z "${PROJECT}" ] || [ "${PROJECT}" != "${PROJECT#/}" ]; then + error "Project name must be defined and not start with a '/'. PROJECT='${PROJECT}'" + fi + rm -rf "${PROJECT}" + + if [[ -z "${GIT_REPO}" ]]; then + asf_repo="gitbox.apache.org/repos/asf/${PROJECT}.git" + echo "[INFO] clone will be of the gitbox repo for ${PROJECT}." + if [ -n "${ASF_USERNAME}" ] && [ -n "${ASF_PASSWORD}" ]; then + # Ugly! + encoded_username=$(python -c "import urllib; print urllib.quote('''$ASF_USERNAME''', '')") + encoded_password=$(python -c "import urllib; print urllib.quote('''$ASF_PASSWORD''', '')") + GIT_REPO="https://$encoded_username:$encoded_password@${asf_repo}" + else + GIT_REPO="https://${asf_repo}" + fi + else + echo "[INFO] clone will be of provided git repo." + fi + # N.B. we use the shared flag because the clone is short lived and if a local repo repo was + # given this will let us refer to objects there directly instead of hardlinks or copying. + # The option is silently ignored for non-local repositories. see the note on git help clone + # for the --shared option for details. + git clone --shared -b "${GIT_BRANCH}" -- "${GIT_REPO}" "${PROJECT}" + # If this was a host local git repo then add in an alternates and remote that will + # work back on the host if the RM needs to do any post-processing steps, i.e. pushing the git tag + # for more info see 'git help remote' and 'git help repository-layout'. + if [ -n "$HOST_GIT_REPO" ]; then + echo "${HOST_GIT_REPO}/objects" >> "${PROJECT}/.git/objects/info/alternates" + (cd "${PROJECT}"; git remote add host "${HOST_GIT_REPO}") + fi +} + +function start_step { + local name=$1 + if [ -z "${name}" ]; then + name="${FUNCNAME[1]}" + fi + echo "$(date -u +'%Y-%m-%dT%H:%M:%SZ') ${name} start" >&2 + get_ctime +} + +function stop_step { + local name=$2 + local start_time=$1 + local stop_time + if [ -z "${name}" ]; then + name="${FUNCNAME[1]}" + fi + stop_time="$(get_ctime)" + echo "$(date -u +'%Y-%m-%dT%H:%M:%SZ') ${name} stop ($((stop_time - start_time)) seconds)" +} + +# Writes report into cwd! +# TODO should have option for maintenance release that include LimitedPrivate in report +function generate_api_report { + local project="$1" + local previous_tag="$2" + local release_tag="$3" + local previous_version + local timing_token + timing_token="$(start_step)" + # Generate api report. + "${project}"/dev-support/checkcompatibility.py --annotation \ + org.apache.yetus.audience.InterfaceAudience.Public \ + "$previous_tag" "$release_tag" + previous_version="$(echo "${previous_tag}" | sed -e 's/rel\///')" + cp "${project}/target/compat-check/report.html" "./api_compare_${previous_version}_to_${release_tag}.html" + stop_step "${timing_token}" +} + +# Look up the Jira name associated with project. +# Currently all the 'hbase-*' projects share the same HBASE jira name. This works because, +# by convention, the HBASE jira "Fix Version" field values have the sub-project name pre-pended, +# as in "hbase-operator-tools-1.0.0". +# TODO: For non-hbase-related projects, enhance this to use Jira API query instead of text lookup. +function get_jira_name { + local project="$1" + local jira_name + case "${project}" in + phoenix-tephra) jira_name="TEPHRA";; + phoenix-omid) jira_name="OMID";; + phoenix*) jira_name="PHOENIX";; + *) jira_name="";; + esac + if [[ -z "$jira_name" ]]; then + error "Sorry, can't determine the Jira name for project $project" + fi + echo "$jira_name" +} + +# Update the CHANGES.md +# DOES NOT DO COMMITS! Caller should do that. +# requires yetus to have a defined home already. +# yetus requires python2 to be on the path. +function update_releasenotes { + local project_dir="$1" + local jira_fix_version="$2" + local jira_project + local timing_token + timing_token="$(start_step)" + jira_project="$(get_jira_name "$(basename "$project_dir")")" + "${YETUS_HOME}/bin/releasedocmaker" -p "${jira_project}" --fileversions -v "${jira_fix_version}" \ + -l --sortorder=newer --skip-credits + pwd + # First clear out the changes written by previous RCs. + if [ -f "${project_dir}/CHANGES.md" ]; then + sed -i -e \ + "/^## Release ${jira_fix_version}/,/^## Release/ {//!d; /^## Release ${jira_fix_version}/d;}" \ + "${project_dir}/CHANGES.md" || true + fi + if [ -f "${project_dir}/RELEASENOTES.md" ]; then + sed -i -e \ + "/^# ${jira_project} ${jira_fix_version} Release Notes/,/^# ${jira_project}/{//!d; /^# ${jira_project} ${jira_fix_version} Release Notes/d;}" \ + "${project_dir}/RELEASENOTES.md" || true + fi + + # The releasedocmaker call above generates RELEASENOTES.X.X.X.md and CHANGELOG.X.X.X.md. + if [ -f "${project_dir}/CHANGES.md" ]; then + # To insert into project's CHANGES.md...need to cut the top off the + # CHANGELOG.X.X.X.md file removing license and first line and then + # insert it after the license comment closing where we have a + # DO NOT REMOVE marker text! + sed -i -e '/## Release/,$!d' "CHANGELOG.${jira_fix_version}.md" + sed -i -e "/DO NOT REMOVE/r CHANGELOG.${jira_fix_version}.md" "${project_dir}/CHANGES.md" + else + mv "CHANGELOG.${jira_fix_version}.md" "${project_dir}/CHANGES.md" + fi + if [ -f "${project_dir}/RELEASENOTES.md" ]; then + # Similar for RELEASENOTES but slightly different. + sed -i -e '/Release Notes/,$!d' "RELEASENOTES.${jira_fix_version}.md" + sed -i -e "/DO NOT REMOVE/r RELEASENOTES.${jira_fix_version}.md" \ + "${project_dir}/RELEASENOTES.md" + else + mv "RELEASENOTES.${jira_fix_version}.md" "${project_dir}/RELEASENOTES.md" + fi + stop_step "${timing_token}" +} + +# Make src release. +# Takes as arguments first the project name -- e.g. hbase or hbase-operator-tools +# -- and then the version string. Expects to find checkout adjacent to this script +# named for 'project', the first arg passed. +# Expects the following three defines in the environment: +# - GPG needs to be defined, with the path to GPG: defaults 'gpg'. +# - GIT_REF which is the tag to create the tgz from: defaults to 'master'. +# For example: +# $ GIT_REF="master" make_src_release hbase-operator-tools 1.0.0 +make_src_release() { + # Tar up the src and sign and hash it. + local project="${1}" + local version="${2}" + local base_name="${project}-${version}" + local timing_token + timing_token="$(start_step)" + rm -rf "${base_name}"-src* + tgz="${base_name}-src.tar.gz" + cd "${project}" || exit + git clean -d -f -x + git archive --format=tar.gz --output="../${tgz}" --prefix="${base_name}/" "${GIT_REF:-master}" + cd .. || exit + $GPG "${GPG_ARGS[@]}" --armor --output "${tgz}.asc" --detach-sig "${tgz}" + $GPG "${GPG_ARGS[@]}" --print-md SHA512 "${tgz}" > "${tgz}.sha512" + stop_step "${timing_token}" +} + +# Make binary release. +# Takes as arguments first the project name -- e.g. hbase or hbase-operator-tools +# -- and then the version string. Expects to find checkout adjacent to this script +# named for 'project', the first arg passed. +# Expects the following three defines in the environment: +# - GPG needs to be defined, with the path to GPG: defaults 'gpg'. +# - GIT_REF which is the tag to create the tgz from: defaults to 'master'. +# - MVN Default is "mvn -B --settings $MAVEN_SETTINGS_FILE". +# For example: +# $ GIT_REF="master" make_src_release hbase-operator-tools 1.0.0 +make_binary_release() { + local project="${1}" + local version="${2}" + local base_name="${project}-${version}" + local timing_token + timing_token="$(start_step)" + rm -rf "${base_name}"-bin* + cd "$project" || exit + + git clean -d -f -x + # Three invocations of maven. This seems to work. One to + # populate the repo, another to build the site, and then + # a third to assemble the binary artifact. Trying to do + # all in the one invocation fails; a problem in our + # assembly spec to in maven. TODO. Meantime, three invocations. + #"${MVN[@]}" clean install -DskipTests + #"${MVN[@]}" site -DskipTests + kick_gpg_agent + "${MVN[@]}" clean package -DskipTests -Dcheckstyle.skip=true "${PUBLISH_PROFILES[@]}" + + # Check there is a bin gz output. The build may not produce one: e.g. hbase-thirdparty. + local f_bin_prefix="./${PROJECT}-assembly/target/${base_name}" + if ls "${f_bin_prefix}"*-bin.tar.gz &>/dev/null; then + cp "${f_bin_prefix}"*-bin.tar.gz .. + cd .. || exit + for i in "${base_name}"*-bin.tar.gz; do + "${GPG}" "${GPG_ARGS[@]}" --armour --output "${i}.asc" --detach-sig "${i}" + "${GPG}" "${GPG_ARGS[@]}" --print-md SHA512 "${i}" > "${i}.sha512" + done + else + cd .. || exit + echo "No ${f_bin_prefix}*-bin.tar.gz product; expected?" + fi + + stop_step "${timing_token}" +} + +# "Wake up" the gpg agent so it responds properly to maven-gpg-plugin, and doesn't cause timeout. +# Specifically this is done between invocation of 'mvn site' and 'mvn assembly:single', because +# the 'site' build takes long enough that the gpg-agent does become unresponsive and the following +# 'assembly' build (where gpg signing occurs) experiences timeout, without this "kick". +function kick_gpg_agent { + # All that's needed is to run gpg on a random file + # TODO could we just call gpg-connect-agent /bye + local i + i="$(mktemp)" + echo "This is a test file" > "$i" + "${GPG}" "${GPG_ARGS[@]}" --armour --output "${i}.asc" --detach-sig "${i}" + rm "$i" "$i.asc" +} + +# Do maven command to set version into local pom +function maven_set_version { #input: <version_to_set> + local this_version="$1" + echo "${MVN[@]}" versions:set -DnewVersion="$this_version" + "${MVN[@]}" versions:set -DnewVersion="$this_version" | grep -v "no value" # silence logs + #Hacking around nonstandard maven submodules + for i in phoenix-hbase-compat-*; do + if [ -e "$i" ]; then + "${MVN[@]}" -pl $i versions:set -DnewVersion="$this_version" | grep -v "no value" # silence logs + fi + done +} + +# Do maven command to read version from local pom +function maven_get_version { + # shellcheck disable=SC2016 + "${MVN[@]}" -q -N -Dexec.executable="echo" -Dexec.args='${project.version}' exec:exec +} + +# Do maven deploy to snapshot or release artifact repository, with checks. +function maven_deploy { #inputs: <snapshot|release> <log_file_path> + local timing_token + # Invoke with cwd=$PROJECT + local deploy_type="$1" + local mvn_log_file="$2" #secondary log file used later to extract staged_repo_id + if [[ "$deploy_type" != "snapshot" && "$deploy_type" != "release" ]]; then + error "unrecognized deploy type, must be 'snapshot'|'release'" + fi + if [[ -z "$mvn_log_file" ]] || ! touch "$mvn_log_file"; then + error "must provide writable maven log output filepath" + fi + timing_token=$(start_step) + # shellcheck disable=SC2153 + if [[ "$deploy_type" == "snapshot" ]] && ! [[ "$RELEASE_VERSION" =~ -SNAPSHOT$ ]]; then + error "Snapshots must have a version with suffix '-SNAPSHOT'; you gave version '$RELEASE_VERSION'" + elif [[ "$deploy_type" == "release" ]] && [[ "$RELEASE_VERSION" =~ SNAPSHOT ]]; then + error "Non-snapshot release version must not include the word 'SNAPSHOT'; you gave version '$RELEASE_VERSION'" + fi + # Publish ${PROJECT} to Maven repo + # shellcheck disable=SC2154 + echo "Publishing ${PROJECT} checkout at '$GIT_REF' ($git_hash)" + echo "Publish version is $RELEASE_VERSION" + # Coerce the requested version + maven_set_version "$RELEASE_VERSION" + # Prepare for signing + kick_gpg_agent + declare -a mvn_goals=(clean install) + if ! is_dry_run; then + mvn_goals=("${mvn_goals[@]}" deploy) + fi + echo "${MVN[@]}" -DskipTests -Dcheckstyle.skip=true "${PUBLISH_PROFILES[@]}" \ + "${mvn_goals[@]}" + echo "Logging to ${mvn_log_file}. This will take a while..." + rm -f "$mvn_log_file" + # The tortuous redirect in the next command allows mvn's stdout and stderr to go to mvn_log_file, + # while also sending stderr back to the caller. + # shellcheck disable=SC2094 + if ! "${MVN[@]}" -DskipTests -Dcheckstyle.skip=true "${PUBLISH_PROFILES[@]}" \ + "${mvn_goals[@]}" 1>> "$mvn_log_file" 2> >( tee -a "$mvn_log_file" >&2 ); then + error "Deploy build failed, for details see log at '$mvn_log_file'." + fi + echo "BUILD SUCCESS." + stop_step "${timing_token}" + return 0 +} + +# guess the host os +# * DARWIN +# * LINUX +function get_host_os() { + uname -s | tr '[:lower:]' '[:upper:]' +} diff --git a/dev/create-release/vote.tmpl b/dev/create-release/vote.tmpl new file mode 100644 index 0000000..8f0638b --- /dev/null +++ b/dev/create-release/vote.tmpl @@ -0,0 +1,31 @@ +Please vote on this Apache ${PROJECT_TEXT} release candidate, +${PROJECT}-${RELEASE_TAG} + +The VOTE will remain open for at least 72 hours. + +[ ] +1 Release this package as Apache ${PROJECT_TEXT} ${RELEASE_VERSION} +[ ] -1 Do not release this package because ... + +The tag to be voted on is ${RELEASE_TAG}: + + https://github.com/apache/${PROJECT}/tree/${RELEASE_TAG} + +The release files, including signatures, digests, as well as CHANGES.md +and RELEASENOTES.md included in this RC can be found at: + + https://dist.apache.org/repos/dist/dev/phoenix/${RELEASE_TAG}/ + +Maven artifacts are available in a staging repository at: + + https://repository.apache.org/content/repositories/${staged_repo_id}/ + +Artifacts were signed with the ${GPG_KEY} key which can be found in: + + https://dist.apache.org/repos/dist/release/phoenix/KEYS + +To learn more about Apache ${PROJECT_TEXT}, please see + + http://phoenix.apache.org/ + +Thanks, +Your Phoenix Release Manager diff --git a/phoenix-assembly/pom.xml b/phoenix-assembly/pom.xml index 0a88f97..2e7b6a1 100644 --- a/phoenix-assembly/pom.xml +++ b/phoenix-assembly/pom.xml @@ -107,7 +107,7 @@ <goal>single</goal> </goals> <configuration> - <finalName>phoenix-${project.version}</finalName> + <finalName>phoenix-${project.version}-bin</finalName> <attach>false</attach> <tarLongFileMode>gnu</tarLongFileMode> <appendAssemblyId>false</appendAssemblyId> @@ -117,23 +117,6 @@ <tarLongFileMode>posix</tarLongFileMode> </configuration> </execution> - <execution> - <id>package-to-source-tar</id> - <phase>package</phase> - <goals> - <goal>single</goal> - </goals> - <configuration> - <finalName>phoenix-${project.version}-source</finalName> - <attach>false</attach> - <tarLongFileMode>gnu</tarLongFileMode> - <appendAssemblyId>false</appendAssemblyId> - <descriptors> - <descriptor>src/build/src.xml</descriptor> - </descriptors> - <tarLongFileMode>posix</tarLongFileMode> - </configuration> - </execution> </executions> </plugin> </plugins> diff --git a/phoenix-assembly/src/build/components/all-common-files.xml b/phoenix-assembly/src/build/components/all-common-files.xml index a17a138..9348397 100644 --- a/phoenix-assembly/src/build/components/all-common-files.xml +++ b/phoenix-assembly/src/build/components/all-common-files.xml @@ -30,6 +30,13 @@ <include>*.txt</include> </includes> </fileSet> + <!-- Copy the binary license files --> + <fileSet> + <directory>${project.basedir}/../dev/release_files</directory> + <fileMode>0644</fileMode> + <directoryMode>0755</directoryMode> + <outputDirectory>/</outputDirectory> + </fileSet> <!-- Copy the executable files from the bin directory --> <fileSet> <directory>${project.basedir}/../bin</directory> @@ -77,19 +84,14 @@ <fileMode>0644</fileMode> <directoryMode>0755</directoryMode> </fileSet> - <fileSet> - <directory>${project.basedir}/../phoenix-pherf/target</directory> - <outputDirectory>phoenix-pherf</outputDirectory> - <includes> - <include>*.zip</include> - </includes> - <fileMode>0644</fileMode> - <directoryMode>0755</directoryMode> - </fileSet> <fileSet> - <directory>${project.basedir}/../python</directory> - <fileMode>0644</fileMode> - <directoryMode>0755</directoryMode> + <directory>${project.basedir}/../phoenix-pherf/target</directory> + <outputDirectory>phoenix-pherf</outputDirectory> + <includes> + <include>*.zip</include> + </includes> + <fileMode>0644</fileMode> + <directoryMode>0755</directoryMode> </fileSet> </fileSets> </component> diff --git a/phoenix-assembly/src/build/src.xml b/phoenix-assembly/src/build/src.xml deleted file mode 100644 index cfa73d6..0000000 --- a/phoenix-assembly/src/build/src.xml +++ /dev/null @@ -1,127 +0,0 @@ -<!-- - - 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. - ---> -<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd"> - <!--This 'all' id is not appended to the produced bundle because we do this: http://maven.apache.org/plugins/maven-assembly-plugin/faq.html#required-classifiers --> - <id>src</id> - <formats> - <format>tar.gz</format> - </formats> - <includeBaseDirectory>true</includeBaseDirectory> - - <moduleSets> - <moduleSet> - <!-- Enable access to all projects in the current multimodule build. Eclipse - says this is an error, but builds from the command line just fine. --> - <useAllReactorProjects>true</useAllReactorProjects> - <!-- Include all the sources in the top directory --> - <sources> - <fileSets> - <fileSet> - <!-- Make sure this excludes is same as the phoenix-hadoop2-compat - excludes below --> - <excludes> - <exclude>target/</exclude> - <exclude>test/</exclude> - <exclude>.classpath</exclude> - <exclude>.project</exclude> - <exclude>.settings/</exclude> - </excludes> - </fileSet> - </fileSets> - </sources> - </moduleSet> - </moduleSets> - - <fileSets> - <!--This one is weird. When we assemble src, it'll be default profile which - at the moment is hadoop1. But we should include the hadoop2 compat module - too so can build hadoop2 from src --> - <fileSet> - <directory>${project.basedir}/..</directory> - <fileMode>0644</fileMode> - <directoryMode>0755</directoryMode> - <includes> - <include>phoenix-*</include> - </includes> - <excludes> - <exclude>target/</exclude> - <exclude>test/</exclude> - <exclude>.classpath</exclude> - <exclude>.project</exclude> - <exclude>.settings/</exclude> - </excludes> - </fileSet> - <fileSet> - <!--Get misc project files --> - <directory>${project.basedir}/..</directory> - <outputDirectory>/</outputDirectory> - <includes> - <include>*.txt</include> - <include>*.md</include> - <include>pom.xml</include> - </includes> - </fileSet> - <!-- Top level directories --> - <fileSet> - <directory>${project.basedir}/../bin</directory> - <outputDirectory>bin</outputDirectory> - <fileMode>0755</fileMode> - <directoryMode>0755</directoryMode> - <includes> - <include>*.py</include> - <include>*.sh</include> - </includes> - </fileSet> - <fileSet> - <directory>${project.basedir}/../bin</directory> - <outputDirectory>bin</outputDirectory> - <fileMode>0644</fileMode> - <directoryMode>0755</directoryMode> - <excludes> - <exclude>*.py*</exclude> - <exclude>*.sh*</exclude> - </excludes> - </fileSet> - <fileSet> - <directory>${project.basedir}/../dev</directory> - <fileMode>0644</fileMode> - <directoryMode>0755</directoryMode> - </fileSet> - <fileSet> - <directory>${project.basedir}/../docs</directory> - <fileMode>0644</fileMode> - <directoryMode>0755</directoryMode> - </fileSet> - <fileSet> - <directory>${project.basedir}/../examples</directory> - <fileMode>0644</fileMode> - <directoryMode>0755</directoryMode> - </fileSet> - <fileSet> - <directory>${project.basedir}/../python</directory> - <fileMode>0644</fileMode> - <directoryMode>0755</directoryMode> - </fileSet> - </fileSets> - -</assembly> diff --git a/pom.xml b/pom.xml index 658c98d..aa6e874 100644 --- a/pom.xml +++ b/pom.xml @@ -578,6 +578,9 @@ <excludes> <!-- Header on changelog isn't normal --> <exclude>CHANGES</exclude> + <!-- create-release scripts --> + <exclude>dev/create-release/README.txt</exclude> + <exclude>dev/create-release/vote.tmpl</exclude> <!-- IDE configuration --> <exclude>dev/phoenix.importorder</exclude> <!-- Release L&N -->