uploadPackageToGitlab.sh
1 #!/usr/bin/env bash 2 set -euo pipefail 3 4 usage() { 5 cat <<'USAGE' 6 Usage: 7 uploadPackageToGitlab.sh \ 8 --gitlab-host https://gitlab.example.com \ 9 --project-id 123 \ 10 --token <YOUR_TOKEN> \ 11 --release-tag latest \ 12 --asset-name archive.7z \ 13 --file ./archive.7z \ 14 --package-name l2ts \ 15 --package-version latest \ 16 --project-path group/project 17 18 What it does: 19 1) Deletes any release asset link with --asset-name from the release identified by --release-tag 20 2) Uploads --file to Generic Package Registry (package name/version/filename) 21 3) Adds a new release asset link pointing to the uploaded package file 22 23 Notes: 24 - --project-id can be numeric ID or URL-encoded path (e.g. group%2Fproject). 25 - Token should have permissions to manage releases + upload packages (typically PAT with api scope). 26 USAGE 27 } 28 29 need() { command -v "$1" >/dev/null 2>&1 || { echo "Missing dependency: $1" >&2; exit 1; }; } 30 31 # --- Parse args --- 32 GITLAB_HOST="" 33 PROJECT_ID="" 34 TOKEN="" 35 RELEASE_TAG="" 36 ASSET_NAME="" 37 FILE_PATH="" 38 PACKAGE_NAME="" 39 PACKAGE_VERSION="" 40 PROJECT_PATH="" 41 PACKAGE_FILENAME="" 42 43 while [[ $# -gt 0 ]]; do 44 case "$1" in 45 --gitlab-host) GITLAB_HOST="${2:-}"; shift 2 ;; 46 --project-id) PROJECT_ID="${2:-}"; shift 2 ;; 47 --token) TOKEN="${2:-}"; shift 2 ;; 48 --release-tag) RELEASE_TAG="${2:-}"; shift 2 ;; 49 --asset-name) ASSET_NAME="${2:-}"; shift 2 ;; 50 --file) FILE_PATH="${2:-}"; shift 2 ;; 51 --package-name) PACKAGE_NAME="${2:-}"; shift 2 ;; 52 --package-version) PACKAGE_VERSION="${2:-}"; shift 2 ;; 53 --project-path) PROJECT_PATH="${2:-}"; shift 2 ;; 54 -h|--help) usage; exit 0 ;; 55 *) echo "Unknown argument: $1" >&2; usage; exit 2 ;; 56 esac 57 done 58 59 # --- Validate --- 60 [[ -n "$GITLAB_HOST" ]] || { echo "Missing --gitlab-host" >&2; usage; exit 2; } 61 [[ -n "$PROJECT_ID" ]] || { echo "Missing --project-id" >&2; usage; exit 2; } 62 [[ -n "$TOKEN" ]] || { echo "Missing --token" >&2; usage; exit 2; } 63 [[ -n "$RELEASE_TAG" ]] || { echo "Missing --release-tag" >&2; usage; exit 2; } 64 [[ -n "$ASSET_NAME" ]] || { echo "Missing --asset-name" >&2; usage; exit 2; } 65 [[ -n "$FILE_PATH" ]] || { echo "Missing --file" >&2; usage; exit 2; } 66 [[ -n "$PACKAGE_NAME" ]] || { echo "Missing --package-name" >&2; usage; exit 2; } 67 [[ -n "$PACKAGE_VERSION" ]] || { echo "Missing --package-version" >&2; usage; exit 2; } 68 [[ -n "$PROJECT_PATH" ]] || { echo "Missing --project-path" >&2; usage; exit 2; } 69 70 PACKAGE_FILENAME="$(basename "$FILE_PATH")" 71 72 if [[ ! -f "$FILE_PATH" ]]; then 73 echo "ERROR: File not found: $FILE_PATH" >&2 74 exit 1 75 fi 76 77 need curl 78 need jq 79 80 api_base="${GITLAB_HOST%/}/api/v4" 81 82 echo "GitLab host: ${GITLAB_HOST%/}" 83 echo "Project ID: ${PROJECT_ID}" 84 echo "Release tag: ${RELEASE_TAG}" 85 echo "Asset link name: ${ASSET_NAME}" 86 echo "File: ${FILE_PATH}" 87 echo "Package: ${PACKAGE_NAME}/${PACKAGE_VERSION}/${PACKAGE_FILENAME}" 88 echo "Project path: ${PROJECT_PATH}" 89 echo 90 91 # we need to delete/remove release asset link with the provided name 92 echo "Fetching existing asset links for release '${RELEASE_TAG}'..." 93 links_json="$( 94 curl --insecure -fsS \ 95 --header "PRIVATE-TOKEN: ${TOKEN}" \ 96 "${api_base}/projects/${PROJECT_ID}/releases/${RELEASE_TAG}/assets/links" 97 )" 98 99 link_ids="$(echo "$links_json" | jq -r --arg NAME "$ASSET_NAME" '.[] | select(.name == $NAME) | .id')" 100 101 if [[ -z "${link_ids}" ]]; then 102 echo "No existing asset link named '${ASSET_NAME}' found." 103 else 104 echo "Deleting asset link(s) named '${ASSET_NAME}'..." 105 while IFS= read -r link_id; do 106 [[ -z "$link_id" ]] && continue 107 curl --insecure -fsS -X DELETE \ 108 --header "PRIVATE-TOKEN: ${TOKEN}" \ 109 "${api_base}/projects/${PROJECT_ID}/releases/${RELEASE_TAG}/assets/links/${link_id}" \ 110 >/dev/null 111 done <<< "$link_ids" 112 echo "Deleted." 113 fi 114 115 echo 116 117 # then we need to upload file to package registory in order to avoid issues with CI artifact expiration 118 upload_url="${api_base}/projects/${PROJECT_ID}/packages/generic/${PACKAGE_NAME}/${PACKAGE_VERSION}/${PACKAGE_FILENAME}" 119 120 # we also need to make sure we delete old package since it would consume extra space 121 echo "Searching for existing Generic Package files named '${PACKAGE_FILENAME}' (any version) to delete..." 122 page=1 123 deleted_any="false" 124 125 while :; do 126 packages_json="$( 127 curl --insecure -fsS \ 128 --header "PRIVATE-TOKEN: ${TOKEN}" \ 129 "${api_base}/projects/${PROJECT_ID}/packages?package_type=generic&package_name=${PACKAGE_NAME}&per_page=100&page=${page}" 130 )" 131 132 pkg_count="$(echo "$packages_json" | jq 'length')" 133 if [[ "$pkg_count" -eq 0 ]]; then 134 break 135 fi 136 137 while IFS= read -r pkg_id; do 138 [[ -z "$pkg_id" ]] && continue 139 140 files_json="$( 141 curl --insecure -fsS \ 142 --header "PRIVATE-TOKEN: ${TOKEN}" \ 143 "${api_base}/projects/${PROJECT_ID}/packages/${pkg_id}/package_files?per_page=100" 144 )" 145 146 file_ids="$(echo "$files_json" | jq -r --arg FN "$PACKAGE_FILENAME" '.[] | select(.file_name == $FN) | .id')" 147 if [[ -n "$file_ids" ]]; then 148 while IFS= read -r file_id; do 149 [[ -z "$file_id" ]] && continue 150 echo "Deleting package file '${PACKAGE_FILENAME}' (package_id=${pkg_id}, file_id=${file_id})..." 151 delete_code="$( 152 curl --insecure -sS -o /dev/null -w "%{http_code}" -X DELETE \ 153 --header "PRIVATE-TOKEN: ${TOKEN}" \ 154 "${api_base}/projects/${PROJECT_ID}/packages/${pkg_id}/package_files/${file_id}" 155 )" 156 if [[ "$delete_code" != "204" ]]; then 157 echo "ERROR: Failed to delete package file '${PACKAGE_FILENAME}' (package_id=${pkg_id}, file_id=${file_id}). HTTP status: ${delete_code}" >&2 158 exit 1 159 fi 160 deleted_any="true" 161 done <<< "$file_ids" 162 fi 163 done < <(echo "$packages_json" | jq -r '.[].id') 164 165 page=$((page + 1)) 166 done 167 168 if [[ "$deleted_any" == "true" ]]; then 169 echo "Old package file(s) deleted." 170 else 171 echo "No existing package files named '${PACKAGE_FILENAME}' found; continuing." 172 fi 173 174 echo 175 176 echo "Uploading to Generic Package Registry..." 177 echo "Upload URL: ${upload_url}" 178 179 curl --insecure -fsS \ 180 --header "PRIVATE-TOKEN: ${TOKEN}" \ 181 --upload-file "${FILE_PATH}" \ 182 "${upload_url}" \ 183 >/dev/null 184 185 echo "Upload complete." 186 echo 187 188 echo "Creating release asset link..." 189 echo "Asset URL: ${upload_url}" 190 191 curl --insecure -fsS -X POST \ 192 --header "PRIVATE-TOKEN: ${TOKEN}" \ 193 --form "name=${ASSET_NAME}" \ 194 --form "url=${upload_url}" \ 195 "${api_base}/projects/${PROJECT_ID}/releases/${RELEASE_TAG}/assets/links" \ 196 >/dev/null 197 198 echo "Finished adding asset with name ${ASSET_NAME}"