Skip to content

feat(lambda): compose-native deploy-time invocation (CDK Trigger) — run a Function during deployment and fail the deployment if it fails #365

Description

@laazyj

Problem / use case

A customer wants to invoke a Lambda function as part of a CloudFormation deployment and have the deployment fail if that function fails.

Their concrete case: during deployment, call an external request/response HTTP API — a synchronous call whose result decides whether the release is valid. Shapes of the same need we should expect to cover:

  • Register/announce a release with an external service (release tracker, feature-flag service, CMDB) and abort the deploy if the registration is rejected.
  • Post-deploy verification: call the API the stack just deployed and fail the stack if it doesn't answer correctly, so a broken release rolls back rather than sitting live.
  • Seeding: write reference data through a service API once the backing resources exist, and fail if seeding fails — a half-seeded environment should never be reported as a successful deploy.

The common thread is that the call is synchronous and consequential: its outcome must gate the deployment, and a failure must produce a rolled-back stack and a diagnosable message, not a green deploy.

There is nothing in the library for this today. @composurecdk/custom-resources covers SDK calls that have no CFN resource, which is a different job — it invokes an AWS API from a provider Lambda, not the consumer's own function. Nothing in @composurecdk/lambda invokes a function at deploy time.

Requirements

  1. Deploy-time invocation of a function built by createFunctionBuilder. The handler is an ordinary component of the composed system, wired by ref(...) like any other cross-component reference — not a construct the consumer has to reach for outside compose().
  2. Synchronous by default. The deployment waits for the function's response. Fire-and-forget must not be the default; if it is offered at all, choosing it must be explicit at the call site.
  3. A failing function fails the deployment. If the handler throws, times out, or is not invocable, the deployment must fail and roll back. This is the requirement that rules out the obvious workaround (see Alternatives) and should be the primary thing the tests assert.
  4. The failure is diagnosable. The handler's error message must reach the CloudFormation stack events; a reviewer reading the failed deploy should see why the API call failed without opening CloudWatch.
  5. Ordering is expressed as data in the compose graph. Consumers must be able to say "run this after these components exist" using refs, consistent with how the rest of the library wires relationships — not procedural node.addDependency glue in an afterBuild hook. Requirement (1) covers the handler dependency; this covers everything else the call needs in place first (the API, the table, the permissions).
  6. A bounded, configurable wait. The maximum time the deployment will wait for the handler is configurable, and the relationship to the handler's own timeout is enforced or at least surfaced — the wait must not be shorter than the handler's timeout, or a slow API is reported as the wrong failure.
  7. Re-invocation semantics are explicit and documented. Consumers must be able to tell whether a given deploy will invoke the function again. Both behaviours have real use cases — "only when the handler changes" for idempotent registration, "every deploy" for verification — and the default must be stated in the README rather than inherited silently.
  8. Delete-time behaviour is documented. Whether the function runs on stack deletion must be stated, and if it does not, the README should point at the existing custom-resource path for teardown-time work.
  9. IAM for the invocation is handled by the library, scoped to the target function. Consumers should not have to hand-write an invoke policy; the handler's own permissions remain the function builder's business (.configureRole).
  10. The correctness trap is documented. A handler that returns an error object rather than throwing is a successful invocation and will not fail the deployment. Whatever we ship must call this out prominently — it is the most likely way a consumer gets a silently-green deploy — ideally alongside a worked example in the package README.

Non-goals

  • Scheduled or event-driven invocation — that is @composurecdk/events and the event-source seam.
  • Replacing @composurecdk/custom-resources. That remains the right tool for SDK calls with no CFN resource, and for anything needing create/update/delete semantics.
  • Long-running orchestration during deploy (waiters, polling for eventual consistency). If the call cannot complete inside a bounded synchronous window, this is not the mechanism.

Alternatives considered

Raw aws-cdk-lib/triggers.Trigger in an afterBuild hook or a hand-written Lifecycle adapter. This works today — Trigger's provider checks the invoke response's status code and FunctionError and throws, which is exactly requirement (3). But every consumer re-derives the same ~15 lines of adapter, has to know that InvocationType.REQUEST_RESPONSE is what makes the failure propagate, and has to discover the re-invocation and delete-time semantics from the CDK source. That is the boilerplate this issue is asking to absorb.

createAwsCustomResourceBuilder() with a Lambda.invoke call. Fails requirement (3) and does so silently, which is the strongest argument for solving this properly. The AwsCustomResource provider only reports FAILED when the SDK call throws; a handler that throws still returns HTTP 200 with a FunctionError field, so the deployment goes green while the API call failed. A consumer reaching for the nearest existing tool in the library lands here and gets the wrong answer.

Do nothing; document the raw pattern in the @composurecdk/lambda README. Cheapest option, and defensible if this is a one-customer need. It leaves requirements (5) and (9) to each consumer and keeps the AwsCustomResource trap one wrong turn away, but it may be the right first step if we want a second use case before committing to API surface.

Open questions for maintainer judgement

  • Placement. @composurecdk/lambda (it is a thing you do to a function), or a separate package (it is a deployment-lifecycle concern and would pull aws-cdk-lib/triggers into lambda's surface)? Either way it needs the new-package treatment if it is separate — dual ESM/CJS, DUAL_PACKAGES registration, a cdk-floors.json entry.
  • Shape. A builder (create...Builder()), or a function returning a Lifecycle component? It creates one construct and has no meaningful props surface of its own, which is a weaker fit for the builder pattern than most of the library.
  • Does this clear the examples bar? Per AGENTS.md, an example must demonstrate a system rather than a resource. "Deploy an API, then verify it during deployment and roll back if it fails" arguably does, and it is smoke-testable — but it may be better as an extension to an existing API example than a new stack.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions