From a254cb2273c5e1cc661a2ea19abb504997a8282f Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Fri, 7 Aug 2026 08:27:09 -0400 Subject: [PATCH] ci(release): close the verify blind spot, check preconditions before the build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auditing the gating turned up two problems. verify-release only checked the APK. Because it runs with `always()`, it runs even when image-release FAILED — so android succeeding while the image push died would have reported "verified" on a release with no immutable :vYYYY.MM.DD image. That is exactly half of what was missing when v2026.08.07 had to be re-cut, so the guard would have caught the incident we had and waved through its mirror image. Now checks the image too, via docker manifest inspect. "Attach APK to gitea Release" resolves the release by tag and fails if it is absent — but it is the LAST step, so a bare `git push origin vX` built an APK for several minutes before discovering it had nowhere to put it. Same check now runs immediately after version computation: seconds, not minutes. Releases created through the API create tag and release together and pass it. The rest of the gating audits clean, and one part is worth not "fixing": image-release's `if: !failure() && !cancelled()` looks odd next to `needs: [android-release]` but is correct. On main pushes android-release is SKIPPED, and a skipped dependency is not success() — so the obvious `if: success()` would silently stop main from ever publishing :latest. Steps 4/5 vs 6 are mutually exclusive on the tag context, and every image step gates on the Dockerfile+go.mod guard. Validated: YAML parses, and `bash -n` over every run: block in all three jobs is clean. --- .gitea/workflows/release.yml | 48 +++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 8d1dc317..6e89d541 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -98,6 +98,31 @@ jobs: echo "code=${COMMIT_COUNT}" >> "$GITHUB_OUTPUT" echo "::notice::APK version: ${VERSION_NAME} (code=${COMMIT_COUNT})" + # Checked BEFORE the expensive work, not after it. "Attach APK to gitea + # Release" below resolves the release by tag and fails if it is absent — + # but that is the final step, so a tag pushed without a release built an + # APK for several minutes first and only then discovered it had nowhere to + # put it. Same check, seconds in instead of minutes. + # + # Releases are normally created through the API (which creates the tag and + # the release together, so this passes). A bare `git push origin vX` is the + # case this catches. + - name: Release must exist for this tag + shell: bash + working-directory: ${{ github.workspace }} + env: + CI_TOKEN: ${{ secrets.CI_TOKEN }} + run: | + set -euo pipefail + TAG="${GITHUB_REF#refs/tags/}" + if ! curl -fsSL -o /dev/null \ + -H "Authorization: token ${CI_TOKEN}" \ + "https://git.fabledsword.com/api/v1/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}"; then + echo "::error::no release exists for ${TAG}. Create the release (which creates the tag) rather than pushing a bare tag — otherwise there is nothing to attach the APK to." + exit 1 + fi + echo "::notice::release found for ${TAG}" + - name: Cache Gradle dirs uses: actions/cache@v4 with: @@ -376,4 +401,25 @@ jobs: exit 1 fi - echo "::notice::${TAG} verified — APK attached: ${APK}" + echo "::notice::APK attached: ${APK}" + + # The other half. Checking only the APK would report success on a release + # whose image push failed — which is precisely the second thing that was + # missing when v2026.08.07 had to be re-cut. `always()` on this job means + # it runs even when image-release failed, so without this the guard would + # cheerfully verify an incomplete release. + - name: Immutable image tag must exist + shell: bash + run: | + set -euo pipefail + TAG="${GITHUB_REF#refs/tags/}" + IMAGE="git.fabledsword.com/bvandeusen/minstrel" + + echo "${{ secrets.CI_TOKEN }}" \ + | docker login git.fabledsword.com -u "${{ github.actor }}" --password-stdin + + if ! docker manifest inspect "${IMAGE}:${TAG}" > /dev/null 2>&1; then + echo "::error::image ${IMAGE}:${TAG} was never pushed — the release tag has no immutable image, so there is nothing to pin or roll back to. Re-run this workflow run." + exit 1 + fi + echo "::notice::image verified: ${IMAGE}:${TAG}"