|
| 1 | +#!/bin/bash |
| 2 | +# |
| 3 | +# Copyright (c) 2025 Oracle and/or its affiliates. |
| 4 | +# |
| 5 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | +# you may not use this file except in compliance with the License. |
| 7 | +# You may obtain a copy of the License at |
| 8 | +# |
| 9 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | +# |
| 11 | +# Unless required by applicable law or agreed to in writing, software |
| 12 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | +# See the License for the specific language governing permissions and |
| 15 | +# limitations under the License. |
| 16 | +# |
| 17 | + |
| 18 | +set -o pipefail || true # trace ERR through pipes |
| 19 | +set -o errtrace || true # trace ERR through commands and functions |
| 20 | +set -o errexit || true # exit the script if any statement returns a non-true return value |
| 21 | + |
| 22 | +on_error(){ |
| 23 | + CODE="${?}" && \ |
| 24 | + set +x && \ |
| 25 | + printf "[ERROR] Error(code=%s) occurred at %s:%s command: %s\n" \ |
| 26 | + "${CODE}" "${BASH_SOURCE[0]}" "${LINENO}" "${BASH_COMMAND}" >&2 |
| 27 | +} |
| 28 | +trap on_error ERR |
| 29 | + |
| 30 | +usage(){ |
| 31 | + cat <<EOF |
| 32 | +
|
| 33 | +DESCRIPTION: Upload staged artifacts to the Central Portal |
| 34 | +
|
| 35 | +USAGE: |
| 36 | +
|
| 37 | +$(basename "${0}") [OPTIONS] --directory=DIR CMD |
| 38 | +
|
| 39 | + --dir=DIR |
| 40 | + Set the staging directory to use. |
| 41 | +
|
| 42 | + --description=DESCRIPTION |
| 43 | + Set the staging repository description to use. |
| 44 | + %{version} can be used to subsitute the release version. |
| 45 | +
|
| 46 | + --help |
| 47 | + Prints the usage and exits. |
| 48 | +
|
| 49 | + CMD: |
| 50 | +
|
| 51 | + upload_release |
| 52 | + Upload staging directory to a release repository |
| 53 | +
|
| 54 | + upload_snapshot |
| 55 | + Uploading staging directory to a snapshots repository |
| 56 | +EOF |
| 57 | +} |
| 58 | + |
| 59 | +# parse command line args |
| 60 | +ARGS=( ) |
| 61 | +while (( ${#} > 0 )); do |
| 62 | + case ${1} in |
| 63 | + "--dir="*) |
| 64 | + STAGING_DIR=${1#*=} |
| 65 | + shift |
| 66 | + ;; |
| 67 | + "--description="*) |
| 68 | + DESCRIPTION=${1#*=} |
| 69 | + shift |
| 70 | + ;; |
| 71 | + "--help") |
| 72 | + usage |
| 73 | + exit 0 |
| 74 | + ;; |
| 75 | + "upload_release"|"upload_snapshot") |
| 76 | + COMMAND="${1}" |
| 77 | + shift |
| 78 | + ;; |
| 79 | + *) |
| 80 | + ARGS+=( "${1}" ) |
| 81 | + shift |
| 82 | + ;; |
| 83 | + esac |
| 84 | +done |
| 85 | +readonly ARGS |
| 86 | +readonly COMMAND |
| 87 | + |
| 88 | +# copy stdout as fd 6 and redirect stdout to stderr |
| 89 | +# this allows us to use fd 6 for returning data |
| 90 | +exec 6>&1 1>&2 |
| 91 | + |
| 92 | +case ${COMMAND} in |
| 93 | +"upload_release") |
| 94 | + if [ -z "${DESCRIPTION}" ] ; then |
| 95 | + echo "ERROR: description required" >&2 |
| 96 | + usage |
| 97 | + exit 1 |
| 98 | + fi |
| 99 | + ;; |
| 100 | +"upload_snapshot") |
| 101 | + # no-op |
| 102 | + ;; |
| 103 | +"") |
| 104 | + echo "ERROR: no command provided" >&2 |
| 105 | + usage |
| 106 | + exit 1 |
| 107 | + ;; |
| 108 | +*) |
| 109 | + echo "ERROR: unknown command ${COMMAND}" >&2 |
| 110 | + usage |
| 111 | + exit 1 |
| 112 | + ;; |
| 113 | +esac |
| 114 | + |
| 115 | +if [ -z "${STAGING_DIR}" ] ; then |
| 116 | + echo "ERROR: --staging-dir is required" >&2 |
| 117 | + usage |
| 118 | + exit 1 |
| 119 | +fi |
| 120 | + |
| 121 | +if [ -z "${CENTRAL_USER}" ] ; then |
| 122 | + echo "ERROR: CENTRAL_USER environment is not set" >&2 |
| 123 | + usage |
| 124 | + exit 1 |
| 125 | +fi |
| 126 | +if [ -z "${CENTRAL_PASSWORD}" ] ; then |
| 127 | + echo "ERROR: CENTRAL_PASSWORD environment is not set" >&2 |
| 128 | + usage |
| 129 | + exit 1 |
| 130 | +fi |
| 131 | + |
| 132 | +if [ ! -d "${STAGING_DIR}" ] ; then |
| 133 | + echo "ERROR: Invalid staging directory: ${STAGING_DIR}" >&2 |
| 134 | + exit 1 |
| 135 | +fi |
| 136 | + |
| 137 | +# Central Portal URL for releases |
| 138 | +readonly CENTRAL_URL="https://central.sonatype.com/api/v1" |
| 139 | +# Central SNAPSHOT URL |
| 140 | +readonly SNAPSHOT_URL="https://central.sonatype.com/repository/maven-snapshots" |
| 141 | + |
| 142 | +BEARER=$(printf "%s:%s" "${CENTRAL_USER}" "${CENTRAL_PASSWORD}" | base64) |
| 143 | + |
| 144 | +find_version() { |
| 145 | + local versions version |
| 146 | + |
| 147 | + # List the "version" directories |
| 148 | + versions=$(while read -r v ; do |
| 149 | + dirname "${v}" | xargs basename |
| 150 | + done < <(find "${STAGING_DIR}" -type f -name "*.pom" -print) | sort | uniq) |
| 151 | + |
| 152 | + # Enforce one version per staging directory |
| 153 | + for v in ${versions} ; do |
| 154 | + if [ -n "${version}" ] ; then |
| 155 | + echo "ERROR: staging directory contains more than one version: ${versions}" >&2 |
| 156 | + return 1 |
| 157 | + fi |
| 158 | + version="${v}" |
| 159 | + done |
| 160 | + |
| 161 | + if [ -z "${version}" ] ; then |
| 162 | + echo "ERROR: version not found" >&2 |
| 163 | + return 1 |
| 164 | + fi |
| 165 | + echo "${version}" |
| 166 | +} |
| 167 | + |
| 168 | +upload_snapshot() { |
| 169 | + echo "Uploading SNAPSHOT..." >&2 |
| 170 | + local version |
| 171 | + version=$(find_version) |
| 172 | + |
| 173 | + # Make sure version ends in -SNAPSHOT |
| 174 | + if [[ "${version}" != *-SNAPSHOT ]]; then |
| 175 | + echo "ERROR: Version ${version} is NOT a SNAPSHOT version" >&2 |
| 176 | + exit 1 |
| 177 | + fi |
| 178 | + |
| 179 | + nexus_upload "${SNAPSHOT_URL}" "${STAGING_DIR}" |
| 180 | +} |
| 181 | + |
| 182 | +upload_release() { |
| 183 | + echo "Uploading release..." >&2 |
| 184 | + local version |
| 185 | + version=$(find_version) |
| 186 | + |
| 187 | + # Make sure version does NOT end in -SNAPSHOT |
| 188 | + if [[ "${v}" = *-SNAPSHOT ]]; then |
| 189 | + echo "ERROR: Version ${version} is a SNAPSHOT version" >&2 |
| 190 | + exit 1 |
| 191 | + fi |
| 192 | + |
| 193 | + deployment_id="$(central_upload "${CENTRAL_URL}" "${STAGING_DIR}")" |
| 194 | + central_finish "${deployment_id}" |
| 195 | +} |
| 196 | + |
| 197 | +# Upload contents of the staging directory to central portal |
| 198 | +# arg1: base URL of upload portal |
| 199 | +# arg2: staging directory |
| 200 | +# prints deployment ID |
| 201 | +central_upload() { |
| 202 | + local version |
| 203 | + version=$(find_version) |
| 204 | + |
| 205 | + printf "Uploading artifacts...\n" >&2 |
| 206 | + readonly UPLOAD_BUNDLE=io-helidon-build-tools-artifacts-${version}.zip |
| 207 | + rm -f "${UPLOAD_BUNDLE}" |
| 208 | + printf "Creating artifact bundle %s...\n" "${UPLOAD_BUNDLE}" >&2 |
| 209 | + (cd "${2}"; zip -ryq "../${UPLOAD_BUNDLE}" .) |
| 210 | + |
| 211 | + local responseFile statusFile |
| 212 | + responseFile=$(mktemp) |
| 213 | + statusFile=$(mktemp) |
| 214 | + |
| 215 | + printf "Uploading %s to %s...\n" "${UPLOAD_BUNDLE}" "${1}" >&2 |
| 216 | + # Upload bundle in one shot |
| 217 | + # publishingType of USER_MANAGED acts like "staging". Artifacts are uploaded and verified but not published. |
| 218 | + curl --request POST \ |
| 219 | + --retry 2 \ |
| 220 | + --header "Authorization: Bearer ${BEARER}" \ |
| 221 | + --write-out "%{stderr}%{http_code} %{url_effective}\n%{stdout}%{http_code}" \ |
| 222 | + --form bundle=@"${UPLOAD_BUNDLE}" \ |
| 223 | + -o "${responseFile}" \ |
| 224 | + "${1}/publisher/upload?name=io-helidon-build-tools-${version}&publishingType=USER_MANAGED" > "${statusFile}" |
| 225 | + |
| 226 | + # handle errors |
| 227 | + if [ "$(cat "${statusFile}")" != "201" ] ; then |
| 228 | + printf "[ERROR] %s\n" "$(cat "${responseFile}")" >&2 |
| 229 | + exit 1 |
| 230 | + fi |
| 231 | + |
| 232 | + # If success then output deployment ID |
| 233 | + cat "${responseFile}" |
| 234 | +} |
| 235 | + |
| 236 | +# Poll deployment status until operation is complete. |
| 237 | +# arg1: deployment ID |
| 238 | +central_finish() { |
| 239 | + printf "\n\nVerifying upload status of %s...\n\n" "$1" >&2 |
| 240 | + |
| 241 | + while true; do |
| 242 | + local deploymentState |
| 243 | + deploymentState=$(central_get_deployment_state "$1") |
| 244 | + printf "%s...\n" "${deploymentState}" >&2 |
| 245 | + case ${deploymentState} in |
| 246 | + "PENDING") |
| 247 | + ;; |
| 248 | + "VALIDATING") |
| 249 | + ;; |
| 250 | + "VALIDATED") |
| 251 | + printf "Done. Bits are uploaded." >&2 |
| 252 | + exit |
| 253 | + ;; |
| 254 | + "PUBLISHING") |
| 255 | + ;; |
| 256 | + "PUBLISHED") |
| 257 | + printf "!!!! Oh No! Artifacts have been published!!!! That should not have happened." >&2 |
| 258 | + exit |
| 259 | + ;; |
| 260 | + "FAILED") |
| 261 | + exit |
| 262 | + ;; |
| 263 | + esac |
| 264 | + sleep 10 |
| 265 | + done |
| 266 | +} |
| 267 | + |
| 268 | +# Gets the status of a deployment from central portal |
| 269 | +# arg1: deployment ID |
| 270 | +# Prints deployment state for the given ID: |
| 271 | +# PENDING, VALIDATING, VALIDATED, PUBLISHING, PUBLISHED, FAILED |
| 272 | +# Should never be PUBLISHING or PUBLISHED since our publishingType is USER_MANAGED |
| 273 | +central_get_deployment_state() { |
| 274 | + curl --request POST \ |
| 275 | + -s \ |
| 276 | + --retry 3 \ |
| 277 | + --header "Authorization: Bearer ${BEARER}" \ |
| 278 | + "${CENTRAL_URL}/publisher/status?id=${1}" \ |
| 279 | + | jq -r ".deploymentState" |
| 280 | +} |
| 281 | + |
| 282 | +# Upload to a nexus repository. This is used to support SNAPSHOT releases |
| 283 | +# arg1: base URL |
| 284 | +# arg2: staging directory |
| 285 | +nexus_upload() { |
| 286 | + printf "\nUploading artifacts...\n" >&2 |
| 287 | + |
| 288 | + local tmpfile |
| 289 | + tmpfile=$(mktemp) |
| 290 | + |
| 291 | + # Generate a curl config file for all files to deploy |
| 292 | + # Use -T <file> --url <url> for each file |
| 293 | + while read -r i ; do |
| 294 | + echo "-T ${2}/${i}" >> "${tmpfile}" |
| 295 | + echo "--url ${1}/${i}" >> "${tmpfile}" |
| 296 | + done < <(find "${2}" -type f | cut -c $((${#2} + 2))-) |
| 297 | + |
| 298 | + # Upload |
| 299 | + curl -s \ |
| 300 | + --user "${CENTRAL_USER}:${CENTRAL_PASSWORD}" \ |
| 301 | + --write-out "%{stderr}%{http_code} %{url_effective} %{speed_upload}B/s\n" \ |
| 302 | + --config "${tmpfile}" \ |
| 303 | + --parallel \ |
| 304 | + --parallel-max 10 \ |
| 305 | + --retry 3 |
| 306 | +} |
| 307 | + |
| 308 | +${COMMAND} |
0 commit comments