From bacc191fa8d89536f1fe8727e88255c087dba170 Mon Sep 17 00:00:00 2001 From: Tim Diekmann Date: Fri, 4 Sep 2026 15:07:04 +0200 Subject: [PATCH 1/2] SRE-1003: Let a deploy catalog entry choose its ECR architecture ECR holds one architecture per repository because Inspector does not scan manifest lists. Until now that architecture was arm64 for every service. A catalog entry can now set `ecr_arch`; ECR-only services build just that arch, GHCR services keep building both for their multi-arch manifest, and the PR-time amd64 skip no longer drops a service whose only arch is amd64. Defaults leave every existing service unchanged. --- .github/workflows/deploy.yml | 37 +++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6fbd1cedfe0..6079f7fe881 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -64,7 +64,9 @@ jobs: # context is their own directory, so a git diff of `paths` is a complete # change signal. `ecs` lists the ECS task definitions to redeploy when the # image publishes; one app may drive several (e.g. `graph` → graph + - # graph-admin + type-fetcher). + # graph-admin + type-fetcher). ECR holds one architecture per repository + # (Inspector does not scan manifest lists); `ecr_arch` names it and + # defaults to arm64. - name: Determine changed packages id: packages env: @@ -190,16 +192,19 @@ jobs: )]' <<<"$CATALOG") fi - # Build matrix: expand each affected entry to its arches. Skip amd64 - # for services that don't publish to GHCR (ECR is arm64-only) and - # also on PR events (arch divergence in Rust/Node is rare; - # merge_group still validates amd64 before main). + # Build matrix: expand each affected entry to its arches. GHCR + # services build both arches for the multi-arch manifest; ECR-only + # services build just their ECR arch. On PR events amd64 is skipped + # where arm64 covers the build (arch divergence in Rust/Node is rare; + # merge_group still validates amd64 before main), but never for a + # service whose only arch it is. BUILD_MATRIX=$(jq -c --argjson skip_amd64 "$SKIP_AMD64" ' [.[] as $e - | ({arch: "arm64"}, {arch: "amd64"}) - | $e + . - | select(($e.push | index("ghcr")) or .arch == "arm64") - | select($skip_amd64 != true or .arch != "amd64") + | ($e.ecr_arch // "arm64") as $ecr_arch + | (if ($e.push | index("ghcr")) then ["arm64", "amd64"] else [$ecr_arch] end) as $arches + | $arches[] + | $e + {arch: ., ecr_arch: $ecr_arch} + | select($skip_amd64 != true or .arch != "amd64" or ($arches | length) == 1) | del(.package, .ecs, .paths) ] | { include: . }' <<<"$AFFECTED_CATALOG") @@ -213,8 +218,8 @@ jobs: [.[] | .ecs[]] | { include: . } ' <<<"$AFFECTED_CATALOG") - # Staging matrix: services whose arm64 image is pushed to ECR and - # tagged :staging (one guard run per such service). + # Staging matrix: services whose image is pushed to ECR and tagged + # :staging (one guard run per such service). STAGING_MATRIX=$(jq -c ' map(select(.push | index("ecr"))) | { service: [.[].service] } ' <<<"$AFFECTED_CATALOG") @@ -342,6 +347,7 @@ jobs: env: PUSH_LIST: ${{ join(matrix.push, ' ') }} ARCH: ${{ matrix.arch }} + ECR_ARCH: ${{ matrix.ecr_arch }} REF: ${{ github.ref }} EVENT: ${{ github.event_name }} SERVICE: ${{ matrix.service }} @@ -358,7 +364,7 @@ jobs: [[ "$REF" == refs/heads/main ]] && is_main=true push_ecr=false - if [[ " $PUSH_LIST " == *" ecr "* && "$ARCH" == arm64 && "$is_main" == true && "$is_push_event" == true ]]; then + if [[ " $PUSH_LIST " == *" ecr "* && "$ARCH" == "$ECR_ARCH" && "$is_main" == true && "$is_push_event" == true ]]; then push_ecr=true fi push_ghcr=false @@ -378,8 +384,9 @@ jobs: echo "push_ghcr=$push_ghcr" echo "push=$push" - # ECR is single-arch (arm64), tag-based. Only immutable tags here - # (:sha / :run); the mutable :staging tag is set by the `stage` job. + # ECR is single-arch (the entry's ecr_arch), tag-based. Only + # immutable tags here (:sha / :run); the mutable :staging tag is + # set by the `stage` job. echo "ecr_tags< Date: Fri, 4 Sep 2026 15:15:04 +0200 Subject: [PATCH 2/2] SRE-1003: Let a catalog entry list the architectures it builds Publishing to GHCR no longer implies building both architectures: `arches` names what a service builds, and the ECR architecture follows from it when there is only one. The setup step fails when `ecr_arch` is not among the arches built, since the ECR push would otherwise silently never happen. --- .github/workflows/deploy.yml | 45 +++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6079f7fe881..9c64e98047a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -64,9 +64,11 @@ jobs: # context is their own directory, so a git diff of `paths` is a complete # change signal. `ecs` lists the ECS task definitions to redeploy when the # image publishes; one app may drive several (e.g. `graph` → graph + - # graph-admin + type-fetcher). ECR holds one architecture per repository - # (Inspector does not scan manifest lists); `ecr_arch` names it and - # defaults to arm64. + # graph-admin + type-fetcher). `arches` lists what to build; it defaults + # to both for GHCR services (multi-arch manifest) and to arm64 otherwise. + # ECR holds one architecture per repository (Inspector does not scan + # manifest lists): the service's only arch when it builds one, else + # arm64, unless `ecr_arch` says otherwise. - name: Determine changed packages id: packages env: @@ -192,21 +194,32 @@ jobs: )]' <<<"$CATALOG") fi - # Build matrix: expand each affected entry to its arches. GHCR - # services build both arches for the multi-arch manifest; ECR-only - # services build just their ECR arch. On PR events amd64 is skipped - # where arm64 covers the build (arch divergence in Rust/Node is rare; - # merge_group still validates amd64 before main), but never for a - # service whose only arch it is. + # Resolve each entry's arches and ECR arch (see the catalog comment), + # then fail loudly if the ECR arch is not among the arches built — + # the ECR push would otherwise silently never happen. + RESOLVED_CATALOG=$(jq -c ' + [.[] + | (.arches // (if (.push | index("ghcr")) then ["arm64", "amd64"] else ["arm64"] end)) as $arches + | (.ecr_arch // (if ($arches | length) == 1 then $arches[0] else "arm64" end)) as $ecr_arch + | . + {arches: $arches, ecr_arch: $ecr_arch} + ]' <<<"$AFFECTED_CATALOG") + BAD_ECR_ARCH=$(jq -c '[.[] | select((.push | index("ecr")) and ((.ecr_arch as $a | .arches | index($a)) | not)) | .service]' <<<"$RESOLVED_CATALOG") + if [[ "$(jq length <<<"$BAD_ECR_ARCH")" -gt 0 ]]; then + echo "::error ::ecr_arch is not among the arches built for: $BAD_ECR_ARCH" + exit 1 + fi + + # Build matrix: expand each entry to its arches. On PR events amd64 is + # skipped where arm64 covers the build (arch divergence in Rust/Node + # is rare; merge_group still validates amd64 before main), but never + # for a service whose only arch it is. BUILD_MATRIX=$(jq -c --argjson skip_amd64 "$SKIP_AMD64" ' [.[] as $e - | ($e.ecr_arch // "arm64") as $ecr_arch - | (if ($e.push | index("ghcr")) then ["arm64", "amd64"] else [$ecr_arch] end) as $arches - | $arches[] - | $e + {arch: ., ecr_arch: $ecr_arch} - | select($skip_amd64 != true or .arch != "amd64" or ($arches | length) == 1) - | del(.package, .ecs, .paths) - ] | { include: . }' <<<"$AFFECTED_CATALOG") + | $e.arches[] + | $e + {arch: .} + | select($skip_amd64 != true or .arch != "amd64" or ($e.arches | length) == 1) + | del(.package, .ecs, .paths, .arches) + ] | { include: . }' <<<"$RESOLVED_CATALOG") # Manifest matrix: services that publish multi-arch to GHCR. MANIFEST_MATRIX=$(jq -c '