huskies: merge 1189 story Gitea Actions workflow: build the sled artifact on master merges and publish to the dev channel
This commit is contained in:
Executable
+121
@@ -0,0 +1,121 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user