122 lines
4.4 KiB
Bash
Executable File
122 lines
4.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# script/ci-publish-artifact — upload a built sled artifact to a release
|
|
# channel and verify the channel's manifest reflects the published commit.
|
|
#
|
|
# Usage: script/ci-publish-artifact <artifact-path> [git-hash]
|
|
#
|
|
# git-hash defaults to `git rev-parse --short HEAD` when omitted.
|
|
#
|
|
# Required env:
|
|
# HUSKIES_CHANNEL_URL Base URL of the release channel (Gitea secret).
|
|
# HUSKIES_CHANNEL_TOKEN Bearer token authorised to publish (Gitea secret).
|
|
#
|
|
# Protocol against the channel host:
|
|
# POST {HUSKIES_CHANNEL_URL}/<artifact-name>
|
|
# Headers: Authorization: Bearer <token>, X-Git-Hash: <hash>
|
|
# Body: raw artifact bytes
|
|
# GET {HUSKIES_CHANNEL_URL}/manifest.json
|
|
# Headers: Authorization: Bearer <token>
|
|
# Body: JSON object with a "git_hash" field
|
|
#
|
|
# 5xx responses and network errors are retried with backoff; 4xx responses
|
|
# (including auth failures) fail immediately since retrying won't fix them.
|
|
set -euo pipefail
|
|
|
|
ARTIFACT_PATH="${1:?Usage: script/ci-publish-artifact <artifact-path> [git-hash]}"
|
|
GIT_HASH="${2:-$(git rev-parse --short HEAD)}"
|
|
|
|
if [ -z "${HUSKIES_CHANNEL_URL:-}" ]; then
|
|
echo "Error: HUSKIES_CHANNEL_URL is not set." >&2
|
|
exit 1
|
|
fi
|
|
if [ -z "${HUSKIES_CHANNEL_TOKEN:-}" ]; then
|
|
echo "Error: HUSKIES_CHANNEL_TOKEN is not set." >&2
|
|
exit 1
|
|
fi
|
|
if [ ! -f "$ARTIFACT_PATH" ]; then
|
|
echo "Error: artifact not found at $ARTIFACT_PATH" >&2
|
|
exit 1
|
|
fi
|
|
|
|
ARTIFACT_NAME="$(basename "$ARTIFACT_PATH")"
|
|
CHANNEL_URL="${HUSKIES_CHANNEL_URL%/}"
|
|
UPLOAD_URL="${CHANNEL_URL}/${ARTIFACT_NAME}"
|
|
MANIFEST_URL="${CHANNEL_URL}/manifest.json"
|
|
|
|
UPLOAD_MAX_ATTEMPTS="${HUSKIES_CI_PUBLISH_MAX_ATTEMPTS:-3}"
|
|
UPLOAD_BACKOFF_SECS="${HUSKIES_CI_PUBLISH_BACKOFF_SECS:-1}"
|
|
MANIFEST_MAX_ATTEMPTS=3
|
|
MANIFEST_BACKOFF_SECS=1
|
|
|
|
RESPONSE_FILE="$(mktemp)"
|
|
trap 'rm -f "$RESPONSE_FILE"' EXIT
|
|
|
|
# ── Upload ────────────────────────────────────────────────────────────────
|
|
attempt=1
|
|
while :; do
|
|
echo "==> Uploading ${ARTIFACT_NAME} (${GIT_HASH}), attempt ${attempt}/${UPLOAD_MAX_ATTEMPTS}..."
|
|
HTTP_CODE=$(curl -sS --connect-timeout 10 --max-time 60 \
|
|
-o "$RESPONSE_FILE" -w "%{http_code}" \
|
|
-X POST \
|
|
-H "Authorization: Bearer ${HUSKIES_CHANNEL_TOKEN}" \
|
|
-H "X-Git-Hash: ${GIT_HASH}" \
|
|
--data-binary "@${ARTIFACT_PATH}" \
|
|
"${UPLOAD_URL}") || HTTP_CODE="000"
|
|
RESPONSE_BODY="$(cat "$RESPONSE_FILE" 2>/dev/null || true)"
|
|
|
|
case "$HTTP_CODE" in
|
|
2??)
|
|
echo "==> Upload succeeded (HTTP ${HTTP_CODE})."
|
|
break
|
|
;;
|
|
401|403)
|
|
echo "Error: upload rejected — authentication failed (HTTP ${HTTP_CODE})." >&2
|
|
echo "Response: ${RESPONSE_BODY}" >&2
|
|
exit 1
|
|
;;
|
|
4??)
|
|
echo "Error: upload rejected by the channel (HTTP ${HTTP_CODE}); not retrying a client error." >&2
|
|
echo "Response: ${RESPONSE_BODY}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
if [ "$attempt" -ge "$UPLOAD_MAX_ATTEMPTS" ]; then
|
|
echo "Error: upload failed after ${UPLOAD_MAX_ATTEMPTS} attempts (last HTTP ${HTTP_CODE})." >&2
|
|
echo "Response: ${RESPONSE_BODY}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "==> Transient failure (HTTP ${HTTP_CODE}); retrying in ${UPLOAD_BACKOFF_SECS}s..."
|
|
sleep "$UPLOAD_BACKOFF_SECS"
|
|
attempt=$((attempt + 1))
|
|
UPLOAD_BACKOFF_SECS=$((UPLOAD_BACKOFF_SECS * 2))
|
|
done
|
|
|
|
# ── Verify manifest ──────────────────────────────────────────────────────
|
|
attempt=1
|
|
while :; do
|
|
echo "==> Verifying channel manifest reflects ${GIT_HASH} (attempt ${attempt}/${MANIFEST_MAX_ATTEMPTS})..."
|
|
MANIFEST_JSON=$(curl -sS --connect-timeout 10 --max-time 30 \
|
|
-H "Authorization: Bearer ${HUSKIES_CHANNEL_TOKEN}" \
|
|
"${MANIFEST_URL}") || MANIFEST_JSON=""
|
|
|
|
MANIFEST_HASH=$(printf '%s' "$MANIFEST_JSON" \
|
|
| python3 -c "import sys,json; print(json.load(sys.stdin).get('git_hash',''))" 2>/dev/null || echo "")
|
|
|
|
if [ "$MANIFEST_HASH" = "$GIT_HASH" ]; then
|
|
echo "==> Published ${ARTIFACT_NAME} (${GIT_HASH}) to ${CHANNEL_URL}; manifest verified."
|
|
exit 0
|
|
fi
|
|
|
|
if [ "$attempt" -ge "$MANIFEST_MAX_ATTEMPTS" ]; then
|
|
echo "Error: manifest mismatch — channel reports git_hash '${MANIFEST_HASH}', expected '${GIT_HASH}'." >&2
|
|
echo "Manifest: ${MANIFEST_JSON}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
sleep "$MANIFEST_BACKOFF_SECS"
|
|
attempt=$((attempt + 1))
|
|
MANIFEST_BACKOFF_SECS=$((MANIFEST_BACKOFF_SECS * 2))
|
|
done
|