Skip to content

Commit 0b5395a

Browse files
authored
Merge pull request #2853 from DmitryGulin/main
[New pattern] Using CDK to create custom resource with wait condition
2 parents 9805519 + 1020bef commit 0b5395a

File tree

14 files changed

+653
-0
lines changed

14 files changed

+653
-0
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
*.js
2+
!jest.config.js
3+
*.d.ts
4+
node_modules
5+
6+
# CDK asset staging directory
7+
.cdk.staging
8+
cdk.out
9+
10+
dist/
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.ts
2+
!*.d.ts
3+
4+
# CDK asset staging directory
5+
.cdk.staging
6+
cdk.out
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Use AWS CloudFormation Wait Conditions for long-running custom resources
2+
3+
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).
4+
5+
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.
6+
7+
![Architecture Diagram](./image/architecture.png)
8+
9+
## How it works
10+
11+
The architecture uses four components:
12+
13+
1. **Custom Resource Handler Lambda** - Receives CloudFormation lifecycle events and starts a Step Function execution, then returns success immediately to prevent timeouts
14+
2. **Step Function** - Orchestrates the long-running process with built-in retry mechanisms and error handling
15+
3. **Completion Signal Handler Lambda** - Sends success/failure signals to the Wait Condition Handle when the process completes
16+
4. **Wait Condition** - Blocks CloudFormation stack completion until receiving the completion signal
17+
18+
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.
19+
20+
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.
21+
22+
## Requirements
23+
24+
* [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.
25+
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
26+
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
27+
* [Node and NPM](https://nodejs.org/en/download/) installed
28+
* [AWS Cloud Development Kit](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) (AWS CDK) installed
29+
30+
## Deploy
31+
32+
1. Clone the project to your local working directory
33+
34+
```sh
35+
git clone https://github.com/aws-samples/serverless-patterns
36+
```
37+
38+
1. Change the working directory to this pattern's directory
39+
40+
```sh
41+
cd cdk-custom-resource-with-wait-condition
42+
```
43+
44+
1. Install the project dependencies
45+
46+
```sh
47+
npm install
48+
```
49+
50+
1. Deploy the stack to your default AWS account and region
51+
52+
```sh
53+
cdk deploy --require-approval never
54+
```
55+
56+
## Test
57+
58+
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.
59+
60+
## Cleanup
61+
62+
Run the given command to delete the resources that were created. It might take some time for the CloudFormation stack to get deleted.
63+
64+
```sh
65+
cdk destroy -f
66+
```
67+
68+
## Useful commands
69+
70+
* `npm run build` compile typescript to js
71+
* `npm run watch` watch for changes and compile
72+
* `npm run test` perform the jest unit tests
73+
* `npx cdk deploy` deploy this stack to your default AWS account/region
74+
* `npx cdk diff` compare deployed stack with current state
75+
* `npx cdk synth` emits the synthesized CloudFormation template
76+
77+
## References
78+
79+
1. [Custom resources](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html)
80+
2. [Using wait conditions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-waitcondition.html)
81+
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/)
82+
83+
----
84+
Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
85+
86+
SPDX-License-Identifier: MIT-0
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env node
2+
import { App } from 'aws-cdk-lib';
3+
import { DemoStack } from '../lib/demo-stack';
4+
5+
const app = new App();
6+
7+
// Deploy demo stack showing custom resource with wait condition pattern
8+
new DemoStack(app, 'CdkCustomResourceWithWaitConditionStack', {
9+
stackName: 'Custom-Resource-With-Wait-Condition-Demo',
10+
description: 'Demo of a custom resource with a wait condition',
11+
env: {
12+
region: process.env.CDK_DEFAULT_REGION,
13+
account: process.env.CDK_DEFAULT_ACCOUNT,
14+
},
15+
});
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
{
2+
"title": "Custom resource with wait condition",
3+
"description": "Use AWS CloudFormation Wait Conditions for long-running custom resources up to 12 hours",
4+
"language": "TypeScript",
5+
"level": "200",
6+
"framework": "AWS CDK",
7+
"introBox": {
8+
"headline": "How it works",
9+
"text": [
10+
"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.",
11+
"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.",
12+
"Use cases include database migrations, complex infrastructure provisioning, and third-party system integrations that exceed Lambda's 15-minute limit."
13+
]
14+
},
15+
"gitHub": {
16+
"template": {
17+
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/cdk-custom-resource-with-wait-condition",
18+
"templateURL": "serverless-patterns/cdk-custom-resource-with-wait-condition",
19+
"projectFolder": "cdk-custom-resource-with-wait-condition",
20+
"templateFile": "lib/demo-stack.ts"
21+
}
22+
},
23+
"resources": {
24+
"bullets": [
25+
{
26+
"text": "Custom resources",
27+
"link": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html"
28+
},
29+
{
30+
"text": "Using wait conditions",
31+
"link": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-waitcondition.html"
32+
},
33+
{
34+
"text": "Implementing long running deployments with AWS CloudFormation Custom Resources using AWS Step Functions",
35+
"link": "https://aws.amazon.com/blogs/devops/implementing-long-running-deployments-with-aws-cloudformation-custom-resources-using-aws-step-functions/"
36+
}
37+
]
38+
},
39+
"deploy": {
40+
"text": [
41+
"cdk deploy --require-approval never"
42+
]
43+
},
44+
"testing": {
45+
"text": [
46+
"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."
47+
]
48+
},
49+
"cleanup": {
50+
"text": [
51+
"Delete the stack: <code>cdk destroy -f</code>."
52+
]
53+
},
54+
"authors": [
55+
{
56+
"name": "Dmitry Gulin",
57+
"bio": "Senior Delivery Consultant, AWS.",
58+
"linkedin": "dmitry-gulin"
59+
}
60+
],
61+
"patternArch": {
62+
"icon1": {
63+
"x": 20,
64+
"y": 50,
65+
"service": "cloudformation",
66+
"label": "AWS CloudFormation"
67+
},
68+
"icon2": {
69+
"x": 50,
70+
"y": 50,
71+
"service": "lambda",
72+
"label": "Custom resource"
73+
},
74+
"icon3": {
75+
"x": 80,
76+
"y": 50,
77+
"service": "sfn",
78+
"label": "AWS Step Functions workflow"
79+
},
80+
"line1": {
81+
"from": "icon1",
82+
"to": "icon2",
83+
"label": ""
84+
},
85+
"line2": {
86+
"from": "icon2",
87+
"to": "icon3",
88+
"label": ""
89+
}
90+
}
91+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
{
2+
"app": "npx ts-node --prefer-ts-exts bin/app.ts",
3+
"watch": {
4+
"include": [
5+
"**"
6+
],
7+
"exclude": [
8+
"README.md",
9+
"cdk*.json",
10+
"**/*.d.ts",
11+
"**/*.js",
12+
"tsconfig.json",
13+
"package*.json",
14+
"yarn.lock",
15+
"node_modules",
16+
"test"
17+
]
18+
},
19+
"context": {
20+
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
21+
"@aws-cdk/core:checkSecretUsage": true,
22+
"@aws-cdk/core:target-partitions": [
23+
"aws",
24+
"aws-cn"
25+
],
26+
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
27+
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
28+
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
29+
"@aws-cdk/aws-iam:minimizePolicies": true,
30+
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
31+
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
32+
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
33+
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
34+
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
35+
"@aws-cdk/core:enablePartitionLiterals": true,
36+
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
37+
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
38+
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
39+
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
40+
"@aws-cdk/aws-route53-patters:useCertificate": true,
41+
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
42+
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
43+
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
44+
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
45+
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
46+
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
47+
"@aws-cdk/aws-redshift:columnId": true,
48+
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
49+
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
50+
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
51+
"@aws-cdk/aws-kms:aliasNameRef": true,
52+
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
53+
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
54+
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
55+
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
56+
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
57+
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
58+
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
59+
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
60+
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
61+
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
62+
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
63+
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
64+
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
65+
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
66+
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
67+
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
68+
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
69+
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
70+
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
71+
"@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false,
72+
"@aws-cdk/aws-ecs:disableEcsImdsBlocking": true,
73+
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
74+
"@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true,
75+
"@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true,
76+
"@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
77+
"@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
78+
"@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
79+
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true,
80+
"@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true,
81+
"@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true,
82+
"@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true,
83+
"@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true,
84+
"@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true
85+
}
86+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{
2+
"title": "Custom resource with wait condition",
3+
"description": "Use AWS CloudFormation Wait Conditions for long-running custom resources up to 12 hours",
4+
"language": "TypeScript",
5+
"level": "200",
6+
"framework": "AWS CDK",
7+
"introBox": {
8+
"headline": "How it works",
9+
"text": [
10+
"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.",
11+
"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.",
12+
"Use cases include database migrations, complex infrastructure provisioning, and third-party system integrations that exceed Lambda's 15-minute limit."
13+
]
14+
},
15+
"gitHub": {
16+
"template": {
17+
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/cdk-custom-resource-with-wait-condition",
18+
"templateURL": "serverless-patterns/cdk-custom-resource-with-wait-condition",
19+
"projectFolder": "cdk-custom-resource-with-wait-condition",
20+
"templateFile": "lib/demo-stack.ts"
21+
}
22+
},
23+
"resources": {
24+
"bullets": [
25+
{
26+
"text": "Custom resources",
27+
"link": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html"
28+
},
29+
{
30+
"text": "Using wait conditions",
31+
"link": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-waitcondition.html"
32+
},
33+
{
34+
"text": "Implementing long running deployments with AWS CloudFormation Custom Resources using AWS Step Functions",
35+
"link": "https://aws.amazon.com/blogs/devops/implementing-long-running-deployments-with-aws-cloudformation-custom-resources-using-aws-step-functions/"
36+
}
37+
]
38+
},
39+
"deploy": {
40+
"text": [
41+
"cdk deploy --require-approval never"
42+
]
43+
},
44+
"testing": {
45+
"text": [
46+
"Review Amazon CloudWatch logs for the Lambda functions and Step Function execution to confirm that the",
47+
"long-running process completed successfully and the wait condition was signaled."
48+
]
49+
},
50+
"cleanup": {
51+
"text": [
52+
"Delete the stack: <code>cdk destroy -f</code>."
53+
]
54+
},
55+
"authors": [
56+
{
57+
"name": "Dmitry Gulin",
58+
"bio": "Senior Delivery Consultant, AWS.",
59+
"linkedin": "dmitry-gulin"
60+
}
61+
]
62+
}
184 KB
Loading

0 commit comments

Comments
 (0)