Skip to content

Commit 2b898b0

Browse files
Verify staging repository before destructive Nexus actions
Before publish-release.sh promotes a staging repo to Maven Central and before cancel-rc.sh drops one, perform a four-layer check on the provided staging_repo_id: 1. Profile == org.apache.parquet (catches wrong-project IDs) 2. State == closed (catches already-released, already-dropped, never-closed) 3. Artifact present: parquet-common-<version>.pom in the repo (catches wrong-version IDs and empty repos) 4. Description contains "Apache Parquet <version> RC<rc>" (catches wrong-RC of the same version line) The first three are unambiguous facts about the repo and hard-fail the script. The description is a free-text field editable in the Nexus UI, so a mismatch hard-fails by default but can be bypassed with --allow-description-mismatch (also exposed as a workflow input) for recovery scenarios. This closes a gap that exists in both Polaris's and the original Parquet manual procedure: "type the staging repo ID into a form field" replaces the Nexus-UI human eyeball check without a verification step in between.
1 parent 4356ea1 commit 2b898b0

7 files changed

Lines changed: 390 additions & 12 deletions

File tree

.github/workflows/release-cancel-rc.yml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ on:
3434
description: 'Nexus staging repository ID to drop (e.g., orgapacheparquet-1234)'
3535
required: true
3636
type: string
37+
allow_description_mismatch:
38+
description: 'Bypass the staging-repo description check (recovery only)'
39+
required: false
40+
type: boolean
41+
default: false
3742
dry_run:
3843
description: 'Dry run mode (no actual changes)'
3944
required: false
@@ -68,8 +73,10 @@ jobs:
6873
INPUT_VERSION: ${{ inputs.version }}
6974
INPUT_RC_NUMBER: ${{ inputs.rc_number }}
7075
INPUT_STAGING_REPO_ID: ${{ inputs.staging_repo_id }}
76+
INPUT_ALLOW_DESCRIPTION_MISMATCH: ${{ inputs.allow_description_mismatch && '1' || '0' }}
7177
run: |
72-
./release/bin/cancel-rc.sh \
73-
"${INPUT_VERSION}" \
74-
"${INPUT_RC_NUMBER}" \
75-
"${INPUT_STAGING_REPO_ID}"
78+
args=("${INPUT_VERSION}" "${INPUT_RC_NUMBER}" "${INPUT_STAGING_REPO_ID}")
79+
if [[ "${INPUT_ALLOW_DESCRIPTION_MISMATCH}" == "1" ]]; then
80+
args+=(--allow-description-mismatch)
81+
fi
82+
./release/bin/cancel-rc.sh "${args[@]}"

.github/workflows/release-publish.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ on:
3535
description: 'Nexus staging repository ID (e.g., orgapacheparquet-1234)'
3636
required: true
3737
type: string
38+
allow_description_mismatch:
39+
description: 'Bypass the staging-repo description check (recovery only)'
40+
required: false
41+
type: boolean
42+
default: false
3843
dry_run:
3944
description: 'Dry run mode (no actual changes)'
4045
required: false
@@ -81,9 +86,13 @@ jobs:
8186
INPUT_VERSION: ${{ inputs.version }}
8287
INPUT_RC_NUMBER: ${{ inputs.rc_number }}
8388
INPUT_STAGING_REPO_ID: ${{ inputs.staging_repo_id }}
89+
INPUT_ALLOW_DESCRIPTION_MISMATCH: ${{ inputs.allow_description_mismatch && '1' || '0' }}
8490
run: |
8591
args=("${INPUT_VERSION}" "${INPUT_STAGING_REPO_ID}")
8692
if [[ -n "${INPUT_RC_NUMBER}" ]]; then
8793
args+=(--rc "${INPUT_RC_NUMBER}")
8894
fi
95+
if [[ "${INPUT_ALLOW_DESCRIPTION_MISMATCH}" == "1" ]]; then
96+
args+=(--allow-description-mismatch)
97+
fi
8998
./release/bin/publish-release.sh "${args[@]}"

