Files
meshcore-analyzer/.github/workflows/release-fast-path.yml
T
9ef4179ef1 fix(release): report correct version on fast-path retagged images (#1807) (#1814)
Fixes #1807.

Implements the fix path from the triage (env → image-version file →
baked version, zero rebuild cost):

## Changes

**`cmd/server/main.go` — `resolveVersion()` fallback chain**
1. `CORESCOPE_VERSION` env (operator override)
2. `.image-version` file in the working dir (`/app` in the container) —
mirrors the existing `.git-commit` pattern in `resolveCommit()`
3. ldflags-baked `Version`
4. `"unknown"`

Edge builds are unaffected: no env, no file → baked `"edge"` as before.

**`.github/workflows/release-fast-path.yml` — retag step**
Instead of a plain `crane tag :edge → :vX.Y.Z`, the fast path now runs
`crane mutate` on `:edge` with:
- `--append` of a deterministic one-file layer containing
`/app/.image-version` = `vX.Y.Z`
- `--label org.opencontainers.image.version=vX.Y.Z`
- `--tag :vX.Y.Z`

`vX.Y`, `vX` and `latest` are then pointed at the mutated image. Still
no rebuild — the mutation is a manifest + single ~100-byte layer
operation.

## Notes
- The release tags no longer share the exact digest with `:edge` (they
carry one extra layer); the fallback SHA check is unaffected since it
compares the `org.opencontainers.image.revision` label against
`github.sha`.
- The layer tar uses `--owner=0 --group=0 --mtime='UTC 2020-01-01'` for
reproducibility.
- Operators can also fix existing deployments immediately with `-e
CORESCOPE_VERSION=v3.9.2`, no image change needed.

## Testing
- `go build ./cmd/server` + `go vet` clean (golang:1.24)
- Workflow YAML validated
- Reporter context: running the affected v3.9.2 fast-path image in
production (live.saarmesh.de), happy to verify the next tagged release
end-to-end.

---------

Co-authored-by: Mathias Kasper <fallisaar@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: SaarMesh-Bot <bot@saarmesh.de>
2026-09-02 09:50:14 +02:00

132 lines
5.6 KiB
YAML

name: Release Fast-Path
# Issue #1677: re-tag :edge as :vX.Y.Z when the tag SHA matches :edge's
# org.opencontainers.image.revision label. Skips ~30 min of Go test +
# Playwright + Docker rebuild because the bytes are identical — only the
# manifest name changes. Falls back to deploy.yml when SHAs differ so
# tags on older commits still go through full validation.
#
# This workflow is the SOLE consumer of push.tags. deploy.yml's tag
# trigger has been removed to prevent double-fire.
on:
push:
tags: ['v[0-9]+.[0-9]+.[0-9]+']
permissions:
contents: read
packages: write
actions: write # issue #1702: required so the fallback `gh workflow run deploy.yml` dispatch is allowed
concurrency:
group: release-fast-path-${{ github.ref }}
cancel-in-progress: false
jobs:
retag-or-fallback:
name: "🏷️ Re-tag :edge → :vX.Y.Z (fast) or dispatch deploy.yml (fallback)"
runs-on: ubuntu-latest
steps:
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Install crane
uses: imjasonh/setup-crane@v0.4
- name: Parse semver from tag
id: semver
run: |
set -euo pipefail
TAG="${GITHUB_REF#refs/tags/}"
# Expect vMAJOR.MINOR.PATCH (workflow trigger already enforces this).
if [[ ! "$TAG" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
echo "Tag $TAG does not match vMAJOR.MINOR.PATCH" >&2
exit 1
fi
MAJOR="${BASH_REMATCH[1]}"
MINOR="${BASH_REMATCH[2]}"
{
echo "tag=$TAG"
echo "vMajor=v$MAJOR"
echo "vMajorMinor=v$MAJOR.$MINOR"
} >> "$GITHUB_OUTPUT"
echo "Parsed: $TAG → v$MAJOR / v$MAJOR.$MINOR / $TAG"
- name: Inspect :edge revision label
id: edge
run: |
set -euo pipefail
IMAGE="ghcr.io/kpa-clawbot/corescope"
EDGE_REF="${IMAGE}:edge"
# crane config returns the OCI image config JSON; the revision label
# is set by docker/metadata-action on the master-edge build.
# If :edge doesn't exist yet (first run on a fresh registry), fall
# through to the slow path.
if ! CONFIG="$(crane config "$EDGE_REF" 2>/dev/null)"; then
echo "edge_revision=" >> "$GITHUB_OUTPUT"
echo "no_edge=true" >> "$GITHUB_OUTPUT"
echo ":edge not found in registry — will use fallback path"
exit 0
fi
REV="$(echo "$CONFIG" | jq -r '.config.Labels["org.opencontainers.image.revision"] // ""')"
echo "edge_revision=$REV" >> "$GITHUB_OUTPUT"
echo "no_edge=false" >> "$GITHUB_OUTPUT"
echo ":edge org.opencontainers.image.revision = $REV"
echo "tag SHA (github.sha) = ${{ github.sha }}"
# ─────────── FAST PATH: SHAs match, metadata-only retag ───────────
# Issue #1807: a plain `crane tag` keeps the ldflags-baked
# Version="edge" inside the binary, so /api/stats reports "edge" on
# tagged releases. Instead of a full rebuild we append a single-file
# layer (/app/.image-version containing the tag) and set the OCI
# version label, then point the remaining tags at that mutated image.
# The server reads .image-version at startup (see resolveVersion()).
- name: Re-tag :edge → :vX.Y.Z + :vX.Y + :vX + :latest (fast path)
if: steps.edge.outputs.no_edge == 'false' && steps.edge.outputs.edge_revision == github.sha
run: |
set -euo pipefail
IMAGE="ghcr.io/kpa-clawbot/corescope"
SRC="${IMAGE}:edge"
TAG="${{ steps.semver.outputs.tag }}"
echo "SHA match — fast-path re-tag from $SRC"
# Build a one-file layer: /app/.image-version = vX.Y.Z
LAYER_DIR="$(mktemp -d)"
mkdir -p "${LAYER_DIR}/app"
printf '%s' "$TAG" > "${LAYER_DIR}/app/.image-version"
tar --owner=0 --group=0 --mtime='UTC 2020-01-01' \
-C "$LAYER_DIR" -cf /tmp/image-version-layer.tar app
echo " crane mutate $SRC (+.image-version layer, +version label) → ${IMAGE}:${TAG}"
crane mutate "$SRC" \
--append /tmp/image-version-layer.tar \
--label "org.opencontainers.image.version=${TAG}" \
--tag "${IMAGE}:${TAG}"
for NEW_TAG in \
"${{ steps.semver.outputs.vMajorMinor }}" \
"${{ steps.semver.outputs.vMajor }}" \
"latest"; do
echo " crane tag ${IMAGE}:${TAG} $NEW_TAG"
crane tag "${IMAGE}:${TAG}" "$NEW_TAG"
done
echo "Fast-path complete — release tags point at :edge plus a one-file version layer."
# ─────────── FALLBACK: SHAs differ, run the full pipeline ───────────
- name: Dispatch full deploy.yml pipeline (fallback)
if: steps.edge.outputs.no_edge == 'true' || steps.edge.outputs.edge_revision != github.sha
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
echo "SHA mismatch (or no :edge) — falling back to full pipeline"
echo " :edge revision = '${{ steps.edge.outputs.edge_revision }}'"
echo " tag SHA = '${{ github.sha }}'"
gh workflow run deploy.yml \
--repo "${{ github.repository }}" \
--ref "${{ github.ref }}"
echo "Dispatched deploy.yml against ${{ github.ref }}"