This is an automated email from the ASF dual-hosted git repository.
jshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new ed9a65a0d0 [#10448] feat(release): enhance do-release scripts to
support non-interactive mode (#10449)
ed9a65a0d0 is described below
commit ed9a65a0d0ae52487f71b9accfc864b56678d5a4
Author: Yuhui <[email protected]>
AuthorDate: Thu Mar 19 12:02:35 2026 +0800
[#10448] feat(release): enhance do-release scripts to support
non-interactive mode (#10449)
### What changes were proposed in this pull request?
- `do-release.sh`: add `-y` (force) flag, `-s/-r/-p/-t` options for full
CLI control; skip interactive prompts when in force mode; add `set -euo
pipefail` for stricter error handling
- `release-util.sh`: read release info from environment variables when
in force mode; auto-detect latest branch and RC count; fix duplicate
`JAVA_VERSION` assignment in `init_java`
### Why are the changes needed?
The release process requires manual input at multiple steps.
Non-interactive mode allows the release pipeline to be driven by
automation without human intervention.
Fix: #10448
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
Manually verified with `bash -n` syntax check on all modified scripts.
---
dev/release/do-release.sh | 95 ++++++++++++++++++++++++++++++++++++++-------
dev/release/release-util.sh | 88 +++++++++++++++++++++++++++--------------
2 files changed, 141 insertions(+), 42 deletions(-)
diff --git a/dev/release/do-release.sh b/dev/release/do-release.sh
index 55ec5dada7..963513e71a 100755
--- a/dev/release/do-release.sh
+++ b/dev/release/do-release.sh
@@ -20,22 +20,81 @@
# Referred from Apache Spark's release script
# dev/create-release/do-release.sh
-SELF=$(cd $(dirname $0) && pwd)
+set -euo pipefail
-while getopts ":b:n" opt; do
+SELF=$(cd "$(dirname "$0")" && pwd)
+
+while getopts ":b:s:p:t:r:nyh" opt; do
case $opt in
b) GIT_BRANCH=$OPTARG ;;
n) DRY_RUN=1 ;;
- \?) error "Invalid option: $OPTARG" ;;
+ s) RELEASE_STEP=$OPTARG ;;
+ p) GPG_PASSPHRASE=$OPTARG ;;
+ t) ASF_PASSWORD=$OPTARG ;;
+ r) RC_COUNT=$OPTARG ;;
+ y) FORCE=1 ;;
+ h)
+ echo "Usage: $0 [options]"
+ echo ""
+ echo "Options:"
+ echo " -b <branch> Git branch to release (e.g., branch-1.2)"
+ echo " -s <step> Release step to execute: tag, build, docs,
publish, finalize"
+ echo " -r <num> Release candidate number (e.g., 6 for rc6)"
+ echo " -n Dry run mode (skip publishing)"
+ echo " -p <pass> GPG passphrase (insecure; prefer GPG_PASSPHRASE
env var)"
+ echo " -t <pass> ASF password (insecure; prefer ASF_PASSWORD env
var)"
+ echo " -y Non-interactive mode: skip all confirmation
prompts"
+ echo " -h Show this help message"
+ echo ""
+ echo "Examples:"
+ echo " # Interactive mode (prompted for all inputs):"
+ echo " $0"
+ echo ""
+ echo " # Non-interactive full release (use env vars for secrets):"
+ echo " export GPG_PASSPHRASE='my-gpg-pass'"
+ echo " export ASF_PASSWORD='my-asf-pass'"
+ echo " export PYPI_API_TOKEN='my-pypi-token'"
+ echo " export ASF_USERNAME='myuser'"
+ echo " $0 -b branch-1.2 -r 1 -y"
+ echo ""
+ echo " # Run a single step only (e.g., finalize):"
+ echo " $0 -b branch-1.2 -r 1 -s finalize -y"
+ echo ""
+ echo " # Dry run to test without publishing:"
+ echo " $0 -n -b branch-1.2"
+ exit 0
+ ;;
+ :) echo "Option -$OPTARG requires an argument." >&2; exit 1 ;;
+ \?) echo "Invalid option: -$OPTARG" >&2; exit 1 ;;
esac
done
-DRY_RUN=${DRY_RUN:-0}
-export DRY_RUN
+export RUNNING_IN_DOCKER=${RUNNING_IN_DOCKER:-0}
+export DRY_RUN=${DRY_RUN:-0}
+export FORCE=${FORCE:-0}
+export RC_COUNT=${RC_COUNT:-0}
+export RELEASE_STEP=${RELEASE_STEP:-}
+export GIT_BRANCH=${GIT_BRANCH:-}
+export RELEASE_VERSION=${RELEASE_VERSION:-}
+export RELEASE_TAG=${RELEASE_TAG:-}
+export ASF_PASSWORD=${ASF_PASSWORD:-}
+export GPG_PASSPHRASE=${GPG_PASSPHRASE:-}
+
+if [[ "${RC_COUNT}" != "0" ]] && ! [[ "${RC_COUNT}" =~ ^[1-9][0-9]*$ ]]; then
+ echo "Error: RC number must be a positive integer, got: '${RC_COUNT}'" >&2
+ exit 1
+fi
+
+if [ -n "${RELEASE_STEP}" ]; then
+ case "${RELEASE_STEP}" in
+ tag|build|docs|publish|finalize) ;;
+ *) echo "Error: invalid release step '${RELEASE_STEP}'. Valid steps: tag,
build, docs, publish, finalize" >&2; exit 1 ;;
+ esac
+fi
cmds=("git" "gpg" "svn" "twine" "shasum" "sha1sum" "jq" "make")
for cmd in "${cmds[@]}"; do
- if ! command -v $cmd &> /dev/null; then
+ if ! command -v "$cmd" &> /dev/null; then
echo "$cmd is required to run this script."
exit 1
fi
@@ -49,7 +108,7 @@ fi
. "$SELF/release-util.sh"
if ! is_dry_run; then
- if [[ -z "$PYPI_API_TOKEN" ]]; then
+ if [[ -z "${PYPI_API_TOKEN:-}" ]]; then
echo 'The environment variable PYPI_API_TOKEN is not set. Exiting.'
exit 1
fi
@@ -57,17 +116,21 @@ fi
if [ "$RUNNING_IN_DOCKER" = "1" ]; then
# Inside docker, need to import the GPG key stored in the current directory.
- echo $GPG_PASSPHRASE | $GPG --passphrase-fd 0 --import "$SELF/gpg.key"
+ printf '%s\n' "${GPG_PASSPHRASE:-}" | $GPG --passphrase-fd 0 --import
"$SELF/gpg.key"
# We may need to adjust the path since JAVA_HOME may be overridden by the
driver script.
- if [ -n "$JAVA_HOME" ]; then
+ if [ -n "${JAVA_HOME:-}" ]; then
export PATH="$JAVA_HOME/bin:$PATH"
else
# JAVA_HOME for the openjdk package.
export JAVA_HOME=/usr
fi
+
+ # Tags are always created by the driver script before entering docker; skip
here.
+ SKIP_TAG=1
else
- # Outside docker, need to ask for information about the release.
+ # Outside docker, collect release information.
+ # In force/non-interactive mode (-y), read_config uses env vars and skips
prompts.
get_release_info
fi
@@ -80,8 +143,12 @@ if should_build "tag" && [ $SKIP_TAG = 0 ]; then
run_silent "Creating release tag $RELEASE_TAG..." "tag.log" \
"$SELF/release-tag.sh"
echo "It may take some time for the tag to be synchronized to github."
- echo "Press enter when you've verified that the new tag ($RELEASE_TAG) is
available."
- read
+ if is_force; then
+ echo "Force mode: skipping wait."
+ else
+ echo "Press enter when you've verified that the new tag ($RELEASE_TAG) is
available."
+ read
+ fi
else
echo "Skipping tag creation for $RELEASE_TAG."
fi
@@ -107,7 +174,9 @@ else
echo "Skipping publish step."
fi
-if [ ! -z "$RELEASE_STEP" ] && [ "$RELEASE_STEP" = "finalize" ]; then
+if [ "$RELEASE_STEP" = "finalize" ]; then
run_silent "Finalizing release" "finalize.log" \
"$SELF/release-build.sh" finalize
fi
+
+echo "Release build and publish completed"
diff --git a/dev/release/release-util.sh b/dev/release/release-util.sh
index 1ab77c5aff..d343036442 100755
--- a/dev/release/release-util.sh
+++ b/dev/release/release-util.sh
@@ -34,12 +34,28 @@ function error {
function read_config {
local PROMPT="$1"
local DEFAULT="$2"
+ local ENV_VAR_NAME="$3" # Optional: env var name to use in non-interactive
(force) mode
local REPLY=
- read -p "$PROMPT [$DEFAULT]: " REPLY
+ # In force/non-interactive mode, the env var must be set; error if missing.
+ if is_force; then
+ if [ -n "$ENV_VAR_NAME" ] && [ -n "${!ENV_VAR_NAME:-}" ]; then
+ echo "${!ENV_VAR_NAME}"
+ return
+ else
+ error "Force mode requires '$PROMPT' to be set via environment variable
${ENV_VAR_NAME:-<none>}."
+ fi
+ fi
+
+ if [ -n "$DEFAULT" ]; then
+ read -p "$PROMPT [$DEFAULT]: " REPLY
+ else
+ read -p "$PROMPT: " REPLY
+ fi
+
local RETVAL="${REPLY:-$DEFAULT}"
if [ -z "$RETVAL" ]; then
- error "$PROMPT is must be provided."
+ error "$PROMPT must be provided."
fi
echo "$RETVAL"
}
@@ -58,9 +74,8 @@ function run_silent {
echo "Command: $@"
echo "Log file: $LOG_FILE"
- "$@" 1>"$LOG_FILE" 2>&1
-
- local EC=$?
+ local EC=0
+ "$@" 1>"$LOG_FILE" 2>&1 || EC=$?
if [ $EC != 0 ]; then
echo "Command FAILED. Check full logs for details."
tail "$LOG_FILE"
@@ -81,7 +96,7 @@ function check_for_tag {
function get_release_info {
if [ -z "$GIT_BRANCH" ]; then
- # If no branch is specified, found out the latest branch from the repo.
+ # If no branch is specified, find out the latest branch from the repo.
GIT_BRANCH=$(git ls-remote --heads "$ASF_REPO" |
grep -e "refs/heads/branch-.*" |
awk '{print $2}' |
@@ -90,7 +105,7 @@ function get_release_info {
cut -d/ -f3)
fi
- export GIT_BRANCH=$(read_config "Branch" "$GIT_BRANCH")
+ export GIT_BRANCH=$(read_config "Branch" "$GIT_BRANCH" GIT_BRANCH)
# Find the current version for the branch.
local VERSION=$(curl -s "$ASF_REPO_WEBUI/$GIT_BRANCH/gradle.properties" |
@@ -115,33 +130,41 @@ function get_release_info {
local PREV_REL_REV=$((REV - 1))
local PREV_REL_TAG="v${SHORT_VERSION}.${PREV_REL_REV}"
if check_for_tag "$PREV_REL_TAG"; then
- RC_COUNT=1
+ NRC_COUNT=1
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" "v${RELEASE_VERSION}-rc*" |
wc -l)
- RC_COUNT=$((RC_COUNT + 1))
+ NRC_COUNT=$(git ls-remote --tags "$ASF_REPO" "v${RELEASE_VERSION}-rc*" |
wc -l)
+ NRC_COUNT=$((NRC_COUNT + 1))
fi
else
REV=$((REV + 1))
NEXT_VERSION="${SHORT_VERSION}.${REV}-SNAPSHOT"
- RC_COUNT=1
+ NRC_COUNT=1
fi
export NEXT_VERSION
- export RELEASE_VERSION=$(read_config "Release" "$RELEASE_VERSION")
+ export RELEASE_VERSION=$(read_config "Release" "$RELEASE_VERSION"
RELEASE_VERSION)
- RC_COUNT=$(read_config "RC #" "$RC_COUNT")
+ # If -r was explicitly provided (non-zero), override the auto-detected
NRC_COUNT
+ if [ "${RC_COUNT:-0}" -gt 0 ]; then
+ NRC_COUNT=$RC_COUNT
+ fi
+ RC_COUNT=$(read_config "RC #" "$NRC_COUNT" NRC_COUNT)
export RC_COUNT
# Check if the RC already exists, and if re-creating the RC, skip tag
creation.
RELEASE_TAG="v${RELEASE_VERSION}-rc${RC_COUNT}"
SKIP_TAG=0
if check_for_tag "$RELEASE_TAG"; then
- read -p "$RELEASE_TAG already exists. Continue anyway [y/n]? " ANSWER
- if [ "$ANSWER" != "y" ]; then
- error "Exiting."
+ if is_force; then
+ echo "$RELEASE_TAG already exists. Force continuing."
+ else
+ read -p "$RELEASE_TAG already exists. Continue anyway [y/n]? " ANSWER
+ if [ "$ANSWER" != "y" ]; then
+ error "Exiting."
+ fi
fi
SKIP_TAG=1
fi
@@ -155,23 +178,23 @@ function get_release_info {
if [[ $SKIP_TAG = 0 ]]; then
GIT_REF="$GIT_BRANCH"
fi
- GIT_REF=$(read_config "Ref" "$GIT_REF")
+ GIT_REF=$(read_config "Ref" "$GIT_REF" GIT_REF)
fi
export GIT_REF
export GRAVITINO_PACKAGE_VERSION="$RELEASE_TAG"
# Gather some user information.
- if [ -z "$ASF_USERNAME" ]; then
- export ASF_USERNAME=$(read_config "ASF user" "$LOGNAME")
+ if [ -z "${ASF_USERNAME:-}" ]; then
+ export ASF_USERNAME=$(read_config "ASF user" "$LOGNAME" ASF_USERNAME)
fi
- if [ -z "$GIT_NAME" ]; then
+ if [ -z "${GIT_NAME:-}" ]; then
GIT_NAME=$(git config user.name || echo "")
- export GIT_NAME=$(read_config "Full name" "$GIT_NAME")
+ export GIT_NAME=$(read_config "Full name" "$GIT_NAME" GIT_NAME)
fi
export GIT_EMAIL="[email protected]"
- export GPG_KEY=$(read_config "GPG key" "$GIT_EMAIL")
+ export GPG_KEY=$(read_config "GPG key" "$GIT_EMAIL" GPG_KEY)
cat <<EOF
================
@@ -188,21 +211,25 @@ E-MAIL: $GIT_EMAIL
================
EOF
- read -p "Is this info correct [y/n]? " ANSWER
- if [ "$ANSWER" != "y" ]; then
- echo "Exiting."
- exit 1
+ if is_force; then
+ echo "Force mode: proceeding without confirmation."
+ else
+ read -p "Is this info correct [y/n]? " ANSWER
+ if [ "$ANSWER" != "y" ]; then
+ echo "Exiting."
+ exit 1
+ fi
fi
if ! is_dry_run; then
- if [ -z "$ASF_PASSWORD" ]; then
+ if [ -z "${ASF_PASSWORD:-}" ]; then
stty -echo && printf "ASF password: " && read ASF_PASSWORD && printf
'\n' && stty echo
fi
else
ASF_PASSWORD="***INVALID***"
fi
- if [ -z "$GPG_PASSPHRASE" ]; then
+ if [ -z "${GPG_PASSPHRASE:-}" ]; then
stty -echo && printf "GPG passphrase: " && read GPG_PASSPHRASE && printf
'\n' && stty echo
fi
@@ -214,6 +241,10 @@ function is_dry_run {
[[ $DRY_RUN = 1 ]]
}
+function is_force {
+ [[ $FORCE = 1 ]]
+}
+
# Initializes JAVA_VERSION to the version of the JVM in use.
function init_java {
if [ -z "$JAVA_HOME" ]; then
@@ -226,7 +257,6 @@ function init_java {
else
error "javac not found in ${JAVA_HOME}/bin, please ensure you have a JDK
installed."
fi
- JAVA_VERSION=$("${JAVA_HOME}"/bin/javac -version 2>&1 | cut -d " " -f 2)
export JAVA_VERSION
echo "Java version is $JAVA_VERSION"
}