Merge pull request 'ci(release): verify a tag release actually shipped its artifacts' (#123) from dev into main
This commit was merged in pull request #123.
This commit is contained in:
@@ -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:
|
||||
@@ -322,3 +347,79 @@ jobs:
|
||||
docker buildx build \
|
||||
--build-arg MINSTREL_VERSION="${{ steps.tags.outputs.version }}" \
|
||||
--push ${{ steps.tags.outputs.args }} .
|
||||
|
||||
# Verifies a tag release actually ended up complete, and names the specific
|
||||
# thing that's missing if not.
|
||||
#
|
||||
# Added 2026-08-07 after v2026.08.07 was re-cut. The android-release job never
|
||||
# started — no log was written at all — so all eight of its steps reported
|
||||
# `failure` with none executed and image-release showed `skipped`. The run was
|
||||
# red, but the *release page rendered fine*, and `main`'s own push build had
|
||||
# already moved `:latest`, so the code was deployable and nothing looked
|
||||
# obviously wrong. The release was simply missing its APK and its immutable
|
||||
# `:vYYYY.MM.DD` image, which is easy to skim past.
|
||||
#
|
||||
# This job cannot prevent that (the cause was a runner failing to launch, not
|
||||
# anything in this file). What it does is turn an incomplete release into an
|
||||
# explicit, named error instead of eight mystery step failures — so the
|
||||
# consequence is legible without having to infer it.
|
||||
#
|
||||
# `if: always()` is the whole point: it has to report precisely when the jobs
|
||||
# above did NOT succeed.
|
||||
verify-release:
|
||||
name: Verify release artifacts (tag releases only)
|
||||
needs: [android-release, image-release]
|
||||
if: ${{ always() && startsWith(github.ref, 'refs/tags/v') }}
|
||||
runs-on: go-ci
|
||||
container:
|
||||
image: git.fabledsword.com/bvandeusen/ci-go:1.26
|
||||
|
||||
steps:
|
||||
- name: Release must have an APK attached
|
||||
shell: bash
|
||||
env:
|
||||
CI_TOKEN: ${{ secrets.CI_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
REPO="${GITHUB_REPOSITORY}"
|
||||
|
||||
REL_JSON="$(curl -fsSL \
|
||||
-H "Authorization: token ${CI_TOKEN}" \
|
||||
"https://git.fabledsword.com/api/v1/repos/${REPO}/releases/tags/${TAG}" || true)"
|
||||
if [ -z "${REL_JSON}" ]; then
|
||||
echo "::error::no release found for ${TAG} — the tag exists but nothing was published"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
APK="$(printf '%s' "${REL_JSON}" \
|
||||
| grep -oP '"browser_download_url":\s*"\K[^"]+' \
|
||||
| grep -E '\.apk$' | head -1 || true)"
|
||||
if [ -z "${APK}" ]; then
|
||||
echo "::error::release ${TAG} has NO APK attached — in-app update will offer nothing, and the bundled-APK path on future :latest builds has no source."
|
||||
echo "::error::Fix by RE-RUNNING this workflow run. Do NOT delete and re-create the tag; if it fails again the runner never started the container, and the evidence is in act_runner on the host (Gitea will hold no job log)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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}"
|
||||
|
||||
Reference in New Issue
Block a user