ci: publish the version-named layer parameter from the pipeline - #846
Merged
Merged
Conversation
The second half of #843. That change removed the version-named SSM pointer from the layer stacks, because a parameter whose Name contains the package version is replaced when the version changes and — retained — collides with its own orphan when a version is re-used, which wedged every merge to main for two months. The pointer itself is still wanted, so it is now written after deployment with `aws ssm put-parameter --overwrite`, which is idempotent and cannot collide. Two scripts: publish-layer-parameter.sh reads the layer ARN from the stack's own output and writes /lambda-web-adapter/layer/<arch>/<version>. It refuses a version containing a slash before making any AWS call, since such a name would fall outside the granted path and could address a `latest` pointer. configure-layer-parameter-iam.sh grants the PipelineExecutionRole ssm:PutParameter on that path. Run once per role, with administrator credentials in the role's account. This is a prerequisite: SAM-managed pipeline roles do not have the permission, and until the bootstrap is run the publish steps fail with AccessDenied. 69 roles are in scope — 32 gamma, 32 prod, 2 China gamma, 2 China prod, 1 beta, all distinct. Three things about the grant are deliberate: * Region-agnostic. `put-role-policy` replaces the whole named policy, so a per-region document meant a second invocation for another region silently revoked the first. The Resource wildcards the region instead, so one run per role covers every region and re-running is idempotent. * No region argument. With the region wildcarded there is nothing for the partition parsed from the role ARN to disagree with; an `aws` role paired with `cn-north-1` previously produced a policy matching nothing while reporting success. * An explicit Deny on `/latest`. The Allow has to wildcard the version segment, which also covers the `latest` pointer CloudFormation owns — and which the e2e fixture resolves at deploy time. Without the Deny, a mistyped version could overwrite stack-managed state out of band, where nobody would look for drift. In the workflows, both architectures deploy before either pointer is written. Ordered the other way — publish x86, deploy arm64 — a missed bootstrap left the region with a new x86 layer and no arm64 one, failed deploy-prod, skipped publish-to-public-ecr, and stopped the release after prod had been partially mutated. Ordered this way the same failure leaves both layers published and the pointers stale. Each stack name is now defined once per job and consumed by both the deploy and the publish step, rather than written twice in ten places with nothing asserting the two agree. Exercised: the publish script's argument, architecture and slash-in-version guards; the IAM script's ARN validation against a non-ARN, a short account id, a user ARN and a nested role path; the rendered policy document; and both workflows parse.
…ap script
The PipelineExecutionRole is managed in LambdaWebAdapterInfrastructureCDK, so the
grant belongs there rather than in an imperative script run by hand against 69 roles.
lib/infra-template.yaml now carries it in PipelineExecutionRolePermissions, allowing
ssm:PutParameter on parameter/lambda-web-adapter/layer/{x86_64,arm64}/* and denying
.../latest, which CloudFormation owns.
Deletes .github/scripts/configure-layer-parameter-iam.sh. Keeping it would have been
worse than redundant: it wrote a separate inline policy under its own name, so the two
would have coexisted on every role with no indication which was authoritative, and its
region-wildcarded Resource was broader than the per-region scope the infra template can
express — one pipeline-resources stack exists per region and its role only deploys in
that region.
The workflow comments and the publish script's header now say where the permission
comes from, so the AccessDenied case points at a region whose pipeline-resources stack
has not been updated rather than at a script nobody ran.
vicheey
approved these changes
Sep 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The second half of #843. That change deleted the version-named SSM pointer from the layer stacks to unwedge the pipeline; this one publishes the same pointer from the pipeline instead, where it cannot collide.
Why it moved out of CloudFormation
A parameter whose
Namecontains the package version is replaced when the version changes, andUpdateReplacePolicy: Retainleft the old name behind un-managed. Deploying a version whose name had already been orphaned then failed:Re-releasing 1.0.1 after the v1.1.0 revert did exactly that, and
deploy-betafailed on every merge to main for two months — takinge2e-test-zipande2e-test-ociwith it, since both areneeds: [deploy-beta].put-parameter --overwriteis idempotent and has no replacement semantics, so the failure mode does not exist.What runs
.github/scripts/publish-layer-parameter.sh <stack> <x86_64|arm64> <version> <region>reads the layer ARN from the stack's own output and writes/lambda-web-adapter/layer/<arch>/<version>. Wired intodeploy-beta(merge) and all four release stages: gamma, prod, China gamma, China prod.It refuses a version containing a slash before making any AWS call — such a name would fall outside the granted path and could address a
latestpointer.