/ package / uploadPackageToGitlab.sh
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}"