From 7d0564cc454d45a505c0d30226fc4fb272450d5f Mon Sep 17 00:00:00 2001 From: Greg Gauthier Date: Sat, 13 Jun 2026 00:15:54 +0100 Subject: [PATCH] ci(release): make release step idempotent (reuse on 409, replace assets) Create-release now treats HTTP 409 as 'already exists' and looks the release up by tag instead of failing, so re-runs and duplicate tag triggers don't go red. Asset uploads delete any same-named asset first, so a run that died mid-upload can be retried cleanly. Co-Authored-By: Claude Opus 4.8 --- .gitea/workflows/release.yml | 52 ++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 7c81213..784227b 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -100,7 +100,11 @@ jobs: echo "Creating release for tag ${VERSION} on ${REPO}..." - curl --fail --silent --show-error -X POST "${GITEA_API}/repos/${REPO}/releases" \ + # Create the release, or reuse it if one already exists for this tag. + # This keeps the step idempotent: re-runs and duplicate tag triggers + # don't fail, and a run that died mid-upload can be retried cleanly. + HTTP_CODE=$(curl --silent --show-error -o release.json -w '%{http_code}' \ + -X POST "${GITEA_API}/repos/${REPO}/releases" \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ -d "{ @@ -109,25 +113,57 @@ jobs: \"target\": \"${GITHUB_SHA}\", \"draft\": false, \"body\": \"## Quick Install\n\n### Bash (Linux/macOS)\n\n\`\`\`bash\ncurl -L https://${HOST}/${REPO}/releases/download/${VERSION}/gostations-install.sh | VERSION=${VERSION} bash\n\`\`\`\n\n### PowerShell (Windows/macOS/Linux)\n\n\`\`\`powershell\nirm https://${HOST}/${REPO}/releases/download/${VERSION}/gostations-install.ps1 | iex\n\`\`\`\n\nPlatform binaries (tar.gz) + checksums.txt are attached. See README.md and CHANGELOG (if present) for details. Legacy wmenu UI still available with --legacy.\" - }" > release.json + }") - echo "Release creation response:" - cat release.json + echo "Create response (HTTP ${HTTP_CODE}):" + cat release.json; echo - RELEASE_ID=$(jq -r '.id // empty' release.json) + case "${HTTP_CODE}" in + 201) + RELEASE_ID=$(jq -r '.id // empty' release.json) + echo "Release created with ID: ${RELEASE_ID}" + ;; + 409) + echo "Release for ${VERSION} already exists; reusing it (idempotent re-run)." + RELEASE_ID=$(curl --fail --silent --show-error \ + "${GITEA_API}/repos/${REPO}/releases/tags/${VERSION}" \ + -H "Authorization: token ${GITEA_TOKEN}" | jq -r '.id // empty') + ;; + *) + echo "ERROR: unexpected HTTP ${HTTP_CODE} creating release. Check the response above and your RELEASE_TOKEN secret (needs write access)." + exit 1 + ;; + esac - if [ -z "$RELEASE_ID" ]; then - echo "ERROR: Failed to create release. No release ID returned. Check the response above and your RELEASE_TOKEN secret (must have 'repo' scope and write access)." + if [ -z "${RELEASE_ID}" ]; then + echo "ERROR: could not determine release ID for ${VERSION}." exit 1 fi - echo "Release created with ID: $RELEASE_ID" + echo "Using release ID: ${RELEASE_ID}" + + # Existing assets on this release, so re-runs replace rather than 409. + EXISTING_ASSETS=$(curl --fail --silent --show-error \ + "${GITEA_API}/repos/${REPO}/releases/${RELEASE_ID}/assets" \ + -H "Authorization: token ${GITEA_TOKEN}") for asset in build/* ; do name=$(basename "$asset") mime="application/octet-stream" [[ "$name" =~ \.tar\.gz$ ]] && mime="application/gzip" [[ "$name" =~ \.(txt|sh|ps1)$ ]] && mime="text/plain" + + # If an asset of this name already exists, delete it first so the + # upload replaces it instead of returning a conflict. + OLD_ID=$(printf '%s' "${EXISTING_ASSETS}" | jq -r --arg n "$name" \ + '.[] | select(.name == $n) | .id' | head -n1) + if [ -n "${OLD_ID}" ]; then + echo "Replacing existing asset: $name (id ${OLD_ID})" + curl --fail --silent --show-error -X DELETE \ + "${GITEA_API}/repos/${REPO}/releases/${RELEASE_ID}/assets/${OLD_ID}" \ + -H "Authorization: token ${GITEA_TOKEN}" + fi + echo "Uploading asset: $name" curl --fail --silent --show-error -X POST "${GITEA_API}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${name}" \ -H "Authorization: token ${GITEA_TOKEN}" \