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 Playwright + Docker rebuild # because the application bytes are identical. Both paths dispatch deploy.yml # for Go validation and release binaries; mismatched SHAs also run the full # image pipeline 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 for the `gh workflow run deploy.yml` dispatch concurrency: group: release-fast-path-${{ github.ref }} cancel-in-progress: false jobs: retag-or-fallback: name: "🏷️ Re-tag :edge and dispatch release artifacts or full 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." # Both image routes need the single release-artifacts writer in deploy.yml. - name: Dispatch release artifacts or full deploy.yml pipeline env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail DISPATCH_ARGS=() if [[ "${{ steps.edge.outputs.no_edge == 'false' && steps.edge.outputs.edge_revision == github.sha }}" == 'true' ]]; then echo "Images published — dispatching Go validation and release artifacts" DISPATCH_ARGS+=(--field images_published=true) else # No new inputs: an older tag may have an older deploy.yml schema. echo "SHA mismatch (or no :edge) — falling back to full pipeline" fi gh workflow run deploy.yml \ --repo "${{ github.repository }}" \ --ref "${{ github.ref }}" \ "${DISPATCH_ARGS[@]}" echo "Dispatched deploy.yml against ${{ github.ref }}"