Skip to content

ci: publish the version-named layer parameter from the pipeline - #846

Merged
bnusunny merged 2 commits into
mainfrom
ci/publish-versioned-layer-parameter
Sep 18, 2026
Merged

bnusunny merged 2 commits into
mainfrom
ci/publish-versioned-layer-parameter

Conversation

@bnusunny

@bnusunny bnusunny commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

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 Name contains the package version is replaced when the version changes, and UpdateReplacePolicy: Retain left the old name behind un-managed. Deploying a version whose name had already been orphaned then failed:

Resource of type 'AWS::SSM::Parameter' with identifier
'/lambda-web-adapter/layer/x86_64/1.0.1' already exists.  (AlreadyExists)

Re-releasing 1.0.1 after the v1.1.0 revert did exactly that, and deploy-beta failed on every merge to main for two months — taking e2e-test-zip and e2e-test-oci with it, since both are needs: [deploy-beta]. put-parameter --overwrite is 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 into deploy-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 latest pointer.

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.
@bnusunny
bnusunny requested a review from a team as a code owner September 17, 2026 20:14
@bnusunny
bnusunny merged commit 5b8790f into main Sep 18, 2026
7 checks passed
@bnusunny
bnusunny deleted the ci/publish-versioned-layer-parameter branch September 18, 2026 21:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants