Retry release asset uploads

This commit is contained in:
rcourtman
2026-05-03 10:26:51 +01:00
parent 0e1cfd4574
commit 9ba0c3fa96
4 changed files with 86 additions and 21 deletions
+67 -13
View File
@@ -799,13 +799,40 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ needs.prepare.outputs.tag }}"
gh release upload "${TAG}" release/checksums.txt --clobber
gh release upload "${TAG}" release/*.sha256 --clobber
release_upload_with_retry() {
local attempt=1
local max_attempts=5
local wait_seconds=15
while true; do
if gh release upload "$@"; then
return 0
fi
if [ "$attempt" -ge "$max_attempts" ]; then
echo "::error::gh release upload failed after ${max_attempts} attempts: $*"
return 1
fi
echo "gh release upload failed on attempt ${attempt}/${max_attempts}; retrying in ${wait_seconds}s: $*"
sleep "$wait_seconds"
attempt=$((attempt + 1))
if [ "$wait_seconds" -lt 120 ]; then
wait_seconds=$((wait_seconds * 2))
if [ "$wait_seconds" -gt 120 ]; then
wait_seconds=120
fi
fi
done
}
release_upload_with_retry "${TAG}" release/checksums.txt --clobber
release_upload_with_retry "${TAG}" release/*.sha256 --clobber
if ls release/*.sig 1> /dev/null 2>&1; then
gh release upload "${TAG}" release/*.sig --clobber
release_upload_with_retry "${TAG}" release/*.sig --clobber
fi
if ls release/*.sshsig 1> /dev/null 2>&1; then
gh release upload "${TAG}" release/*.sshsig --clobber
release_upload_with_retry "${TAG}" release/*.sshsig --clobber
fi
- name: Upload release assets
@@ -813,13 +840,40 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ needs.prepare.outputs.tag }}"
release_upload_with_retry() {
local attempt=1
local max_attempts=5
local wait_seconds=15
while true; do
if gh release upload "$@"; then
return 0
fi
if [ "$attempt" -ge "$max_attempts" ]; then
echo "::error::gh release upload failed after ${max_attempts} attempts: $*"
return 1
fi
echo "gh release upload failed on attempt ${attempt}/${max_attempts}; retrying in ${wait_seconds}s: $*"
sleep "$wait_seconds"
attempt=$((attempt + 1))
if [ "$wait_seconds" -lt 120 ]; then
wait_seconds=$((wait_seconds * 2))
if [ "$wait_seconds" -gt 120 ]; then
wait_seconds=120
fi
fi
done
}
if ls release/*.sbom.spdx.json 1> /dev/null 2>&1; then
gh release upload "${TAG}" release/*.sbom.spdx.json --clobber
release_upload_with_retry "${TAG}" release/*.sbom.spdx.json --clobber
fi
gh release upload "${TAG}" release/*.tar.gz --clobber
gh release upload "${TAG}" release/*.zip --clobber
release_upload_with_retry "${TAG}" release/*.tar.gz --clobber
release_upload_with_retry "${TAG}" release/*.zip --clobber
if ls release/*.tgz 1> /dev/null 2>&1; then
gh release upload "${TAG}" release/*.tgz --clobber
release_upload_with_retry "${TAG}" release/*.tgz --clobber
fi
for bare_agent in \
release/pulse-agent-linux-amd64 \
@@ -833,15 +887,15 @@ jobs:
release/pulse-agent-windows-arm64.exe \
release/pulse-agent-windows-386.exe; do
if [ -f "${bare_agent}" ]; then
gh release upload "${TAG}" "${bare_agent}" --clobber
release_upload_with_retry "${TAG}" "${bare_agent}" --clobber
fi
done
gh release upload "${TAG}" release/install.sh --clobber
release_upload_with_retry "${TAG}" release/install.sh --clobber
if [ -f release/install.ps1 ]; then
gh release upload "${TAG}" release/install.ps1 --clobber
release_upload_with_retry "${TAG}" release/install.ps1 --clobber
fi
gh release upload "${TAG}" release/install-docker.sh --clobber
gh release upload "${TAG}" release/pulse-auto-update.sh --clobber
release_upload_with_retry "${TAG}" release/install-docker.sh --clobber
release_upload_with_retry "${TAG}" release/pulse-auto-update.sh --clobber
- name: Publish release
if: ${{ github.event.inputs.draft_only != 'true' }}
@@ -462,6 +462,11 @@ unpublished tag must locate the existing draft release, retarget its git tag
and release `target_commitish` to the current governed release-line head, and
continue publication without requiring an operator to delete the tag manually;
published tags remain immutable and must still fail closed.
That same upload boundary must tolerate transient GitHub release-asset API
failures. `.github/workflows/create-release.yml` must retry every
`gh release upload` operation with bounded backoff before failing the release
job, because a single 5xx response during upload can otherwise strand a draft
release with a partial asset set and no validation run.
That same public release-body boundary also owns publish-safe packet rendering.
When operators pass draft packet markdown to `.github/workflows/create-release.yml`,
the workflow must sanitize draft-only framing and append the standardized
@@ -114,7 +114,7 @@ func TestCreateReleaseUploadsPowerShellInstaller(t *testing.T) {
`SYFT_ARCHIVE="syft_${SYFT_VERSION}_linux_amd64.tar.gz"`,
`SYFT_SHA256="590650c2743b83f327d1bf9bec64f6f83b7fec504187bb84f500c862bf8f2a0f"`,
`install -m 0755 "${TMP_DIR}/syft" /usr/local/bin/syft`,
`gh release upload "${TAG}" release/*.sbom.spdx.json --clobber`,
`release_upload_with_retry "${TAG}" release/*.sbom.spdx.json --clobber`,
`release/pulse-agent-linux-amd64`,
`release/pulse-agent-linux-arm64`,
`release/pulse-agent-linux-armv7`,
@@ -125,11 +125,14 @@ func TestCreateReleaseUploadsPowerShellInstaller(t *testing.T) {
`release/pulse-agent-windows-amd64.exe`,
`release/pulse-agent-windows-arm64.exe`,
`release/pulse-agent-windows-386.exe`,
`gh release upload "${TAG}" release/install.sh --clobber`,
`release_upload_with_retry "${TAG}" release/install.sh --clobber`,
`if [ -f release/install.ps1 ]; then`,
`gh release upload "${TAG}" release/install.ps1 --clobber`,
`gh release upload "${TAG}" release/*.sig --clobber`,
`gh release upload "${TAG}" release/*.sshsig --clobber`,
`release_upload_with_retry "${TAG}" release/install.ps1 --clobber`,
`release_upload_with_retry "${TAG}" release/*.sig --clobber`,
`release_upload_with_retry "${TAG}" release/*.sshsig --clobber`,
`gh release upload "$@"`,
`gh release upload failed on attempt ${attempt}/${max_attempts}; retrying in ${wait_seconds}s`,
`gh release upload failed after ${max_attempts} attempts`,
`uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4`,
`subject-path: release/*`,
`gh api "repos/${{ github.repository }}/releases?per_page=100" --paginate`,
@@ -404,9 +404,12 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
self.assertIn('SYFT_ARCHIVE="syft_${SYFT_VERSION}_linux_amd64.tar.gz"', content)
self.assertIn('SYFT_SHA256="590650c2743b83f327d1bf9bec64f6f83b7fec504187bb84f500c862bf8f2a0f"', content)
self.assertIn('install -m 0755 "${TMP_DIR}/syft" /usr/local/bin/syft', content)
self.assertIn('gh release upload "${TAG}" release/*.sig --clobber', content)
self.assertIn('gh release upload "${TAG}" release/*.sshsig --clobber', content)
self.assertIn('gh release upload "${TAG}" release/*.sbom.spdx.json --clobber', content)
self.assertIn('release_upload_with_retry "${TAG}" release/*.sig --clobber', content)
self.assertIn('release_upload_with_retry "${TAG}" release/*.sshsig --clobber', content)
self.assertIn('release_upload_with_retry "${TAG}" release/*.sbom.spdx.json --clobber', content)
self.assertIn('gh release upload "$@"', content)
self.assertIn('gh release upload failed on attempt ${attempt}/${max_attempts}; retrying in ${wait_seconds}s', content)
self.assertIn('gh release upload failed after ${max_attempts} attempts', content)
self.assertIn("provenance: mode=max", content)
self.assertIn("sbom: true", content)
self.assertIn("id-token: write", content)