diff --git a/.github/workflows/promote-latest.yml b/.github/workflows/promote-latest.yml new file mode 100644 index 00000000..8f5bc106 --- /dev/null +++ b/.github/workflows/promote-latest.yml @@ -0,0 +1,88 @@ +name: promote-latest + +# Manually retag ghcr.io/molecule-ai/platform:staging- → :latest +# (and the same for the tenant image). Use this to: +# +# 1. Promote a :staging- to prod before the canary fleet is live +# (one-off during the initial rollout). +# 2. Roll back :latest to a prior known-good digest after a bad +# promotion slipped past canary (use scripts/rollback-latest.sh +# for a local / emergency path; this workflow is for scheduled +# or from-browser promotions). +# +# Running this workflow needs no extra secrets — GitHub's default +# GITHUB_TOKEN has write:packages for repo-owned GHCR images, which +# is all we need for a remote retag via `crane tag`. + +on: + workflow_dispatch: + inputs: + sha: + description: 'Short sha to promote (e.g. 4c1d56e). Must match an existing :staging- tag.' + required: true + type: string + +permissions: + contents: read + packages: write + +env: + IMAGE_NAME: ghcr.io/molecule-ai/platform + TENANT_IMAGE_NAME: ghcr.io/molecule-ai/platform-tenant + +jobs: + promote: + runs-on: ubuntu-latest + steps: + - name: Install crane + run: | + curl -fsSL https://github.com/google/go-containerregistry/releases/download/v0.20.2/go-containerregistry_Linux_x86_64.tar.gz \ + | tar xz -C /usr/local/bin crane + + - name: GHCR login + run: | + echo "${{ secrets.GITHUB_TOKEN }}" \ + | crane auth login ghcr.io -u "${{ github.actor }}" --password-stdin + + - name: Retag platform image + run: | + set -eu + SRC="${IMAGE_NAME}:staging-${{ inputs.sha }}" + if ! crane digest "$SRC" >/dev/null 2>&1; then + echo "::error::$SRC not found in registry — double-check the sha." + exit 1 + fi + EXPECTED=$(crane digest "$SRC") + crane tag "$SRC" latest + ACTUAL=$(crane digest "${IMAGE_NAME}:latest") + if [ "$ACTUAL" != "$EXPECTED" ]; then + echo "::error::retag digest mismatch (expected $EXPECTED, got $ACTUAL)" + exit 1 + fi + echo "OK ${IMAGE_NAME}:latest → $ACTUAL" + + - name: Retag tenant image + run: | + set -eu + SRC="${TENANT_IMAGE_NAME}:staging-${{ inputs.sha }}" + if ! crane digest "$SRC" >/dev/null 2>&1; then + echo "::error::$SRC not found — tenant image may not have built for this sha." + exit 1 + fi + EXPECTED=$(crane digest "$SRC") + crane tag "$SRC" latest + ACTUAL=$(crane digest "${TENANT_IMAGE_NAME}:latest") + if [ "$ACTUAL" != "$EXPECTED" ]; then + echo "::error::tenant retag digest mismatch" + exit 1 + fi + echo "OK ${TENANT_IMAGE_NAME}:latest → $ACTUAL" + + - name: Summary + run: | + { + echo "## :latest promoted to staging-${{ inputs.sha }}" + echo + echo "Both platform + tenant images retagged. Prod tenants" + echo "will auto-pull within their 5-min update cycle." + } >> "$GITHUB_STEP_SUMMARY"