diff --git a/cdk-custom-resource-with-wait-condition/.gitignore b/cdk-custom-resource-with-wait-condition/.gitignore new file mode 100644 index 000000000..28c650ddc --- /dev/null +++ b/cdk-custom-resource-with-wait-condition/.gitignore @@ -0,0 +1,10 @@ +*.js +!jest.config.js +*.d.ts +node_modules + +# CDK asset staging directory +.cdk.staging +cdk.out + +dist/ diff --git a/cdk-custom-resource-with-wait-condition/.npmignore b/cdk-custom-resource-with-wait-condition/.npmignore new file mode 100644 index 000000000..c1d6d45dc --- /dev/null +++ b/cdk-custom-resource-with-wait-condition/.npmignore @@ -0,0 +1,6 @@ +*.ts +!*.d.ts + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/cdk-custom-resource-with-wait-condition/README.md b/cdk-custom-resource-with-wait-condition/README.md new file mode 100644 index 000000000..b8fc4a191 --- /dev/null +++ b/cdk-custom-resource-with-wait-condition/README.md @@ -0,0 +1,86 @@ +# Use AWS CloudFormation Wait Conditions for long-running custom resources + +This project demonstrates how to implement [AWS CloudFormation](https://aws.amazon.com/cloudformation/) custom resources that can run for up to 12 hours using [Wait Conditions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-waitcondition.html). + +AWS Lambda functions have a 15-minute execution timeout, limiting CloudFormation custom resources to short-running operations. This pattern extends custom resource execution time to 12 hours by decoupling lifecycle management from process execution. + +![Architecture Diagram](./image/architecture.png) + +## How it works + +The architecture uses four components: + +1. **Custom Resource Handler Lambda** - Receives CloudFormation lifecycle events and starts a Step Function execution, then returns success immediately to prevent timeouts +2. **Step Function** - Orchestrates the long-running process with built-in retry mechanisms and error handling +3. **Completion Signal Handler Lambda** - Sends success/failure signals to the Wait Condition Handle when the process completes +4. **Wait Condition** - Blocks CloudFormation stack completion until receiving the completion signal + +This approach enables asynchronous processing with proper CloudFormation integration, supporting use cases like database migrations, complex infrastructure provisioning, and third-party system integrations that exceed Lambda's 15-minute limit. + +Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. + +## Requirements + +* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. +* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured +* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +* [Node and NPM](https://nodejs.org/en/download/) installed +* [AWS Cloud Development Kit](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) (AWS CDK) installed + +## Deploy + +1. Clone the project to your local working directory + + ```sh + git clone https://github.com/aws-samples/serverless-patterns + ``` + +1. Change the working directory to this pattern's directory + + ```sh + cd cdk-custom-resource-with-wait-condition + ``` + +1. Install the project dependencies + + ```sh + npm install + ``` + +1. Deploy the stack to your default AWS account and region + + ```sh + cdk deploy --require-approval never + ``` + +## Test + +You can review Amazon CloudWatch logs for the Lambda functions and Step Function execution to confirm that the long-running process completed successfully and the wait condition was signaled. + +## Cleanup + +Run the given command to delete the resources that were created. It might take some time for the CloudFormation stack to get deleted. + +```sh +cdk destroy -f +``` + +## Useful commands + +* `npm run build` compile typescript to js +* `npm run watch` watch for changes and compile +* `npm run test` perform the jest unit tests +* `npx cdk deploy` deploy this stack to your default AWS account/region +* `npx cdk diff` compare deployed stack with current state +* `npx cdk synth` emits the synthesized CloudFormation template + +## References + +1. [Custom resources](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html) +2. [Using wait conditions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-waitcondition.html) +3. [Implementing long running deployments with AWS CloudFormation Custom Resources using AWS Step Functions](https://aws.amazon.com/blogs/devops/implementing-long-running-deployments-with-aws-cloudformation-custom-resources-using-aws-step-functions/) + +---- +Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/cdk-custom-resource-with-wait-condition/bin/app.ts b/cdk-custom-resource-with-wait-condition/bin/app.ts new file mode 100644 index 000000000..542024c0c --- /dev/null +++ b/cdk-custom-resource-with-wait-condition/bin/app.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env node +import { App } from 'aws-cdk-lib'; +import { DemoStack } from '../lib/demo-stack'; + +const app = new App(); + +// Deploy demo stack showing custom resource with wait condition pattern +new DemoStack(app, 'CdkCustomResourceWithWaitConditionStack', { + stackName: 'Custom-Resource-With-Wait-Condition-Demo', + description: 'Demo of a custom resource with a wait condition', + env: { + region: process.env.CDK_DEFAULT_REGION, + account: process.env.CDK_DEFAULT_ACCOUNT, + }, +}); \ No newline at end of file diff --git a/cdk-custom-resource-with-wait-condition/cdk.json b/cdk-custom-resource-with-wait-condition/cdk.json new file mode 100644 index 000000000..47d584419 --- /dev/null +++ b/cdk-custom-resource-with-wait-condition/cdk.json @@ -0,0 +1,86 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/app.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, + "@aws-cdk/aws-eks:nodegroupNameAttribute": true, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false, + "@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false, + "@aws-cdk/aws-ecs:disableEcsImdsBlocking": true, + "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true, + "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true, + "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true, + "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true, + "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true, + "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true, + "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true, + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true, + "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true, + "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true, + "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true, + "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true + } +} diff --git a/cdk-custom-resource-with-wait-condition/example-pattern.json b/cdk-custom-resource-with-wait-condition/example-pattern.json new file mode 100644 index 000000000..7f97dce65 --- /dev/null +++ b/cdk-custom-resource-with-wait-condition/example-pattern.json @@ -0,0 +1,72 @@ +{ + "title": "Custom resource with wait condition", + "description": "Use AWS CloudFormation Wait Conditions for long-running custom resources up to 12 hours", + "language": "TypeScript", + "level": "200", + "framework": "AWS CDK", + "introBox": { + "headline": "How it works", + "text": [ + "AWS Lambda functions have a 15-minute execution timeout, limiting CloudFormation custom resources to short-running", + "operations. This pattern extends custom resource execution time to 12 hours using CloudFormation Wait Conditions.", + "The architecture uses four components working together: A Custom Resource Handler Lambda receives CloudFormation", + "lifecycle events and immediately starts a Step Function execution, then returns success to prevent timeouts.", + "The Step Function orchestrates the long-running process with built-in retry mechanisms and error handling.", + "When the process completes, a Completion Signal Handler Lambda sends success or failure signals to a Wait", + "Condition Handle URL. The Wait Condition blocks CloudFormation stack completion until receiving the signal.", + "This decouples custom resource lifecycle management from process execution, enabling asynchronous processing", + "with proper CloudFormation integration. The Step Function provides visual workflow monitoring and state management", + "while the Wait Condition ensures stack operations complete only after the long-running process finishes.", + "The pattern deploys two Lambda functions, one Step Function, a Wait Condition Handle, and a Wait Condition.", + "Use cases include database migrations, complex infrastructure provisioning, and third-party system integrations", + "that exceed Lambda's 15-minute limit." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/cdk-custom-resource-with-wait-condition", + "templateURL": "serverless-patterns/cdk-custom-resource-with-wait-condition", + "projectFolder": "cdk-custom-resource-with-wait-condition", + "templateFile": "lib/demo-stack.ts" + } + }, + "resources": { + "bullets": [ + { + "text": "Custom resources", + "link": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html" + }, + { + "text": "Using wait conditions", + "link": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-waitcondition.html" + }, + { + "text": "Implementing long running deployments with AWS CloudFormation Custom Resources using AWS Step Functions", + "link": "https://aws.amazon.com/blogs/devops/implementing-long-running-deployments-with-aws-cloudformation-custom-resources-using-aws-step-functions/" + } + ] + }, + "deploy": { + "text": [ + "cdk deploy --require-approval never" + ] + }, + "testing": { + "text": [ + "Review Amazon CloudWatch logs for the Lambda functions and Step Function execution to confirm that the", + "long-running process completed successfully and the wait condition was signaled." + ] + }, + "cleanup": { + "text": [ + "Delete the stack: cdk destroy -f." + ] + }, + "authors": [ + { + "name": "Dmitry Gulin", + "bio": "Senior Delivery Consultant, AWS.", + "linkedin": "dmitry-gulin" + } + ] +} diff --git a/cdk-custom-resource-with-wait-condition/image/architecture.png b/cdk-custom-resource-with-wait-condition/image/architecture.png new file mode 100644 index 000000000..8994c876f Binary files /dev/null and b/cdk-custom-resource-with-wait-condition/image/architecture.png differ diff --git a/cdk-custom-resource-with-wait-condition/lib/demo-stack.ts b/cdk-custom-resource-with-wait-condition/lib/demo-stack.ts new file mode 100644 index 000000000..4a720d241 --- /dev/null +++ b/cdk-custom-resource-with-wait-condition/lib/demo-stack.ts @@ -0,0 +1,98 @@ +import { CfnWaitConditionHandle, RemovalPolicy, Stack, StackProps, CustomResource, CfnWaitCondition } from 'aws-cdk-lib'; +import { Architecture, LoggingFormat, Runtime } from 'aws-cdk-lib/aws-lambda'; +import { NodejsFunction, NodejsFunctionProps } from 'aws-cdk-lib/aws-lambda-nodejs'; +import { LogGroup, LogGroupProps, RetentionDays } from 'aws-cdk-lib/aws-logs'; +import { StateMachine, DefinitionBody } from 'aws-cdk-lib/aws-stepfunctions'; +import { Construct } from 'constructs'; + +/** + * Demo stack showing custom resource with wait condition pattern. + * Uses Step Functions for long-running processes and wait conditions for synchronization. + */ +export class DemoStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + // Create unique wait condition handle for each deployment + // Note: WaitCondition resources don't support updates, requiring new handles per deployment + const resourceName: string = `WaitConditionHandle-${Date.now()}`; + const cfnWaitConditionHandle = new CfnWaitConditionHandle(this, resourceName); + + // Common configuration for Lambda functions + const commonLambdaProps: Partial = { + architecture: Architecture.ARM_64, + loggingFormat: LoggingFormat.JSON, + runtime: Runtime.NODEJS_22_X, + memorySize: 256, + }; + + // Common configuration for CloudWatch log groups + const commonLogGroupProps: Partial = { + removalPolicy: RemovalPolicy.DESTROY, + retention: RetentionDays.ONE_WEEK, + }; + + // Lambda function that handles custom resource lifecycle events + // Starts Step Function execution and returns immediately + const customResourceHandler = new NodejsFunction(this, 'CustomResourceHandler', { + ...commonLambdaProps, + functionName: 'CustomResourceHandler', + entry: 'lib/lambda/custom-resource-handler.mts', + logGroup: new LogGroup(this, 'CustomResourceHandlerLogGroup', { + ...commonLogGroupProps, + logGroupName: `/demo/CustomResourceHandler`, + }), + }); + + // Lambda function that sends completion signals to wait condition handles + const sendCompletionSignalHandler = new NodejsFunction(this, 'SendCompletionSignalHandler', { + ...commonLambdaProps, + functionName: 'SendCompletionSignalHandler', + entry: 'lib/lambda/send-completion-signal.mts', + logGroup: new LogGroup(this, 'SendCompletionSignalHandlerLogGroup', { + ...commonLogGroupProps, + logGroupName: `/demo/SendCompletionSignalHandler`, + }), + }); + + // Step Function that simulates a long-running process + // Invokes completion signal Lambda when process finishes + const longRunningProcessStateMachine = new StateMachine(this, 'LongRunningProcessStateMachine', { + definitionBody: DefinitionBody.fromFile('lib/sfn/long-running-process.asl.json'), + stateMachineName: 'LongRunningProcessStateMachine', + definitionSubstitutions: { + SendCompletionSignalLambdaArn: sendCompletionSignalHandler.functionArn, + }, + logs: { + destination: new LogGroup(this, 'LongRunningProcessStateMachineLogGroup', { + ...commonLogGroupProps, + logGroupName: `/demo/LongRunningProcessStateMachine`, + }), + }, + }); + + // Grant permissions for Lambda functions to interact with Step Function + longRunningProcessStateMachine.grantStartExecution(customResourceHandler); + sendCompletionSignalHandler.grantInvoke(longRunningProcessStateMachine); + + // Custom resource that triggers the long-running process + const customResource = new CustomResource(this, 'CustomResource', { + serviceToken: customResourceHandler.functionArn, + properties: { + WaitConditionHandle: cfnWaitConditionHandle.ref, + StateMachineArn: longRunningProcessStateMachine.stateMachineArn, + } + }); + + // Wait condition that blocks stack completion until process finishes + const waitCondition = new CfnWaitCondition(this, 'WaitCondition', { + count: 1, + handle: cfnWaitConditionHandle.ref, + timeout: '60', // 60 seconds timeout + }); + + // Ensure wait condition depends on custom resource and state machine + waitCondition.node.addDependency(customResource); + waitCondition.node.addDependency(longRunningProcessStateMachine); + } +} diff --git a/cdk-custom-resource-with-wait-condition/lib/lambda/custom-resource-handler.mts b/cdk-custom-resource-with-wait-condition/lib/lambda/custom-resource-handler.mts new file mode 100644 index 000000000..7551e57cf --- /dev/null +++ b/cdk-custom-resource-with-wait-condition/lib/lambda/custom-resource-handler.mts @@ -0,0 +1,84 @@ +import { CloudFormationCustomResourceEvent, CloudFormationCustomResourceHandler, CloudFormationCustomResourceResponse, Context } from "aws-lambda"; +import { SFNClient, StartExecutionCommand } from "@aws-sdk/client-sfn"; +import { randomUUID } from "node:crypto"; + +const sfnClient = new SFNClient({}); + +//#region Local TypeScript version of the cfn-response package +type Status = 'SUCCESS' | 'FAILED'; + +/** + * Creates a CloudFormation custom resource response template + */ +function responseTemplate(event: CloudFormationCustomResourceEvent, context: Context, physicalResourceId: string, reason?: string) { + return { + PhysicalResourceId: physicalResourceId, + LogicalResourceId: event.LogicalResourceId, + RequestId: event.RequestId, + StackId: event.StackId, + NoEcho: false, + Reason: reason || (`See the details in CloudWatch Log Stream: ${context.logStreamName}`), + }; +}; + +/** + * Sends response back to CloudFormation + */ +async function send(event: CloudFormationCustomResourceEvent, context: Context, status: Status, physicalResourceId: string, reason?: string): Promise { + const response: CloudFormationCustomResourceResponse = { + ...responseTemplate(event, context, physicalResourceId, reason), + Status: status, + }; + const responseBody = JSON.stringify(response); + const sendResult = await fetch(event.ResponseURL, { + method: 'PUT', + body: responseBody, + }); + console.log(`${sendResult.status}: ${sendResult.statusText}`); +}; +//#endregion + +/** + * Custom resource handler that starts a Step Function for long-running processes. + * Returns immediately after starting the execution - the Step Function handles completion signaling. + */ +export const handler: CloudFormationCustomResourceHandler = async (event, context) => { + console.log(event); + + // Generate unique physical resource ID for new resources, reuse for updates/deletes + const physicalResourceId = (event.RequestType === 'Update' || event.RequestType === 'Delete') + ? event.PhysicalResourceId + : randomUUID(); + + try { + // Extract required properties from custom resource + const stateMachineArn = event.ResourceProperties.StateMachineArn; + const waitConditionHandle = event.ResourceProperties.WaitConditionHandle; + + // Prepare payload for Step Function execution + const stepFunctionInput = { + RequestType: event.RequestType, + WaitConditionHandle: waitConditionHandle, + PhysicalResourceId: physicalResourceId, + LogicalResourceId: event.LogicalResourceId, + RequestId: event.RequestId, + StackId: event.StackId + }; + + // Start Step Function execution asynchronously + const startExecutionCommand = new StartExecutionCommand({ + stateMachineArn: stateMachineArn, + input: JSON.stringify(stepFunctionInput) + }); + + const executionResult = await sfnClient.send(startExecutionCommand); + console.log('Step Function execution started:', executionResult.executionArn); + + // Return success immediately - Step Function will signal completion via wait condition + await send(event, context, 'SUCCESS', physicalResourceId, 'Step Function execution started successfully'); + } + catch (e: any) { + console.error(e.message); + await send(event, context, 'FAILED', physicalResourceId, e.message); + } +} \ No newline at end of file diff --git a/cdk-custom-resource-with-wait-condition/lib/lambda/send-completion-signal.mts b/cdk-custom-resource-with-wait-condition/lib/lambda/send-completion-signal.mts new file mode 100644 index 000000000..68a047eeb --- /dev/null +++ b/cdk-custom-resource-with-wait-condition/lib/lambda/send-completion-signal.mts @@ -0,0 +1,48 @@ +import { Handler } from "aws-lambda"; + +/** + * Status values for wait condition signals + */ +enum Status { + SUCCESS = 'SUCCESS', + FAILURE = 'FAILURE', +} + +/** + * Event payload for task completion signaling + */ +interface TaskCompletionEvent { + readonly waitConditionHandle: string; + readonly status: Status; + readonly uniqueId: string; + readonly data: string; + readonly reason: string; +} + +/** + * Sends completion signal to CloudFormation wait condition handle + */ +async function send(responseURL: string, status: Status, reason: string, uniqueId: string, data: string): Promise { + const response = { + Status: status, + Reason: reason, + UniqueId: uniqueId, + Data: data, + }; + const responseBody = JSON.stringify(response); + const sendResult = await fetch(responseURL, { + method: 'PUT', + body: responseBody, + }); + console.log(`${sendResult.status}: ${sendResult.statusText}`); +}; + +/** + * Lambda handler that sends completion signals to wait condition handles. + * Called by Step Functions when long-running processes complete. + */ +export const handler: Handler = async (event) => { + console.log(event); + const { waitConditionHandle, status, uniqueId, data, reason } = event; + await send(waitConditionHandle, status, reason, uniqueId, data); +} \ No newline at end of file diff --git a/cdk-custom-resource-with-wait-condition/lib/sfn/long-running-process.asl.json b/cdk-custom-resource-with-wait-condition/lib/sfn/long-running-process.asl.json new file mode 100644 index 000000000..1462424b2 --- /dev/null +++ b/cdk-custom-resource-with-wait-condition/lib/sfn/long-running-process.asl.json @@ -0,0 +1,29 @@ +{ + "Comment": "This is a demo Step Function immitating a long running process for managing a custom resource.", + "StartAt": "Custom Resource Management Placeholder", + "States": { + "Custom Resource Management Placeholder": { + "Type": "Wait", + "Seconds": 10, + "Next": "Send Completion Signal", + "Comment": "This wait task simulates some business logic applying changes to the environment." + }, + "Send Completion Signal": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Output": "{% $states.result.Payload %}", + "Arguments": { + "FunctionName": "${SendCompletionSignalLambdaArn}", + "Payload": { + "waitConditionHandle": "{% $states.input.WaitConditionHandle %}", + "status": "SUCCESS", + "reason": "Configuration Complete", + "uniqueId": "ID1234", + "data": "Application has completed configuration." + } + }, + "End": true + } + }, + "QueryLanguage": "JSONata" +} \ No newline at end of file diff --git a/cdk-custom-resource-with-wait-condition/package.json b/cdk-custom-resource-with-wait-condition/package.json new file mode 100644 index 000000000..8a36081e6 --- /dev/null +++ b/cdk-custom-resource-with-wait-condition/package.json @@ -0,0 +1,26 @@ +{ + "name": "cdk-custom-resource-with-wait-condition", + "version": "0.1.0", + "bin": { + "cdk-custom-resource-with-wait-condition": "bin/cdk-custom-resource-with-wait-condition.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "cdk": "cdk" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.2", + "@types/node": "24.10.0", + "aws-cdk": "2.1031.1", + "esbuild": "^0.25.12", + "ts-node": "^10.9.2", + "typescript": "~5.9.3" + }, + "dependencies": { + "@aws-sdk/client-sfn": "^3.922.0", + "@types/aws-lambda": "^8.10.157", + "aws-cdk-lib": "2.222.0", + "constructs": "^10.4.2" + } +} diff --git a/cdk-custom-resource-with-wait-condition/tsconfig.json b/cdk-custom-resource-with-wait-condition/tsconfig.json new file mode 100644 index 000000000..4a95f7d3b --- /dev/null +++ b/cdk-custom-resource-with-wait-condition/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@tsconfig/node22", + "compilerOptions": { + "outDir": "./dist", + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "isolatedModules": true, + "rootDir": "." + }, + "exclude": ["cdk.out", "build", "node_modules", "dist", "**/__tests__/**", "**/*.test.ts", "**/*.spec.ts"] +} \ No newline at end of file