| #!/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 |
| } |