82 lines
3.7 KiB
YAML
82 lines
3.7 KiB
YAML
name: Promote to public (manual fallback)
|
|
# Primary promotion is the gated `promote` job in ci-staging.yml (auto on a green stable tag). This
|
|
# manual dispatch re-runs the same procedure for a specific tag (e.g. after a promote.sh fix). It
|
|
# mirrors the auto job: resolve the exact image digests, build+guard the public code tree, publish
|
|
# the images, then push atomically. promote.sh refuses if the tag is already public — yank first.
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: 'Stable tag to (re-)publish (e.g. v0.11.0)'
|
|
required: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
promote:
|
|
runs-on: ubuntu-latest
|
|
environment: production
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
# The tag reaches every run-step via this env var (never inline `${{ inputs.tag }}` in a shell —
|
|
# that would be a command-injection sink for a hand-typed input).
|
|
env:
|
|
TAG: ${{ inputs.tag }}
|
|
steps:
|
|
- name: Enforce a clean stable SemVer tag
|
|
run: |
|
|
[[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] \
|
|
|| { echo "::error::not a stable vX.Y.Z tag: $TAG"; exit 1; }
|
|
- uses: actions/checkout@v5
|
|
with:
|
|
ref: ${{ inputs.tag }}
|
|
fetch-depth: 0
|
|
path: src
|
|
- uses: docker/login-action@v3
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GHCR_TOKEN }}
|
|
- name: Resolve private image digests
|
|
id: img
|
|
run: |
|
|
set -euo pipefail
|
|
appd=$(docker buildx imagetools inspect "ghcr.io/${{ vars.GHCR_OWNER }}/clusev:$TAG" --format '{{.Manifest.Digest}}')
|
|
termd=$(docker buildx imagetools inspect "ghcr.io/${{ vars.GHCR_OWNER }}/clusev-terminal:$TAG" --format '{{.Manifest.Digest}}')
|
|
{ echo "appd=$appd"; echo "termd=$termd"; } >> "$GITHUB_OUTPUT"
|
|
- uses: actions/checkout@v5
|
|
with:
|
|
repository: ${{ vars.PUBLIC_REPO_SLUG }}
|
|
token: ${{ secrets.PUBLIC_REPO_TOKEN }}
|
|
fetch-depth: 0
|
|
path: pub
|
|
- name: Build the public tree (guarded, no publish)
|
|
env:
|
|
CLUSEV_PROMOTE_FORBIDDEN: ${{ secrets.PROMOTE_FORBIDDEN }}
|
|
APP_DIGEST: ghcr.io/${{ vars.PUBLIC_GHCR_OWNER }}/clusev@${{ steps.img.outputs.appd }}
|
|
TERM_DIGEST: ghcr.io/${{ vars.PUBLIC_GHCR_OWNER }}/clusev-terminal@${{ steps.img.outputs.termd }}
|
|
run: |
|
|
set -euo pipefail
|
|
git config --global user.name 'clusev-release'
|
|
git config --global user.email 'release@clusev'
|
|
chmod +x src/scripts/promote.sh
|
|
src/scripts/promote.sh "$PWD/src" "$TAG" "$PWD/pub" "Release $TAG"
|
|
printf '{ "version": "%s", "app": "%s", "terminal": "%s" }\n' "$TAG" "$APP_DIGEST" "$TERM_DIGEST" > pub/release-images.lock
|
|
git -C pub add -f release-images.lock
|
|
git -C pub commit -q --amend --no-edit
|
|
git -C pub tag -f "$TAG"
|
|
# Publish AFTER guards, BEFORE the push (publish-then-advertise): a release ref always has an
|
|
# image. A push failure leaves only unreferenced idempotent image tags; re-run to recover.
|
|
- name: Publish images to public GHCR
|
|
env:
|
|
APP_SRC: ghcr.io/${{ vars.GHCR_OWNER }}/clusev@${{ steps.img.outputs.appd }}
|
|
TERM_SRC: ghcr.io/${{ vars.GHCR_OWNER }}/clusev-terminal@${{ steps.img.outputs.termd }}
|
|
run: |
|
|
set -euo pipefail
|
|
docker buildx imagetools create -t "ghcr.io/${{ vars.PUBLIC_GHCR_OWNER }}/clusev:$TAG" "$APP_SRC"
|
|
docker buildx imagetools create -t "ghcr.io/${{ vars.PUBLIC_GHCR_OWNER }}/clusev-terminal:$TAG" "$TERM_SRC"
|
|
- name: Push code + tag to public (atomic)
|
|
run: git -C pub push --atomic origin HEAD:main "refs/tags/$TAG"
|