Skip to content

feat(lambda): external invoke permissions, function DLQ and scope identity metadata - #45

Closed
andres-nullplatform wants to merge 5 commits into
mainfrom
feat/lambda-external-triggers
Closed

feat(lambda): external invoke permissions, function DLQ and scope identity metadata#45
andres-nullplatform wants to merge 5 commits into
mainfrom
feat/lambda-external-triggers

Conversation

@andres-nullplatform

Copy link
Copy Markdown
Contributor

Covers the Spin gaps that live in this repo (CLIEN-1002). The scope covered the function well but not its invocation: Spin's lambdas are triggered by DynamoDB streams, SQS, API Gateway authorizers, EventBridge and S3, none of which the scope could reach.

What's in

Gap Change
External invoke permissions (blocking) triggers.invoke_permissions in the scope-configuration, reconciled onto the main alias
Function dead letter queue dead_letter_target_arn attribute, with an IAM grant scoped to that ARN
Scope identity lambda.function_name / function_arn / alias_arn / main_alias / execution_role_* published to the NRN
provided.al2 runtime added to the spec enum (kafka-dlt-manager uses it)

Out of scope: the SQS event source mapping and the missing stream ESM parameters (both in the SQS / DynamoDB services — the NRN identity above is their prerequisite), the EventBridge rules themselves (they stay in Spin's Terraform and only need the permission), and gaps 3/4 (VPC overrides, advanced logging), dropped by decision.

Why these run outside Terraform

The scope's tofu run only happens on create/delete. Its state holds the placeholder function while deployments mutate the real one through the AWS CLI, so an apply on update sees drift and reverts the function to the placeholder — publishing a placeholder version and moving the main alias to it. lifecycle.ignore_changes on aws_lambda_function.main is currently an empty block, and compute/lambda/setup also forces runtime=nodejs22.x on placeholder-zip deploys, which would break a .NET function regardless.

So invoke permissions and the DLQ are reconciled by idempotent AWS CLI scripts that run on both create and update and touch only the field they own. Making update-scope tofu-driven is real work with its own e2e validation — see Follow-ups.

Two safety properties worth reviewing closely:

  • Permissions target the alias, so external triggers follow blue/green weights.
  • Only Sids carrying the np-ext- prefix are ever removed. AllowAPIGatewayInvoke, AllowALBInvoke and hand-made statements are untouched (covered by tests).

Incidental fixes

  • update-scope was unreachable. It declares "type": "custom", map_custom_action had no case for it, so it looked for a non-existent update-scope.yaml.
  • delete-scope would have failed once a DLQ existed: IAM refuses DeleteRole while unmanaged inline policies remain. cleanup_role_inline_policies runs before the destroy.
  • Failure assertions in the bats suite were vacuous. These scripts are sourced by the engine and use return; run as bash <script>, return is a warning that doesn't stop execution, so the script always exited 0. wait_provisioned_concurrency looped forever for the same reason — its timeout branch couldn't return out of the poll loop. Locally this was masked by utils/log using ${level,,}, which is a fatal error on bash 3.2 and aborted scripts for the wrong reason.

Verification

ok / not ok, per suite, this branch vs main:

Suite main branch
scope 58 / 29 96 / 26
deployment (excl. polling) — / 76 — / 64
wait_provisioned_concurrency 6 / 12 11 / 7
new test files 39 / 0

No new failures anywhere; 15 pre-existing ones fixed. shellcheck clean on everything new, tofu fmt clean on the files touched.

Not yet verified end to end against a real AWS account — that's the check that matters for "does not alter code, env vars or the active version of a running lambda". Steps: create a scope with permissions + DLQ, deploy real code, run update-scope changing only the permissions, then confirm CodeSha256, Environment and the alias version are unchanged.

Follow-ups (not in this PR)

  • update-scope can't apply function configuration (memory, timeout, runtime, layers…); those still land on the next deployment.
  • ~97 pre-existing test failures remain: orphaned files (deployment/tests/scripts/store_nrn_metadata.bats points at a script that moved) and tests that shell out to the real AWS CLI.
  • The README "Scope Structure" tree still describes the old deployment/-rooted tofu layout.

andres-nullplatform and others added 5 commits September 2, 2026 08:36
map_custom_action had no case for it, so the workflow was unreachable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
These scripts use return, so `bash <script>` never propagates their exit
code and every assert_failure passed vacuously. Also lowercases log
levels with tr, since ${level,,} aborts on bash 3.2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reconciled by AWS CLI scripts on create and update, not by tofu: the
scope state holds the placeholder function, so an apply on update would
roll back deployed code. delete-scope drops the DLQ inline policy first,
or DeleteRole fails. Also adds the provided.al2 runtime.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Lets a service attach an event source mapping to the alias instead of
re-deriving names. update-scope backfills pre-existing scopes from AWS.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Also fixes a stale capability table and test paths.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@andres-nullplatform
andres-nullplatform deleted the feat/lambda-external-triggers branch September 2, 2026 13:55
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.

1 participant