diff --git a/.github/workflows/staging-dev-relay-image.yml b/.github/workflows/staging-dev-relay-image.yml new file mode 100644 index 00000000000..c8a746a0b94 --- /dev/null +++ b/.github/workflows/staging-dev-relay-image.yml @@ -0,0 +1,276 @@ +name: Staging dev relay image + +# Publishes pre-merge relay runtime images for bb-block staging only. +# +# Operators run this workflow from the default branch and provide a target ref in +# this repository. GitHub's workflow_dispatch permission model limits triggering +# to collaborators who can run repository workflows; no actor allowlist is kept +# here. The target ref is resolved to an immutable commit SHA before checkout, +# and the published tag is derived from that full SHA. + +on: + workflow_dispatch: + inputs: + target_ref: + description: "Branch, tag, refs/heads/*, or refs/tags/* in block/buzz to publish for bb-block staging" + required: true + type: string + +permissions: {} + +env: + IMAGE_NAME: ghcr.io/block/buzz-staging-dev + ECR_REPOSITORY: 929862310821.dkr.ecr.us-west-2.amazonaws.com/ghcr.io/block/buzz-staging-dev + +jobs: + resolve: + name: Resolve target ref + runs-on: ubuntu-24.04 + timeout-minutes: 5 + permissions: + contents: read + outputs: + target_sha: ${{ steps.resolve.outputs.target_sha }} + image_tag: ${{ steps.resolve.outputs.image_tag }} + steps: + - name: Require reviewed workflow from main in canonical repository + env: + DISPATCH_REF: ${{ github.ref }} + REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + if [ "$REPOSITORY" != "block/buzz" ]; then + echo "::error::Staging dev relay image publication is restricted to block/buzz" + exit 1 + fi + + if [ "$DISPATCH_REF" != "refs/heads/main" ]; then + echo "::error::Dispatch this workflow from main, not $DISPATCH_REF" + exit 1 + fi + + - name: Resolve target ref in this repository + id: resolve + env: + TARGET_REF_INPUT: ${{ inputs.target_ref }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + + target_ref=$(printf '%s' "$TARGET_REF_INPUT" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') + + if [ -z "$target_ref" ]; then + echo "target_ref must not be empty" >&2 + exit 1 + fi + + if [ ${#target_ref} -gt 255 ]; then + echo "target_ref is too long" >&2 + exit 1 + fi + + if [[ ! "$target_ref" =~ ^[A-Za-z0-9._/@-]+$ ]]; then + echo "target_ref contains unsupported characters; use a branch, tag, or full refs/heads/* or refs/tags/* name" >&2 + exit 1 + fi + + case "$target_ref" in + -*|*..*|*.lock|refs/pull/*|pull/*|*/pull/*) + echo "target_ref is not an allowed repository branch/tag/ref" >&2 + exit 1 + ;; + esac + + workdir=$(mktemp -d) + trap 'rm -rf "$workdir"' EXIT + git -C "$workdir" init --quiet + git -C "$workdir" remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" + + fetch_commit() { + local ref=$1 + git -C "$workdir" fetch --no-tags --depth=1 origin "$ref" >/dev/null 2>&1 + git -C "$workdir" rev-parse --verify "FETCH_HEAD^{commit}" + } + + target_sha="" + if [[ "$target_ref" == refs/heads/* || "$target_ref" == refs/tags/* ]]; then + target_sha=$(fetch_commit "$target_ref") || { + echo "target_ref did not resolve in ${GITHUB_REPOSITORY}: $target_ref" >&2 + exit 1 + } + else + if target_sha=$(fetch_commit "refs/heads/${target_ref}"); then + : + elif target_sha=$(fetch_commit "refs/tags/${target_ref}"); then + : + else + echo "target_ref did not resolve as a branch or tag in ${GITHUB_REPOSITORY}: $target_ref" >&2 + exit 1 + fi + fi + + target_sha=$(printf '%s' "$target_sha" | tr '[:upper:]' '[:lower:]') + if [[ ! "$target_sha" =~ ^[0-9a-f]{40}$ ]]; then + echo "resolved target SHA is invalid: $target_sha" >&2 + exit 1 + fi + + image_tag="dev-sha-${target_sha}-run-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + { + echo "target_sha=${target_sha}" + echo "image_tag=${image_tag}" + } >> "$GITHUB_OUTPUT" + + printf "Resolved \`%s\` to \`%s\`; image tag \`%s\`.\n" "$target_ref" "$target_sha" "$image_tag" >> "$GITHUB_STEP_SUMMARY" + + build: + name: Build staging relay runtime (${{ matrix.platform }}) + runs-on: ${{ matrix.runner }} + needs: resolve + timeout-minutes: 60 + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-24.04 + arch: amd64 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + arch: arm64 + steps: + - name: Checkout resolved target + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + ref: ${{ needs.resolve.outputs.target_sha }} + fetch-depth: 1 + persist-credentials: false + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + with: + buildkitd-config-inline: | + [worker.oci] + max-parallelism = 2 + + - name: Log in to GHCR + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 + with: + images: ${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.title=Buzz staging dev relay + org.opencontainers.image.description=Pre-merge Buzz relay runtime image for bb-block staging only + org.opencontainers.image.licenses=Apache-2.0 + org.opencontainers.image.revision=${{ needs.resolve.outputs.target_sha }} + + - name: Build and push runtime image by digest + id: build + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + with: + context: . + file: ./Dockerfile + target: runtime + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true + cache-from: | + type=registry,ref=${{ env.IMAGE_NAME }}-buildcache:${{ matrix.arch }} + cache-to: | + type=registry,ref=${{ env.IMAGE_NAME }}-buildcache:${{ matrix.arch }},mode=max,compression=zstd + + - name: Export digest + env: + DIGEST: ${{ steps.build.outputs.digest }} + run: | + set -euo pipefail + mkdir -p /tmp/digests-release + touch "/tmp/digests-release/${DIGEST#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: staging-dev-relay-digests-${{ matrix.arch }} + path: /tmp/digests-release/* + if-no-files-found: error + retention-days: 1 + + merge: + name: Publish staging relay runtime manifest + runs-on: ubuntu-24.04 + needs: + - resolve + - build + timeout-minutes: 15 + permissions: + contents: read + packages: write + steps: + - name: Download per-arch digests + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + path: /tmp/digests + pattern: staging-dev-relay-digests-* + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + + - name: Log in to GHCR + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Create and push manifest list + id: manifest + working-directory: /tmp/digests + env: + IMAGE_NAME: ${{ env.IMAGE_NAME }} + IMAGE_TAG: ${{ needs.resolve.outputs.image_tag }} + run: | + set -euo pipefail + digests=() + for digest in *; do + digests+=("${IMAGE_NAME}@sha256:${digest}") + done + + docker buildx imagetools create -t "${IMAGE_NAME}:${IMAGE_TAG}" "${digests[@]}" + merged_digest=$(docker buildx imagetools inspect "${IMAGE_NAME}:${IMAGE_TAG}" \ + --format '{{json .Manifest}}' | jq -r '.digest') + echo "digest=${merged_digest}" >> "$GITHUB_OUTPUT" + + - name: Deployment summary + env: + IMAGE_NAME: ${{ env.IMAGE_NAME }} + ECR_REPOSITORY: ${{ env.ECR_REPOSITORY }} + IMAGE_TAG: ${{ needs.resolve.outputs.image_tag }} + TARGET_SHA: ${{ needs.resolve.outputs.target_sha }} + MERGED_DIGEST: ${{ steps.manifest.outputs.digest }} + run: | + { + echo "### Published bb-block staging dev relay image" + echo + echo "**Source commit:** \`${TARGET_SHA}\`" + echo "**GHCR image:** \`${IMAGE_NAME}:${IMAGE_TAG}\`" + echo "**Manifest digest:** \`${MERGED_DIGEST}\`" + echo + echo "Set bb-block staging BPCI values to:" + echo '```yaml' + echo "buzz:" + echo " image:" + echo " repository: ${ECR_REPOSITORY}" + echo " tag: ${IMAGE_TAG}" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" diff --git a/docs/staging-dev-relay-images.md b/docs/staging-dev-relay-images.md new file mode 100644 index 00000000000..1fdfdd7e026 --- /dev/null +++ b/docs/staging-dev-relay-images.md @@ -0,0 +1,26 @@ +# Staging dev relay images + +Use the **Staging dev relay image** GitHub Actions workflow to publish a pre-merge Buzz relay runtime image for bb-block staging. + +1. Run `.github/workflows/staging-dev-relay-image.yml` from the default branch. +2. Enter a `target_ref` from `block/buzz` (`my-branch`, `refs/heads/my-branch`, `my-tag`, or `refs/tags/my-tag`). +3. Wait for the workflow summary. It resolves that ref to a commit and publishes only the relay `runtime` image to: + + ```text + ghcr.io/block/buzz-staging-dev:dev-sha-<40-character-commit-sha>-run-- + ``` + +4. In `squareup/builderbot-platform-core-infrastructure`, set bb-block staging values to the distinct pull-through ECR path and immutable tag from the workflow summary: + + ```yaml + buzz: + image: + repository: 929862310821.dkr.ecr.us-west-2.amazonaws.com/ghcr.io/block/buzz-staging-dev + tag: dev-sha-<40-character-commit-sha>-run-- + ``` + +This path is intentionally separate from the production/main relay image path (`ghcr.io/block/buzz`) so a staging-only branch deployment is obvious in BPCI. + +These images are for manual, pre-merge staging evaluation only. They are not release-qualified and must not be promoted to production or used by the canonical Kargo promotion path. + +While bb-block remains a shared staging environment, do not deploy a target ref through this runtime-only path if it changes `migrations/` or requires changes to `deploy/charts/buzz/`. Backwards-compatible migrations can still leave the shared database ahead of the restored `main` image. Use an isolated environment or deployment-time enforcement for those changes.