blob: 1dd3f1c4e7fdbcba4314caa3a9f3ddd116984035 [file]
#!/bin/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.
#
[[ -n "${_NEXUS_LOADED:-}" ]] && return 0 2>/dev/null || true
_NEXUS_LOADED=1
LIBS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${LIBS_DIR}/_constants.sh"
source "${LIBS_DIR}/_exec.sh"
function _nexus_bulk_action {
local action="$1"
local repo_id="$2"
local description="$3"
local url="${NEXUS_BASE_URL}/staging/bulk/${action}"
local payload
payload=$(jq -n --arg id "${repo_id}" --arg desc "${description}" \
'{"data": {"stagedRepositoryIds": [$id], "description": $desc}}')
print_info "Nexus ${action}: repo_id=${repo_id}"
if [[ ${DRY_RUN:-1} -ne 1 ]]; then
print_command "Executing 'curl --fail -X POST ${url}' (credentials via stdin)"
curl --fail --silent --show-error \
-K <(printf 'user = "%s:%s"\n' "${NEXUS_USERNAME}" "${NEXUS_PASSWORD}") \
-H "Content-Type: application/json" \
-d "${payload}" \
"${url}"
else
print_command "Dry-run, WOULD POST to ${url} with payload for repo ${repo_id}"
fi
}
function nexus_close_staging_repo {
local repo_id="$1"
local description="${2:-Closing staging repository}"
_nexus_bulk_action "close" "${repo_id}" "${description}"
}
function nexus_release_staging_repo {
local repo_id="$1"
local description="${2:-Releasing staging repository}"
_nexus_bulk_action "promote" "${repo_id}" "${description}"
}
function nexus_drop_staging_repo {
local repo_id="$1"
local description="${2:-Dropping staging repository}"
_nexus_bulk_action "drop" "${repo_id}" "${description}"
}
function nexus_get_staging_repo_metadata {
local repo_id="$1"
local url="${NEXUS_BASE_URL}/staging/repository/${repo_id}"
nexus_repo_profile=""
nexus_repo_state=""
nexus_repo_description=""
if [[ ${DRY_RUN:-1} -eq 1 ]]; then
print_command "Dry-run, WOULD GET ${url} (skipping metadata fetch)"
nexus_repo_profile="${NEXUS_PROFILE_NAME}"
nexus_repo_state="closed"
nexus_repo_description="DRY_RUN_DESCRIPTION"
return 0
fi
local response
if ! response=$(curl --fail --silent --show-error \
-K <(printf 'user = "%s:%s"\n' "${NEXUS_USERNAME}" "${NEXUS_PASSWORD}") \
-H "Accept: application/json" \
"${url}"); then
print_error "Failed to fetch staging repository metadata for ${repo_id}"
return 1
fi
nexus_repo_profile=$(echo "${response}" | jq -r '.profileName // ""')
nexus_repo_state=$(echo "${response}" | jq -r '.type // ""')
nexus_repo_description=$(echo "${response}" | jq -r '.description // ""')
if [[ -z "${nexus_repo_profile}" || -z "${nexus_repo_state}" ]]; then
print_error "Unable to parse staging repository metadata for ${repo_id}"
return 1
fi
return 0
}
function nexus_check_staging_artifact {
local repo_id="$1"
local version="$2"
local artifact_url="${NEXUS_CONTENT_BASE_URL}/${repo_id}/${NEXUS_VERIFY_GROUP_PATH}/${NEXUS_VERIFY_ARTIFACT_ID}/${version}/${NEXUS_VERIFY_ARTIFACT_ID}-${version}.pom"
if [[ ${DRY_RUN:-1} -eq 1 ]]; then
print_command "Dry-run, WOULD HEAD ${artifact_url}"
return 0
fi
if ! curl --fail --silent --show-error --head \
-K <(printf 'user = "%s:%s"\n' "${NEXUS_USERNAME}" "${NEXUS_PASSWORD}") \
"${artifact_url}" >/dev/null; then
print_error "Expected artifact not found in staging repo: ${artifact_url}"
return 1
fi
return 0
}
function nexus_verify_staging_repo {
local repo_id="$1"
local version="$2"
local rc_num="$3"
local allow_description_mismatch="${4:-0}"
local expected_description="Apache Parquet ${version} RC${rc_num}"
print_info "Verifying staging repository ${repo_id}..."
if ! nexus_get_staging_repo_metadata "${repo_id}"; then
return 1
fi
if [[ "${nexus_repo_profile}" != "${NEXUS_PROFILE_NAME}" ]]; then
print_error "Profile mismatch: expected '${NEXUS_PROFILE_NAME}', got '${nexus_repo_profile}'"
print_error "This staging repo does not belong to Apache Parquet."
return 1
fi
if [[ "${nexus_repo_state}" != "closed" ]]; then
print_error "Unexpected state: expected 'closed', got '${nexus_repo_state}'"
print_error "Staging repo must be closed (not open/released/dropped) before this action."
return 1
fi
if ! nexus_check_staging_artifact "${repo_id}" "${version}"; then
print_error "Verification failed: ${NEXUS_VERIFY_ARTIFACT_ID}-${version}.pom not found in staging repo."
print_error "This staging repo does not appear to contain ${version} artifacts."
return 1
fi
if [[ ${DRY_RUN:-1} -ne 1 && "${nexus_repo_description}" != *"${expected_description}"* ]]; then
print_warning "Description mismatch: expected to contain '${expected_description}'"
print_warning "Actual description: '${nexus_repo_description}'"
if [[ "${allow_description_mismatch}" != "1" ]]; then
print_error "Refusing to proceed. Re-run with --allow-description-mismatch to bypass."
return 1
fi
print_warning "Continuing despite description mismatch (--allow-description-mismatch)."
fi
print_info "Staging repository ${repo_id} verified."
return 0
}
function nexus_find_open_staging_repo {
local profile_name="${1:-${NEXUS_PROFILE_NAME}}"
print_info "Searching for open staging repository for ${profile_name}..."
if [[ ${DRY_RUN:-1} -eq 1 ]]; then
print_command "Dry-run, WOULD search Nexus for open staging repo"
staging_repo_id="DRY-RUN-REPO-ID"
return 0
fi
local response
if ! response=$(curl --fail --silent --show-error \
-K <(printf 'user = "%s:%s"\n' "${NEXUS_USERNAME}" "${NEXUS_PASSWORD}") \
"${NEXUS_BASE_URL}/staging/profile_repositories"); then
print_error "Failed to query Nexus staging repositories"
return 1
fi
# Filter on <profileName> exactly (not on a substring of <repositoryId>),
# and reject the result if multiple open repos exist for the profile —
# Nexus permits this but it indicates a leftover from a previous failed
# run that the operator must clean up before we can pick the right one.
local matches
matches=$(echo "${response}" | \
NEXUS_PROFILE_NAME_VAR="${profile_name}" python3 -c "
import sys, os, xml.etree.ElementTree as ET
profile = os.environ['NEXUS_PROFILE_NAME_VAR']
try:
tree = ET.parse(sys.stdin)
except ET.ParseError as e:
print('PARSE_ERROR:' + str(e), file=sys.stderr)
sys.exit(2)
for repo in tree.findall('.//stagingProfileRepository'):
repo_type = repo.find('type')
repo_profile = repo.find('profileName')
repo_id = repo.find('repositoryId')
if repo_type is None or repo_id is None or repo_profile is None:
continue
if repo_type.text == 'open' and repo_profile.text == profile:
print(repo_id.text)
" 2>&1)
local py_status=$?
if [[ ${py_status} -ne 0 ]]; then
print_error "Failed to parse Nexus staging response: ${matches}"
return 1
fi
local match_count
match_count=$(echo -n "${matches}" | grep -c . || true)
if [[ ${match_count} -eq 0 ]]; then
print_error "No open staging repository found for profile '${profile_name}'"
return 1
fi
if [[ ${match_count} -gt 1 ]]; then
print_error "Multiple open staging repositories found for profile '${profile_name}':"
echo "${matches}" | while read -r line; do print_error " - ${line}"; done
print_error "Please drop or close the stale repos in Nexus and re-run."
return 1
fi
staging_repo_id=$(echo "${matches}" | head -n 1)
print_info "Found staging repository: ${staging_repo_id}"
return 0
}