From da395f7587797f9fb103c5d6d760a63e84241177 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 15:53:24 +0000 Subject: [PATCH 1/3] Initial plan From 5d83dd488d9f9d8d02d388855b1d5fc39023cae5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 15:58:14 +0000 Subject: [PATCH 2/3] Decouple migration job image rollout from Terraform --- .github/workflows/deploy.yml | 22 ++------- docs/migrations.md | 15 +++++++ infra/migrations.tf | 8 +++- infra/scripts/preflight-bootstrap-images.sh | 49 +++++++++++++++++++++ 4 files changed, 74 insertions(+), 20 deletions(-) create mode 100755 infra/scripts/preflight-bootstrap-images.sh diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index adde3bde..3b6e0101 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -364,7 +364,7 @@ jobs: - name: Terraform Validate run: terraform validate - - name: Ensure migration image exists for Terraform + - name: Preflight bootstrap images for Terraform if: github.ref == 'refs/heads/main' working-directory: . run: | @@ -374,27 +374,11 @@ jobs: registry_endpoint=$(terraform -chdir=infra output -raw AZURE_CONTAINER_REGISTRY_ENDPOINT 2>/dev/null || true) if [[ -z "$registry_name" || -z "$registry_endpoint" ]]; then - echo "No existing container registry output found; skipping migration image bootstrap." + echo "No existing container registry output found; skipping bootstrap image preflight." exit 0 fi - if az acr repository show-tags \ - --name "$registry_name" \ - --repository migrations \ - --query "contains(@, 'latest')" \ - --output tsv 2>/dev/null | grep -q true; then - echo "Migration image already exists: $registry_endpoint/migrations:latest" - exit 0 - fi - - echo "Bootstrapping migration image for Terraform validation." - az acr login --name "$registry_name" - docker build -f api/Dockerfile \ - --target migrations-runtime \ - --build-arg BUILDKIT_INLINE_CACHE=1 \ - --label git-commit=${{ github.sha }} \ - -t "$registry_endpoint/migrations:latest" . - docker push "$registry_endpoint/migrations:latest" + infra/scripts/preflight-bootstrap-images.sh "$registry_name" "$registry_endpoint" - name: Terraform Plan run: terraform plan -out=tfplan -input=false -lock-timeout=120s diff --git a/docs/migrations.md b/docs/migrations.md index 90faa09c..c9d921b7 100644 --- a/docs/migrations.md +++ b/docs/migrations.md @@ -26,6 +26,21 @@ multi-worker race to defend against. > ever executes `alembic upgrade head` per deploy. `alembic/env.py` relies > on this and does not include application-layer concurrency controls. +### Bootstrap image preflight + +Terraform keeps the migration job shape, but it no longer manages rollout tags. +The job template now uses a bootstrap image (`migrations:bootstrap`) and ignores +future image changes. Deploy uses commit SHA tags when it starts the job. + +Before `terraform apply`, check that the bootstrap image exists in ACR: + +```bash +infra/scripts/preflight-bootstrap-images.sh +``` + +The deploy workflow runs this same preflight in CI and fails early with a clear +error if the bootstrap image is missing. + ### Failure Detection `alembic/env.py` logs and re-raises every exception from diff --git a/infra/migrations.tf b/infra/migrations.tf index a7288712..797ccf81 100644 --- a/infra/migrations.tf +++ b/infra/migrations.tf @@ -7,6 +7,12 @@ resource "azurerm_container_app_job" "migrations" { replica_retry_limit = 0 tags = local.tags + lifecycle { + ignore_changes = [ + template[0].container[0].image, + ] + } + identity { type = "UserAssigned" identity_ids = [azurerm_user_assigned_identity.migrations.id] @@ -25,7 +31,7 @@ resource "azurerm_container_app_job" "migrations" { template { container { name = "migrations" - image = "${azurerm_container_registry.main.login_server}/migrations:latest" + image = "${azurerm_container_registry.main.login_server}/migrations:bootstrap" command = ["python"] args = ["scripts/run_migrations.py"] cpu = 0.5 diff --git a/infra/scripts/preflight-bootstrap-images.sh b/infra/scripts/preflight-bootstrap-images.sh new file mode 100755 index 00000000..c8645b95 --- /dev/null +++ b/infra/scripts/preflight-bootstrap-images.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +registry_name="${1:-}" +registry_endpoint="${2:-}" + +if [[ -z "$registry_name" || -z "$registry_endpoint" ]]; then + echo "Usage: $0 " >&2 + exit 2 +fi + +missing=0 + +check_image() { + local repository="$1" + local tag="$2" + local image_ref="$registry_endpoint/$repository:$tag" + + if az acr repository show-tags \ + --name "$registry_name" \ + --repository "$repository" \ + --query "contains(@, '$tag')" \ + --output tsv 2>/dev/null | grep -q true; then + echo "Found required bootstrap image: $image_ref" + return + fi + + echo "::error::Missing required bootstrap image: $image_ref" + missing=1 +} + +check_image migrations bootstrap + +if [[ "$missing" -eq 0 ]]; then + exit 0 +fi + +cat < Date: Mon, 25 May 2026 15:59:23 +0000 Subject: [PATCH 3/3] Make bootstrap preflight errors local-friendly --- infra/scripts/preflight-bootstrap-images.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/infra/scripts/preflight-bootstrap-images.sh b/infra/scripts/preflight-bootstrap-images.sh index c8645b95..b892f5aa 100755 --- a/infra/scripts/preflight-bootstrap-images.sh +++ b/infra/scripts/preflight-bootstrap-images.sh @@ -11,6 +11,16 @@ fi missing=0 +emit_error() { + local message="$1" + if [[ "${GITHUB_ACTIONS:-}" == "true" ]]; then + echo "::error::$message" + return + fi + + echo "ERROR: $message" >&2 +} + check_image() { local repository="$1" local tag="$2" @@ -25,7 +35,7 @@ check_image() { return fi - echo "::error::Missing required bootstrap image: $image_ref" + emit_error "Missing required bootstrap image: $image_ref" missing=1 }