feat(18-02): Task 2 — Release-Dateien idempotent an den Gitea-Release haengen
- upload_asset() nach dem bestehenden GET-then-PATCH/POST-Idempotenzmuster: GET .../assets -> vorhandene Datei gleichen Namens per DELETE entfernen -> frisch per POST multipart (Feld attachment) hochladen - Zweite Header-Datei HDR_AUTH (nur Authorization, kein JSON-Content-Type) fuer den multipart-Upload; Token bleibt in Header-Dateien, nie als Argument (T-18-03) - Nach Release-Anlage/-Aktualisierung wird jede Datei aus desktop-dist/manifest.json hochgeladen; fehlt das Manifest bei einem Freigabe-Tag, bricht das Skript hart ab - --dry-run listet zusaetzlich die geplanten Uploads aus dem Manifest, ohne Netzaufruf Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,9 +21,21 @@
|
|||||||
# sonst http://localhost:3002/api/v1 (nur lokal erreichbar).
|
# sonst http://localhost:3002/api/v1 (nur lokal erreichbar).
|
||||||
# GITEA_REPO owner/repo; sonst GITHUB_REPOSITORY, sonst schalli/tessera-ctl.
|
# GITEA_REPO owner/repo; sonst GITHUB_REPOSITORY, sonst schalli/tessera-ctl.
|
||||||
# CHANGELOG_FILE Pfad zur Aenderungsliste; Vorgabe CHANGELOG.md.
|
# CHANGELOG_FILE Pfad zur Aenderungsliste; Vorgabe CHANGELOG.md.
|
||||||
|
# DESKTOP_DIST Ordner mit den Desktop-Paketen und manifest.json (Phase 18,
|
||||||
|
# 18-02); Vorgabe desktop-dist. Fehlt manifest.json bei Tags,
|
||||||
|
# bricht das Skript ab -- der Release-Text ist dann schon
|
||||||
|
# angelegt/aktualisiert, der Job wird sichtbar rot.
|
||||||
#
|
#
|
||||||
# Fehlt der Abschnitt fuer die Version, endet das Skript mit Exit 1 -- es entsteht
|
# Fehlt der Abschnitt fuer die Version, endet das Skript mit Exit 1 -- es entsteht
|
||||||
# nie ein leerer Release. JSON wird ausschliesslich mit jq gebaut.
|
# nie ein leerer Release. JSON wird ausschliesslich mit jq gebaut.
|
||||||
|
#
|
||||||
|
# Release-Dateien (Phase 18, 18-02): jede Datei aus manifest.json wird idempotent
|
||||||
|
# angehaengt -- GET .../releases/{id}/assets, vorhandene Datei gleichen Namens per
|
||||||
|
# DELETE .../releases/{id}/assets/{asset_id} entfernen, dann frisch per
|
||||||
|
# POST .../releases/{id}/assets?name=... (multipart-Feld attachment) hochladen.
|
||||||
|
# Der multipart-Upload braucht eine zweite Header-Datei ($HDR_AUTH) OHNE
|
||||||
|
# Content-Type: application/json -- curl setzt den multipart-Content-Type sonst
|
||||||
|
# nicht korrekt, wenn der JSON-Header schon gesetzt ist.
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
@@ -101,11 +113,21 @@ UPDATE_JSON=$(jq -n --arg name "$NAME" --arg body "$BODY" '{name: $name, body: $
|
|||||||
RELEASES_URL="$API/repos/$REPO/releases"
|
RELEASES_URL="$API/repos/$REPO/releases"
|
||||||
TAG_URL="$API/repos/$REPO/releases/tags/$TAG"
|
TAG_URL="$API/repos/$REPO/releases/tags/$TAG"
|
||||||
|
|
||||||
|
DESKTOP_DIST="${DESKTOP_DIST:-desktop-dist}"
|
||||||
|
MANIFEST="$DESKTOP_DIST/manifest.json"
|
||||||
|
|
||||||
if [ "$DRY_RUN" -eq 1 ]; then
|
if [ "$DRY_RUN" -eq 1 ]; then
|
||||||
echo "Probelauf (kein Netzaufruf):"
|
echo "Probelauf (kein Netzaufruf):"
|
||||||
echo " POST $RELEASES_URL"
|
echo " POST $RELEASES_URL"
|
||||||
echo " PATCH $RELEASES_URL/<id> (falls GET $TAG_URL bereits 200 liefert)"
|
echo " PATCH $RELEASES_URL/<id> (falls GET $TAG_URL bereits 200 liefert)"
|
||||||
printf '%s\n' "$CREATE_JSON"
|
printf '%s\n' "$CREATE_JSON"
|
||||||
|
if [ -f "$MANIFEST" ]; then
|
||||||
|
for FNAME in $(jq -r '.files[].name' "$MANIFEST"); do
|
||||||
|
echo " POST $RELEASES_URL/<id>/assets?name=$FNAME"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo " ($MANIFEST fehlt -- keine geplanten Uploads im Probelauf)"
|
||||||
|
fi
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -118,9 +140,46 @@ umask 077
|
|||||||
TMPDIR_REL=$(mktemp -d)
|
TMPDIR_REL=$(mktemp -d)
|
||||||
trap 'rm -rf "$TMPDIR_REL"' EXIT INT TERM
|
trap 'rm -rf "$TMPDIR_REL"' EXIT INT TERM
|
||||||
HDR="$TMPDIR_REL/headers"
|
HDR="$TMPDIR_REL/headers"
|
||||||
|
HDR_AUTH="$TMPDIR_REL/headers-auth"
|
||||||
RESP="$TMPDIR_REL/response.json"
|
RESP="$TMPDIR_REL/response.json"
|
||||||
|
ASSETS_RESP="$TMPDIR_REL/assets.json"
|
||||||
JSONFILE="$TMPDIR_REL/payload.json"
|
JSONFILE="$TMPDIR_REL/payload.json"
|
||||||
printf 'Authorization: token %s\nContent-Type: application/json\n' "$GITEA_TOKEN" > "$HDR"
|
printf 'Authorization: token %s\nContent-Type: application/json\n' "$GITEA_TOKEN" > "$HDR"
|
||||||
|
# Zweite Header-Datei ohne Content-Type: application/json -- der multipart-Upload
|
||||||
|
# (POST .../assets) darf keinen JSON-Content-Type mitbekommen.
|
||||||
|
printf 'Authorization: token %s\n' "$GITEA_TOKEN" > "$HDR_AUTH"
|
||||||
|
|
||||||
|
# upload_asset FILE NAME RELEASE_ID -- idempotent: vorhandene Datei gleichen Namens
|
||||||
|
# wird zuerst entfernt (GET -> DELETE), dann frisch hochgeladen (POST multipart).
|
||||||
|
upload_asset() {
|
||||||
|
ASSET_FILE="$1"
|
||||||
|
ASSET_NAME="$2"
|
||||||
|
ASSET_RELEASE_ID="$3"
|
||||||
|
|
||||||
|
ASSETS_CODE=$(curl -sS --header @"$HDR" -o "$ASSETS_RESP" -w '%{http_code}' "$RELEASES_URL/$ASSET_RELEASE_ID/assets")
|
||||||
|
if [ "$ASSETS_CODE" != "200" ]; then
|
||||||
|
echo "GET $RELEASES_URL/$ASSET_RELEASE_ID/assets antwortete mit $ASSETS_CODE:" >&2
|
||||||
|
cat "$ASSETS_RESP" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
EXISTING_ID=$(jq -r --arg n "$ASSET_NAME" '.[] | select(.name == $n) | .id' "$ASSETS_RESP")
|
||||||
|
if [ -n "$EXISTING_ID" ]; then
|
||||||
|
DEL_CODE=$(curl -sS --header @"$HDR" -X DELETE -o "$RESP" -w '%{http_code}' "$RELEASES_URL/$ASSET_RELEASE_ID/assets/$EXISTING_ID")
|
||||||
|
if [ "$DEL_CODE" != "204" ]; then
|
||||||
|
echo "DELETE $RELEASES_URL/$ASSET_RELEASE_ID/assets/$EXISTING_ID antwortete mit $DEL_CODE:" >&2
|
||||||
|
cat "$RESP" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
UPLOAD_CODE=$(curl -sS --header @"$HDR_AUTH" -X POST -F "attachment=@${ASSET_FILE};filename=${ASSET_NAME}" -o "$RESP" -w '%{http_code}' "$RELEASES_URL/$ASSET_RELEASE_ID/assets?name=$ASSET_NAME")
|
||||||
|
if [ "$UPLOAD_CODE" != "201" ]; then
|
||||||
|
echo "POST $RELEASES_URL/$ASSET_RELEASE_ID/assets?name=$ASSET_NAME antwortete mit $UPLOAD_CODE:" >&2
|
||||||
|
cat "$RESP" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
CODE=$(curl -sS --header @"$HDR" -o "$RESP" -w '%{http_code}' "$TAG_URL")
|
CODE=$(curl -sS --header @"$HDR" -o "$RESP" -w '%{http_code}' "$TAG_URL")
|
||||||
case "$CODE" in
|
case "$CODE" in
|
||||||
@@ -140,7 +199,8 @@ case "$CODE" in
|
|||||||
printf '%s' "$CREATE_JSON" > "$JSONFILE"
|
printf '%s' "$CREATE_JSON" > "$JSONFILE"
|
||||||
CODE=$(curl -sS --header @"$HDR" -X POST --data @"$JSONFILE" -o "$RESP" -w '%{http_code}' "$RELEASES_URL")
|
CODE=$(curl -sS --header @"$HDR" -X POST --data @"$JSONFILE" -o "$RESP" -w '%{http_code}' "$RELEASES_URL")
|
||||||
if [ "$CODE" = "201" ]; then
|
if [ "$CODE" = "201" ]; then
|
||||||
echo "Release $TAG angelegt (id $(jq -r .id "$RESP"))"
|
ID=$(jq -r .id "$RESP")
|
||||||
|
echo "Release $TAG angelegt (id $ID)"
|
||||||
else
|
else
|
||||||
echo "POST $RELEASES_URL antwortete mit $CODE:" >&2
|
echo "POST $RELEASES_URL antwortete mit $CODE:" >&2
|
||||||
cat "$RESP" >&2
|
cat "$RESP" >&2
|
||||||
@@ -153,3 +213,16 @@ case "$CODE" in
|
|||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
# Release-Dateien aus dem Manifest anhaengen (Phase 18, 18-02). Der Release-Text
|
||||||
|
# ist an dieser Stelle bereits angelegt/aktualisiert -- fehlt das Manifest, wird
|
||||||
|
# das trotzdem hart abgebrochen (kein Release ohne Pakete bei einem Freigabe-Tag).
|
||||||
|
if [ ! -f "$MANIFEST" ]; then
|
||||||
|
echo "$MANIFEST fehlt -- kein Release ohne Desktop-Pakete." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for FNAME in $(jq -r '.files[].name' "$MANIFEST"); do
|
||||||
|
upload_asset "$DESKTOP_DIST/$FNAME" "$FNAME" "$ID"
|
||||||
|
echo "Release-Datei $FNAME hochgeladen"
|
||||||
|
done
|
||||||
|
|||||||
Reference in New Issue
Block a user