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: # Self-hosted mac mini — GitHub-hosted minutes are currently quota- # blocked. mac mini already has crane available via homebrew. runs-on: [self-hosted, macos, arm64] steps: - name: Ensure crane installed # HOMEBREW_NO_INSTALL_CLEANUP + HOMEBREW_NO_AUTO_UPDATE stop # brew from touching unrelated symlinks in /opt/homebrew owned # by other users on this shared runner — cleanup was exiting # non-zero even though crane itself installed successfully. env: HOMEBREW_NO_INSTALL_CLEANUP: "1" HOMEBREW_NO_AUTO_UPDATE: "1" HOMEBREW_NO_ENV_HINTS: "1" run: | if ! command -v crane >/dev/null 2>&1; then brew install crane fi crane version - 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"