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"
 }

Reply via email to