release/bin/cancel-rc.sh

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ source "${LIBS_DIR}/_nexus.sh"
3434
# ---------------------------------------------------------------------------
3535
function usage {
3636
cat <<EOF
37-
Usage: $0 <version> <rc-num> <staging-repo-id>
37+
Usage: $0 <version> <rc-num> <staging-repo-id> [--allow-description-mismatch]
3838
3939
Cancel a release candidate after a failed vote.
4040
@@ -43,6 +43,15 @@ Arguments:
4343
rc-num RC number to cancel (e.g., 0)
4444
staging-repo-id Nexus staging repository ID (e.g., orgapacheparquet-1234)
4545
46+
Options:
47+
--allow-description-mismatch
48+
Bypass the staging-repo description check (for recovery scenarios)
49+
50+
Before dropping the staging repo, this script verifies that it belongs
51+
to org.apache.parquet, is in 'closed' state, contains
52+
${NEXUS_VERIFY_ARTIFACT_ID:-parquet-common}-<version>.pom, and has a
53+
description matching "Apache Parquet <version> RC<num>".
54+
4655
Environment variables:
4756
DRY_RUN Set to 0 for real execution (default: 1)
4857
NEXUS_USERNAME Apache Nexus username
@@ -59,14 +68,40 @@ EOF
5968
# ---------------------------------------------------------------------------
6069
# Parse arguments
6170
# ---------------------------------------------------------------------------
62-
if [[ $# -lt 3 ]]; then
63-
print_error "Expected 3 arguments, got $#"
71+
version=""
72+
rc_num=""
73+
staging_repo_id=""
74+
allow_description_mismatch=0
75+
positional=()
76+
77+
while [[ $# -gt 0 ]]; do
78+
case "$1" in
79+
--allow-description-mismatch)
80+
allow_description_mismatch=1
81+
shift
82+
;;
83+
--help|-h)
84+
usage 0
85+
;;
86+
-*)
87+
print_error "Unknown option: $1"
88+
usage 1
89+
;;
90+
*)
91+
positional+=("$1")
92+
shift
93+
;;
94+
esac
95+
done
96+
97+
if [[ ${#positional[@]} -lt 3 ]]; then
98+
print_error "Expected 3 positional arguments (version, rc-num, staging-repo-id), got ${#positional[@]}"
6499
usage 1
65100
fi
66101

67-
version="$1"
68-
rc_num="$2"
69-
staging_repo_id="$3"
102+
version="${positional[0]}"
103+
rc_num="${positional[1]}"
104+
staging_repo_id="${positional[2]}"
70105

71106
# ---------------------------------------------------------------------------
72107
# Validate inputs
@@ -102,6 +137,18 @@ step_summary "| Version | \`${version}\` |"
102137
step_summary "| RC tag | \`${rc_tag}\` |"
103138
step_summary "| Staging repo | \`${staging_repo_id}\` |"
104139

140+
# ---------------------------------------------------------------------------
141+
# Step 0: Verify staging repository before any destructive action
142+
# ---------------------------------------------------------------------------
143+
step_summary ""
144+
step_summary "### Staging Repository Verification"
145+
146+
if ! nexus_verify_staging_repo "${staging_repo_id}" "${version}" "${rc_num}" "${allow_description_mismatch}"; then
147+
step_summary "Staging repository verification: **FAILED**"
148+
exit 1
149+
fi
150+
step_summary "Staging repository \`${staging_repo_id}\` verified"
151+
105152
# ---------------------------------------------------------------------------
106153
# Step 1: Drop Nexus staging repo
107154
# ---------------------------------------------------------------------------

release/bin/publish-release.sh

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ source "${LIBS_DIR}/_maven.sh"
3535
# ---------------------------------------------------------------------------
3636
function usage {
3737
cat <<EOF
38-
Usage: $0 <version> <staging-repo-id> [--rc <num>]
38+
Usage: $0 <version> <staging-repo-id> [--rc <num>] [--allow-description-mismatch]
3939
4040
Publish a release after the vote passes.
4141
@@ -45,8 +45,15 @@ Arguments:
4545
4646
Options:
4747
--rc <num> RC number that passed the vote (default: auto-detect latest)
48+
--allow-description-mismatch
49+
Bypass the staging-repo description check (for recovery scenarios)
4850
--help Show this help
4951
52+
Before any destructive action, this script verifies that the staging repo
53+
belongs to org.apache.parquet, is in 'closed' state, contains
54+
${NEXUS_VERIFY_ARTIFACT_ID:-parquet-common}-<version>.pom, and has a
55+
description matching "Apache Parquet <version> RC<num>".
56+
5057
The next development version is auto-computed by incrementing the patch
5158
version (e.g., 1.18.0 -> 1.18.1-SNAPSHOT).
5259
@@ -71,6 +78,7 @@ EOF
7178
version=""
7279
staging_repo_id=""
7380
rc_num=""
81+
allow_description_mismatch=0
7482
positional=()
7583

7684
while [[ $# -gt 0 ]]; do
@@ -83,6 +91,10 @@ while [[ $# -gt 0 ]]; do
8391
rc_num="$2"
8492
shift 2
8593
;;
94+
--allow-description-mismatch)
95+
allow_description_mismatch=1
96+
shift
97+
;;
8698
--help|-h)
8799
usage 0
88100
;;
@@ -181,6 +193,18 @@ step_summary "| Staging repo | \`${staging_repo_id}\` |"
181193
step_summary "| Next dev version | \`${next_dev_version}-SNAPSHOT\` |"
182194
step_summary "| Commit | \`${rc_commit}\` |"
183195

196+
# ---------------------------------------------------------------------------
197+
# Step 0: Verify staging repository before any destructive action
198+
# ---------------------------------------------------------------------------
199+
step_summary ""
200+
step_summary "### Staging Repository Verification"
201+
202+
if ! nexus_verify_staging_repo "${staging_repo_id}" "${version}" "${rc_num}" "${allow_description_mismatch}"; then
203+
step_summary "Staging repository verification: **FAILED**"
204+
exit 1
205+
fi
206+
step_summary "Staging repository \`${staging_repo_id}\` verified"
207+
184208
# ---------------------------------------------------------------------------
185209
# Step 1: Move SVN artifacts from dist/dev to dist/release
186210
# ---------------------------------------------------------------------------

release/libs/_constants.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,13 @@ APACHE_DIST_DEV_PATH="/dev/parquet"
2929
APACHE_DIST_RELEASE_PATH="/release/parquet"
3030

3131
NEXUS_BASE_URL=${NEXUS_BASE_URL:-"https://repository.apache.org/service/local"}
32+
NEXUS_CONTENT_BASE_URL=${NEXUS_CONTENT_BASE_URL:-"https://repository.apache.org/content/repositories"}
3233
NEXUS_STAGING_GROUP_URL="https://repository.apache.org/content/groups/staging/org/apache/parquet/"
3334

35+
NEXUS_PROFILE_NAME="org.apache.parquet"
36+
NEXUS_VERIFY_GROUP_PATH="org/apache/parquet"
37+
NEXUS_VERIFY_ARTIFACT_ID="parquet-common"
38+
3439
DRY_RUN=${DRY_RUN:-1}
3540

3641
VERSION_REGEX="([0-9]+)\.([0-9]+)\.([0-9]+)"

release/libs/_nexus.sh

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,111 @@ function nexus_drop_staging_repo {
6868
_nexus_bulk_action "drop" "${repo_id}" "${description}"
6969
}
7070

71+
function nexus_get_staging_repo_metadata {
72+
local repo_id="$1"
73+
local url="${NEXUS_BASE_URL}/staging/repository/${repo_id}"
74+
75+
nexus_repo_profile=""
76+
nexus_repo_state=""
77+
nexus_repo_description=""
78+
79+
if [[ ${DRY_RUN:-1} -eq 1 ]]; then
80+
print_command "Dry-run, WOULD GET ${url} (skipping metadata fetch)"
81+
nexus_repo_profile="${NEXUS_PROFILE_NAME}"
82+
nexus_repo_state="closed"
83+
nexus_repo_description="DRY_RUN_DESCRIPTION"
84+
return 0
85+
fi
86+
87+
local response
88+
if ! response=$(curl --fail --silent --show-error \
89+
-K <(printf 'user = "%s:%s"\n' "${NEXUS_USERNAME}" "${NEXUS_PASSWORD}") \
90+
-H "Accept: application/json" \
91+
"${url}"); then
92+
print_error "Failed to fetch staging repository metadata for ${repo_id}"
93+
return 1
94+
fi
95+
96+
nexus_repo_profile=$(echo "${response}" | jq -r '.profileName // ""')
97+
nexus_repo_state=$(echo "${response}" | jq -r '.type // ""')
98+
nexus_repo_description=$(echo "${response}" | jq -r '.description // ""')
99+
100+
if [[ -z "${nexus_repo_profile}" || -z "${nexus_repo_state}" ]]; then
101+
print_error "Unable to parse staging repository metadata for ${repo_id}"
102+
return 1
103+
fi
104+
105+
return 0
106+
}
107+
108+
function nexus_check_staging_artifact {
109+
local repo_id="$1"
110+
local version="$2"
111+
local artifact_url="${NEXUS_CONTENT_BASE_URL}/${repo_id}/${NEXUS_VERIFY_GROUP_PATH}/${NEXUS_VERIFY_ARTIFACT_ID}/${version}/${NEXUS_VERIFY_ARTIFACT_ID}-${version}.pom"
112+
113+
if [[ ${DRY_RUN:-1} -eq 1 ]]; then
114+
print_command "Dry-run, WOULD HEAD ${artifact_url}"
115+
return 0
116+
fi
117+
118+
if ! curl --fail --silent --show-error --head \
119+
-K <(printf 'user = "%s:%s"\n' "${NEXUS_USERNAME}" "${NEXUS_PASSWORD}") \
120+
"${artifact_url}" >/dev/null; then
121+
print_error "Expected artifact not found in staging repo: ${artifact_url}"
122+
return 1
123+
fi
124+
125+
return 0
126+
}
127+
128+
function nexus_verify_staging_repo {
129+
local repo_id="$1"
130+
local version="$2"
131+
local rc_num="$3"
132+
local allow_description_mismatch="${4:-0}"
133+
134+
local expected_description="Apache Parquet ${version} RC${rc_num}"
135+
136+
print_info "Verifying staging repository ${repo_id}..."
137+
138+
if ! nexus_get_staging_repo_metadata "${repo_id}"; then
139+
return 1
140+
fi
141+
142+
if [[ "${nexus_repo_profile}" != "${NEXUS_PROFILE_NAME}" ]]; then
143+
print_error "Profile mismatch: expected '${NEXUS_PROFILE_NAME}', got '${nexus_repo_profile}'"
144+
print_error "This staging repo does not belong to Apache Parquet."
145+
return 1
146+
fi
147+
148+
if [[ "${nexus_repo_state}" != "closed" ]]; then
149+
print_error "Unexpected state: expected 'closed', got '${nexus_repo_state}'"
150+
print_error "Staging repo must be closed (not open/released/dropped) before this action."
151+
return 1
152+
fi
153+
154+
if ! nexus_check_staging_artifact "${repo_id}" "${version}"; then
155+
print_error "Verification failed: ${NEXUS_VERIFY_ARTIFACT_ID}-${version}.pom not found in staging repo."
156+
print_error "This staging repo does not appear to contain ${version} artifacts."
157+
return 1
158+
fi
159+
160+
if [[ ${DRY_RUN:-1} -ne 1 && "${nexus_repo_description}" != *"${expected_description}"* ]]; then
161+
print_warning "Description mismatch: expected to contain '${expected_description}'"
162+
print_warning "Actual description: '${nexus_repo_description}'"
163+
if [[ "${allow_description_mismatch}" != "1" ]]; then
164+
print_error "Refusing to proceed. Re-run with --allow-description-mismatch to bypass."
165+
return 1
166+
fi
167+
print_warning "Continuing despite description mismatch (--allow-description-mismatch)."
168+
fi
169+
170+
print_info "Staging repository ${repo_id} verified."
171+
return 0
172+
}
173+
71174
function nexus_find_open_staging_repo {
72-
local profile_name="${1:-org.apache.parquet}"
175+
local profile_name="${1:-${NEXUS_PROFILE_NAME}}"
73176

74177
print_info "Searching for open staging repository for ${profile_name}..."
75178

0 commit comments

Comments
 (0)