Repository: cloudcomponents/cdk-constructs (language: python)

Issue:
The Cloudcomponent cdk-construct does not support multiple pipelines(e.g. FE and BE) within the same stack deployment
I have raised a PR [here ](https://github.com/cloudcomponents/cdk-constructs/pull/118) to fix this problem and would like to get it reviewed/approved or an alternative provided.
At this point in time, we had to fork this project and fix it ourselves but going forward we would like to have this feature included to avoid maintaining yet another package.
Thanks



Discussion / hints:

Fix patch:
diff --git a/examples/blue-green-container-deployment-example/src/blue-green-container-deployment-stack.ts b/examples/blue-green-container-deployment-example/src/blue-green-container-deployment-stack.ts
index ec32b5b93..47be2d583 100644
--- a/examples/blue-green-container-deployment-example/src/blue-green-container-deployment-stack.ts
+++ b/examples/blue-green-container-deployment-example/src/blue-green-container-deployment-stack.ts
@@ -4,7 +4,7 @@ import { CodeBuildAction, CodeCommitSourceAction, CodeDeployEcsDeployAction } fr
 import { Vpc, Port } from '@aws-cdk/aws-ec2';
 import { Cluster } from '@aws-cdk/aws-ecs';
 import { ApplicationLoadBalancer, ApplicationTargetGroup, TargetType } from '@aws-cdk/aws-elasticloadbalancingv2';
-import { Construct, Stack, StackProps } from '@aws-cdk/core';
+import { Construct, Duration, Stack, StackProps } from '@aws-cdk/core';
 
 import { EcsService, DummyTaskDefinition, EcsDeploymentGroup, PushImageProject } from '@cloudcomponents/cdk-blue-green-container-deployment';
 import { ImageRepository } from '@cloudcomponents/cdk-container-registry';
@@ -67,6 +67,7 @@ export class BlueGreenContainerDeploymentStack extends Stack {
       desiredCount: 2,
       taskDefinition,
       prodTargetGroup,
+      testTargetGroup,
     });
 
     ecsService.connections.allowFrom(loadBalancer, Port.tcp(80));
@@ -76,10 +77,10 @@ export class BlueGreenContainerDeploymentStack extends Stack {
       applicationName: 'blue-green-application',
       deploymentGroupName: 'blue-green-deployment-group',
       ecsServices: [ecsService],
-      targetGroupNames: [prodTargetGroup.targetGroupName, testTargetGroup.targetGroupName],
+      targetGroups: [prodTargetGroup, testTargetGroup],
       prodTrafficListener: prodListener,
       testTrafficListener: testListener,
-      terminationWaitTimeInMinutes: 100,
+      terminationWaitTime: Duration.minutes(100),
     });
 
     // @see files: ./blue-green-repository for example content
diff --git a/packages/cdk-blue-green-container-deployment/API.md b/packages/cdk-blue-green-container-deployment/API.md
index 12aa7a197..054040491 100644
--- a/packages/cdk-blue-green-container-deployment/API.md
+++ b/packages/cdk-blue-green-container-deployment/API.md
@@ -177,12 +177,12 @@ new EcsDeploymentGroup(scope: Construct, id: string, props: EcsDeploymentGroupPr
   * **deploymentGroupName** (<code>string</code>)  *No description* 
   * **ecsServices** (<code>Array<[IEcsService](#cloudcomponents-cdk-blue-green-container-deployment-iecsservice)></code>)  *No description* 
   * **prodTrafficListener** (<code>[TrafficListener](#cloudcomponents-cdk-blue-green-container-deployment-trafficlistener)</code>)  *No description* 
-  * **targetGroupNames** (<code>Array<string></code>)  *No description* 
+  * **targetGroups** (<code>Array<[ApplicationTargetGroup](#aws-cdk-aws-elasticloadbalancingv2-applicationtargetgroup)></code>)  *No description* 
   * **testTrafficListener** (<code>[TrafficListener](#cloudcomponents-cdk-blue-green-container-deployment-trafficlistener)</code>)  *No description* 
   * **applicationName** (<code>string</code>)  *No description* __*Optional*__
   * **autoRollbackOnEvents** (<code>Array<[RollbackEvent](#cloudcomponents-cdk-blue-green-container-deployment-rollbackevent)></code>)  The event type or types that trigger a rollback. __*Optional*__
   * **deploymentConfig** (<code>[IEcsDeploymentConfig](#cloudcomponents-cdk-blue-green-container-deployment-iecsdeploymentconfig)</code>)  *No description* __*Optional*__
-  * **terminationWaitTimeInMinutes** (<code>number</code>)  the number of minutes before deleting the original (blue) task set. __*Default*__: 60
+  * **terminationWaitTime** (<code>[Duration](#aws-cdk-core-duration)</code>)  the number of minutes before deleting the original (blue) task set. __*Default*__: 60 minutes
 
 
 
@@ -221,6 +221,7 @@ new EcsService(scope: Construct, id: string, props: EcsServiceProps)
   * **prodTargetGroup** (<code>[ITargetGroup](#aws-cdk-aws-elasticloadbalancingv2-itargetgroup)</code>)  *No description* 
   * **serviceName** (<code>string</code>)  *No description* 
   * **taskDefinition** (<code>[DummyTaskDefinition](#cloudcomponents-cdk-blue-green-container-deployment-dummytaskdefinition)</code>)  *No description* 
+  * **testTargetGroup** (<code>[ITargetGroup](#aws-cdk-aws-elasticloadbalancingv2-itargetgroup)</code>)  *No description* 
   * **circuitBreaker** (<code>[DeploymentCircuitBreaker](#aws-cdk-aws-ecs-deploymentcircuitbreaker)</code>)  Whether to enable the deployment circuit breaker. __*Default*__: disabled
   * **containerPort** (<code>number</code>)  *No description* __*Optional*__
   * **desiredCount** (<code>number</code>)  *No description* __*Optional*__
@@ -317,12 +318,12 @@ Name | Type | Description
 **deploymentGroupName** | <code>string</code> | <span></span>
 **ecsServices** | <code>Array<[IEcsService](#cloudcomponents-cdk-blue-green-container-deployment-iecsservice)></code> | <span></span>
 **prodTrafficListener** | <code>[TrafficListener](#cloudcomponents-cdk-blue-green-container-deployment-trafficlistener)</code> | <span></span>
-**targetGroupNames** | <code>Array<string></code> | <span></span>
+**targetGroups** | <code>Array<[ApplicationTargetGroup](#aws-cdk-aws-elasticloadbalancingv2-applicationtargetgroup)></code> | <span></span>
 **testTrafficListener** | <code>[TrafficListener](#cloudcomponents-cdk-blue-green-container-deployment-trafficlistener)</code> | <span></span>
 **applicationName**? | <code>string</code> | __*Optional*__
 **autoRollbackOnEvents**? | <code>Array<[RollbackEvent](#cloudcomponents-cdk-blue-green-container-deployment-rollbackevent)></code> | The event type or types that trigger a rollback.<br/>__*Optional*__
 **deploymentConfig**? | <code>[IEcsDeploymentConfig](#cloudcomponents-cdk-blue-green-container-deployment-iecsdeploymentconfig)</code> | __*Optional*__
-**terminationWaitTimeInMinutes**? | <code>number</code> | the number of minutes before deleting the original (blue) task set.<br/>__*Default*__: 60
+**terminationWaitTime**? | <code>[Duration](#aws-cdk-core-duration)</code> | the number of minutes before deleting the original (blue) task set.<br/>__*Default*__: 60 minutes
 
 
 
@@ -339,6 +340,7 @@ Name | Type | Description
 **prodTargetGroup** | <code>[ITargetGroup](#aws-cdk-aws-elasticloadbalancingv2-itargetgroup)</code> | <span></span>
 **serviceName** | <code>string</code> | <span></span>
 **taskDefinition** | <code>[DummyTaskDefinition](#cloudcomponents-cdk-blue-green-container-deployment-dummytaskdefinition)</code> | <span></span>
+**testTargetGroup** | <code>[ITargetGroup](#aws-cdk-aws-elasticloadbalancingv2-itargetgroup)</code> | <span></span>
 **circuitBreaker**? | <code>[DeploymentCircuitBreaker](#aws-cdk-aws-ecs-deploymentcircuitbreaker)</code> | Whether to enable the deployment circuit breaker.<br/>__*Default*__: disabled
 **containerPort**? | <code>number</code> | __*Optional*__
 **desiredCount**? | <code>number</code> | __*Optional*__
diff --git a/packages/cdk-blue-green-container-deployment/lambda-file-sizes.json b/packages/cdk-blue-green-container-deployment/lambda-file-sizes.json
index 6e7d33e9b..5fc34da0a 100644
--- a/packages/cdk-blue-green-container-deployment/lambda-file-sizes.json
+++ b/packages/cdk-blue-green-container-deployment/lambda-file-sizes.json
@@ -1,1 +1,1 @@
-[{"timestamp":1631683651094,"files":[{"filename":"dummy-task-definition/index.js","previous":1728,"size":1728,"diff":0},{"filename":"ecs-deployment-group/index.js","previous":2182,"size":2182,"diff":0},{"filename":"ecs-service/index.js","previous":2152,"size":2138,"diff":-14}]},{"timestamp":1629825530770,"files":[{"filename":"dummy-task-definition/index.js","previous":1728,"size":1728,"diff":0},{"filename":"ecs-deployment-group/index.js","previous":2171,"size":2182,"diff":11},{"filename":"ecs-service/index.js","previous":2152,"size":2152,"diff":0}]},{"timestamp":1629823812165,"files":[{"filename":"dummy-task-definition/index.js","previous":1728,"size":1728,"diff":0},{"filename":"ecs-deployment-group/index.js","previous":2123,"size":2171,"diff":48},{"filename":"ecs-service/index.js","previous":2152,"size":2152,"diff":0}]},{"timestamp":1627652613714,"files":[{"filename":"dummy-task-definition/index.js","previous":1726,"size":1728,"diff":2},{"filename":"ecs-deployment-group/index.js","previous":2123,"size":2123,"diff":0},{"filename":"ecs-service/index.js","previous":2081,"size":2152,"diff":71}]},{"timestamp":1609276390870,"files":[{"filename":"dummy-task-definition/index.js","previous":1712,"size":1726,"diff":14},{"filename":"ecs-deployment-group/index.js","previous":2123,"size":2123,"diff":0},{"filename":"ecs-service/index.js","previous":2081,"size":2081,"diff":0}]},{"timestamp":1606329521054,"files":[{"filename":"dummy-task-definition/index.js","previous":1706,"size":1712,"diff":6},{"filename":"ecs-deployment-group/index.js","previous":2116,"size":2123,"diff":7},{"filename":"ecs-service/index.js","previous":2073,"size":2081,"diff":8}]},{"timestamp":1596457247342,"files":[{"filename":"dummy-task-definition/index.js","previous":1756,"size":1706,"diff":-50},{"filename":"ecs-deployment-group/index.js","previous":2116,"size":2116,"diff":0},{"filename":"ecs-service/index.js","previous":2073,"size":2073,"diff":0}]},{"timestamp":1596454924871,"files":[{"filename":"dummy-task-definition/index.js","previous":4964,"size":1756,"diff":-3208},{"filename":"ecs-deployment-group/index.js","previous":6103,"size":2116,"diff":-3987},{"filename":"ecs-service/index.js","previous":6141,"size":2073,"diff":-4068}]},{"timestamp":1596407637937,"files":[{"filename":"blue-green-service/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"blue-green-service/index.js","previous":3368,"size":0,"diff":-3368},{"filename":"dummy-task-definition/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"dummy-task-definition/index.js","previous":1963,"size":4964,"diff":3001},{"filename":"ecs-deployment-group/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"ecs-deployment-group/index.js","previous":2292,"size":6103,"diff":3811},{"filename":"ecs-service/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"ecs-service/index.js","previous":2312,"size":6141,"diff":3829}]}]
+[{"timestamp":1634467601943,"files":[{"filename":"ecs-deployment-group/index.js","previous":144792,"size":144825,"diff":33},{"filename":"ecs-service/index.js","previous":2133,"size":144797,"diff":142664}]},{"timestamp":1634411334117,"files":[{"filename":"dummy-task-definition/index.js","previous":1728,"size":0,"diff":-1728},{"filename":"ecs-deployment-group/index.js","previous":2182,"size":144792,"diff":142610},{"filename":"ecs-service/index.js","previous":2138,"size":2133,"diff":-5}]},{"timestamp":1631683651094,"files":[{"filename":"dummy-task-definition/index.js","previous":1728,"size":1728,"diff":0},{"filename":"ecs-deployment-group/index.js","previous":2182,"size":2182,"diff":0},{"filename":"ecs-service/index.js","previous":2152,"size":2138,"diff":-14}]},{"timestamp":1629825530770,"files":[{"filename":"dummy-task-definition/index.js","previous":1728,"size":1728,"diff":0},{"filename":"ecs-deployment-group/index.js","previous":2171,"size":2182,"diff":11},{"filename":"ecs-service/index.js","previous":2152,"size":2152,"diff":0}]},{"timestamp":1629823812165,"files":[{"filename":"dummy-task-definition/index.js","previous":1728,"size":1728,"diff":0},{"filename":"ecs-deployment-group/index.js","previous":2123,"size":2171,"diff":48},{"filename":"ecs-service/index.js","previous":2152,"size":2152,"diff":0}]},{"timestamp":1627652613714,"files":[{"filename":"dummy-task-definition/index.js","previous":1726,"size":1728,"diff":2},{"filename":"ecs-deployment-group/index.js","previous":2123,"size":2123,"diff":0},{"filename":"ecs-service/index.js","previous":2081,"size":2152,"diff":71}]},{"timestamp":1609276390870,"files":[{"filename":"dummy-task-definition/index.js","previous":1712,"size":1726,"diff":14},{"filename":"ecs-deployment-group/index.js","previous":2123,"size":2123,"diff":0},{"filename":"ecs-service/index.js","previous":2081,"size":2081,"diff":0}]},{"timestamp":1606329521054,"files":[{"filename":"dummy-task-definition/index.js","previous":1706,"size":1712,"diff":6},{"filename":"ecs-deployment-group/index.js","previous":2116,"size":2123,"diff":7},{"filename":"ecs-service/index.js","previous":2073,"size":2081,"diff":8}]},{"timestamp":1596457247342,"files":[{"filename":"dummy-task-definition/index.js","previous":1756,"size":1706,"diff":-50},{"filename":"ecs-deployment-group/index.js","previous":2116,"size":2116,"diff":0},{"filename":"ecs-service/index.js","previous":2073,"size":2073,"diff":0}]},{"timestamp":1596454924871,"files":[{"filename":"dummy-task-definition/index.js","previous":4964,"size":1756,"diff":-3208},{"filename":"ecs-deployment-group/index.js","previous":6103,"size":2116,"diff":-3987},{"filename":"ecs-service/index.js","previous":6141,"size":2073,"diff":-4068}]},{"timestamp":1596407637937,"files":[{"filename":"blue-green-service/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"blue-green-service/index.js","previous":3368,"size":0,"diff":-3368},{"filename":"dummy-task-definition/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"dummy-task-definition/index.js","previous":1963,"size":4964,"diff":3001},{"filename":"ecs-deployment-group/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"ecs-deployment-group/index.js","previous":2292,"size":6103,"diff":3811},{"filename":"ecs-service/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"ecs-service/index.js","previous":2312,"size":6141,"diff":3829}]}]
diff --git a/packages/cdk-blue-green-container-deployment/package.json b/packages/cdk-blue-green-container-deployment/package.json
index 52702c0cf..ef3a838cf 100644
--- a/packages/cdk-blue-green-container-deployment/package.json
+++ b/packages/cdk-blue-green-container-deployment/package.json
@@ -63,27 +63,32 @@
   "peerDependencies": {
     "@aws-cdk/aws-codebuild": "^1.127.0",
     "@aws-cdk/aws-codedeploy": "^1.127.0",
+    "@aws-cdk/custom-resources": "^1.127.0",
     "@aws-cdk/aws-ec2": "^1.127.0",
     "@aws-cdk/aws-ecr": "^1.127.0",
     "@aws-cdk/aws-ecs": "^1.127.0",
     "@aws-cdk/aws-elasticloadbalancingv2": "^1.127.0",
     "@aws-cdk/aws-iam": "^1.127.0",
+    "@aws-cdk/aws-lambda": "^1.127.0",
     "@aws-cdk/core": "^1.127.0",
     "constructs": "^3.2.0"
   },
   "dependencies": {
     "@aws-cdk/aws-codebuild": "^1.127.0",
     "@aws-cdk/aws-codedeploy": "^1.127.0",
+    "@aws-cdk/custom-resources": "^1.127.0",
     "@aws-cdk/aws-ec2": "^1.127.0",
     "@aws-cdk/aws-ecr": "^1.127.0",
     "@aws-cdk/aws-ecs": "^1.127.0",
     "@aws-cdk/aws-elasticloadbalancingv2": "^1.127.0",
     "@aws-cdk/aws-iam": "^1.127.0",
+    "@aws-cdk/aws-lambda": "^1.127.0",
     "@aws-cdk/core": "^1.127.0"
   },
   "devDependencies": {
     "@aws-cdk/assert": "^1.127.0",
     "aws-sdk": "^2.1004.0",
+    "custom-resource-helper": "^1.0.15",
     "jest-cdk-snapshot": "^1.4.2"
   },
   "externals": [
diff --git a/packages/cdk-blue-green-container-deployment/src/dummy-task-definition.ts b/packages/cdk-blue-green-container-deployment/src/dummy-task-definition.ts
index ede7cb0d5..f5397a639 100644
--- a/packages/cdk-blue-green-container-deployment/src/dummy-task-definition.ts
+++ b/packages/cdk-blue-green-container-deployment/src/dummy-task-definition.ts
@@ -1,8 +1,7 @@
-import * as path from 'path';
-
 import { NetworkMode } from '@aws-cdk/aws-ecs';
 import { Role, ServicePrincipal, ManagedPolicy, PolicyStatement, Effect, IRole } from '@aws-cdk/aws-iam';
-import { Construct, CustomResource, CustomResourceProvider, CustomResourceProviderRuntime } from '@aws-cdk/core';
+import { Construct } from '@aws-cdk/core';
+import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId, PhysicalResourceIdReference } from '@aws-cdk/custom-resources';
 
 export interface IDummyTaskDefinition {
   readonly executionRole: IRole;
@@ -60,41 +59,60 @@ export class DummyTaskDefinition extends Construct implements IDummyTaskDefiniti
       managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy')],
     });
 
-    const serviceToken = CustomResourceProvider.getOrCreate(this, 'Custom::DummyTaskDefinition', {
-      codeDirectory: path.join(__dirname, 'lambdas', 'dummy-task-definition'),
-      runtime: CustomResourceProviderRuntime.NODEJS_12_X,
-      policyStatements: [
-        {
-          Effect: Effect.ALLOW,
-          Action: ['ecs:RegisterTaskDefinition', 'ecs:DeregisterTaskDefinition'],
-          Resource: '*',
-        },
-        {
-          Effect: Effect.ALLOW,
-          Action: ['iam:PassRole'],
-          Resource: this.executionRole.roleArn,
-        },
-      ],
-    });
-
     this.family = props.family ?? this.node.addr;
     this.containerName = props.containerName ?? 'sample-website';
     this.containerPort = props.containerPort ?? 80;
 
-    const taskDefinition = new CustomResource(this, 'CustomResource', {
-      serviceToken,
+    const taskDefinition = new AwsCustomResource(this, 'DummyTaskDefinition', {
       resourceType: 'Custom::DummyTaskDefinition',
-      properties: {
-        Family: this.family,
-        Image: props.image,
-        ExecutionRoleArn: this.executionRole.roleArn,
-        NetworkMode: NetworkMode.AWS_VPC,
-        ContainerName: this.containerName,
-        ContainerPort: this.containerPort,
+      onCreate: {
+        service: 'ECS',
+        action: 'registerTaskDefinition',
+        parameters: {
+          requiresCompatibilities: ['FARGATE'],
+          family: this.family,
+          executionRoleArn: this.executionRole.roleArn,
+          networkMode: NetworkMode.AWS_VPC,
+          cpu: '256',
+          memory: '512',
+          containerDefinitions: [
+            {
+              name: this.containerName,
+              image: props.image,
+              portMappings: [
+                {
+                  hostPort: this.containerPort,
+                  protocol: 'tcp',
+                  containerPort: this.containerPort,
+                },
+              ],
+            },
+          ],
+        },
+        physicalResourceId: PhysicalResourceId.fromResponse('taskDefinition.taskDefinitionArn'),
+      },
+      onDelete: {
+        service: 'ECS',
+        action: 'deregisterTaskDefinition',
+        parameters: {
+          taskDefinition: new PhysicalResourceIdReference(),
+        },
       },
+      policy: AwsCustomResourcePolicy.fromStatements([
+        new PolicyStatement({
+          effect: Effect.ALLOW,
+          actions: ['ecs:RegisterTaskDefinition', 'ecs:DeregisterTaskDefinition'],
+          resources: ['*'],
+        }),
+        new PolicyStatement({
+          effect: Effect.ALLOW,
+          actions: ['iam:PassRole'],
+          resources: [this.executionRole.roleArn],
+        }),
+      ]),
     });
 
-    this.taskDefinitionArn = taskDefinition.ref;
+    this.taskDefinitionArn = taskDefinition.getResponseField('taskDefinition.taskDefinitionArn');
   }
 
   /**
diff --git a/packages/cdk-blue-green-container-deployment/src/ecs-deployment-group.ts b/packages/cdk-blue-green-container-deployment/src/ecs-deployment-group.ts
index cf1b5af9d..6a4591088 100644
--- a/packages/cdk-blue-green-container-deployment/src/ecs-deployment-group.ts
+++ b/packages/cdk-blue-green-container-deployment/src/ecs-deployment-group.ts
@@ -1,7 +1,9 @@
 import * as path from 'path';
 import { EcsApplication, IEcsApplication } from '@aws-cdk/aws-codedeploy';
-import { Role, ServicePrincipal, ManagedPolicy, Effect } from '@aws-cdk/aws-iam';
-import { Aws, Construct, Resource, IResource, CustomResource, CustomResourceProvider, CustomResourceProviderRuntime } from '@aws-cdk/core';
+import { ApplicationTargetGroup } from '@aws-cdk/aws-elasticloadbalancingv2';
+import { Role, ServicePrincipal, ManagedPolicy, Effect, PolicyStatement } from '@aws-cdk/aws-iam';
+import { Function, Runtime, Code } from '@aws-cdk/aws-lambda';
+import { Aws, Construct, Resource, IResource, CustomResource, Duration } from '@aws-cdk/core';
 
 import { EcsDeploymentConfig, IEcsDeploymentConfig } from './ecs-deployment-config';
 import { IEcsService } from './ecs-service';
@@ -48,7 +50,7 @@ export interface EcsDeploymentGroupProps {
 
   readonly ecsServices: IEcsService[];
 
-  readonly targetGroupNames: string[];
+  readonly targetGroups: ApplicationTargetGroup[];
 
   readonly prodTrafficListener: TrafficListener;
 
@@ -61,9 +63,9 @@ export interface EcsDeploymentGroupProps {
    *
    * The maximum setting is 2880 minutes (2 days).
    *
-   * @default 60
+   * @default 60 minutes
    */
-  readonly terminationWaitTimeInMinutes?: number;
+  readonly terminationWaitTime?: Duration;
 
   /**
    * The event type or types that trigger a rollback.
@@ -85,18 +87,18 @@ export class EcsDeploymentGroup extends Resource implements IEcsDeploymentGroup
       deploymentGroupName,
       deploymentConfig,
       ecsServices,
-      targetGroupNames,
+      targetGroups,
       prodTrafficListener,
       testTrafficListener,
-      terminationWaitTimeInMinutes = 60,
+      terminationWaitTime = Duration.minutes(60),
       autoRollbackOnEvents,
     } = props;
 
-    if (terminationWaitTimeInMinutes > 2880) {
+    if (terminationWaitTime.toMinutes() > 2880) {
       throw new Error('Invalid TerminationWaitTimeInMinutes: The maximum setting is 2880 minutes (2 days).');
     }
 
-    const codeDeployEcsRole = new Role(this, 'EcsCodeDeployRole', {
+    const codeDeployEcsRole = new Role(this, 'Role', {
       assumedBy: new ServicePrincipal('codedeploy.amazonaws.com'),
       managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName('AWSCodeDeployRoleForECS')],
     });
@@ -105,44 +107,51 @@ export class EcsDeploymentGroup extends Resource implements IEcsDeploymentGroup
       applicationName,
     });
 
-    const serviceToken = CustomResourceProvider.getOrCreate(this, 'Custom::EcsDeploymentGroup', {
-      codeDirectory: path.join(__dirname, 'lambdas', 'ecs-deployment-group'),
-      runtime: CustomResourceProviderRuntime.NODEJS_12_X,
-      policyStatements: [
-        {
-          Effect: Effect.ALLOW,
-          Action: ['codeDeploy:CreateDeploymentGroup', 'codeDeploy:UpdateDeploymentGroup', 'codeDeploy:DeleteDeploymentGroup'],
-          Resource: '*',
-        },
-        {
-          Effect: Effect.ALLOW,
-          Action: ['iam:PassRole'],
-          Resource: codeDeployEcsRole.roleArn,
-        },
-      ],
+    const serviceToken = new Function(this, 'Function', {
+      runtime: Runtime.NODEJS_12_X,
+      code: Code.fromAsset(path.join(__dirname, 'lambdas', 'ecs-deployment-group')),
+      handler: 'index.handler',
+      timeout: Duration.minutes(15),
     });
 
+    serviceToken.addToRolePolicy(
+      new PolicyStatement({
+        effect: Effect.ALLOW,
+        actions: ['codeDeploy:CreateDeploymentGroup', 'codeDeploy:UpdateDeploymentGroup', 'codeDeploy:DeleteDeploymentGroup'],
+        resources: ['*'],
+      }),
+    );
+
+    serviceToken.addToRolePolicy(
+      new PolicyStatement({
+        effect: Effect.ALLOW,
+        actions: ['iam:PassRole'],
+        resources: [codeDeployEcsRole.roleArn],
+      }),
+    );
+
     this.deploymentConfig = deploymentConfig || EcsDeploymentConfig.ALL_AT_ONCE;
 
     if (Construct.isConstruct(props.deploymentConfig)) {
       this.node.addDependency(props.deploymentConfig);
     }
+    this.node.addDependency(...ecsServices);
 
     const ecsDeploymentGroup = new CustomResource(this, 'CustomResource', {
-      serviceToken,
+      serviceToken: serviceToken.functionArn,
       resourceType: 'Custom::EcsDeploymentGroup',
       properties: {
         ApplicationName: this.application.applicationName,
         DeploymentGroupName: deploymentGroupName,
         ServiceRoleArn: codeDeployEcsRole.roleArn,
-        TargetGroupNames: targetGroupNames,
+        TargetGroupNames: targetGroups.map((tg) => tg.targetGroupName),
         EcsServices: ecsServices.map((service) => ({
           ClusterName: service.clusterName,
           ServiceName: service.serviceName,
         })),
         ProdTrafficListenerArn: prodTrafficListener.listenerArn,
         TestTrafficListenerArn: testTrafficListener.listenerArn,
-        TerminationWaitTimeInMinutes: terminationWaitTimeInMinutes,
+        TerminationWaitTimeInMinutes: terminationWaitTime.toMinutes(),
         AutoRollbackOnEvents: autoRollbackOnEvents,
         DeploymentConfigName: this.deploymentConfig.deploymentConfigName,
       },
diff --git a/packages/cdk-blue-green-container-deployment/src/ecs-service.ts b/packages/cdk-blue-green-container-deployment/src/ecs-service.ts
index b8955e437..cf5b4ff18 100644
--- a/packages/cdk-blue-green-container-deployment/src/ecs-service.ts
+++ b/packages/cdk-blue-green-container-deployment/src/ecs-service.ts
@@ -2,8 +2,9 @@ import * as path from 'path';
 import { IConnectable, Connections, SecurityGroup, Port } from '@aws-cdk/aws-ec2';
 import { ICluster, LaunchType, DeploymentCircuitBreaker } from '@aws-cdk/aws-ecs';
 import { ITargetGroup } from '@aws-cdk/aws-elasticloadbalancingv2';
-import { Effect } from '@aws-cdk/aws-iam';
-import { Duration, Construct, CustomResource, CustomResourceProvider, CustomResourceProviderRuntime } from '@aws-cdk/core';
+import { Effect, PolicyStatement } from '@aws-cdk/aws-iam';
+import { Function, Runtime, Code } from '@aws-cdk/aws-lambda';
+import { Duration, Construct, CustomResource } from '@aws-cdk/core';
 
 import { DummyTaskDefinition } from './dummy-task-definition';
 
@@ -21,6 +22,7 @@ export interface EcsServiceProps {
   readonly desiredCount?: number;
   readonly containerPort?: number;
   readonly prodTargetGroup: ITargetGroup;
+  readonly testTargetGroup: ITargetGroup;
   readonly taskDefinition: DummyTaskDefinition;
 
   /**
@@ -72,6 +74,7 @@ export class EcsService extends Construct implements IConnectable, IEcsService {
       platformVersion = '1.4.0',
       desiredCount = 1,
       prodTargetGroup,
+      testTargetGroup,
       taskDefinition,
       healthCheckGracePeriod = Duration.seconds(60),
     } = props;
@@ -80,6 +83,8 @@ export class EcsService extends Construct implements IConnectable, IEcsService {
 
     const { vpc } = cluster;
 
+    this.node.addDependency(prodTargetGroup, testTargetGroup);
+
     const securityGroups = props.securityGroups || [
       new SecurityGroup(this, 'SecurityGroup', {
         description: `Security group for ${this.node.id} service`,
@@ -87,25 +92,31 @@ export class EcsService extends Construct implements IConnectable, IEcsService {
       }),
     ];
 
-    const serviceToken = CustomResourceProvider.getOrCreate(this, 'Custom::BlueGreenService', {
-      codeDirectory: path.join(__dirname, 'lambdas', 'ecs-service'),
-      runtime: CustomResourceProviderRuntime.NODEJS_12_X,
-      policyStatements: [
-        {
-          Effect: Effect.ALLOW,
-          Action: ['ecs:CreateService', 'ecs:UpdateService', 'ecs:DeleteService', 'ecs:DescribeServices'],
-          Resource: '*',
-        },
-        {
-          Effect: Effect.ALLOW,
-          Action: ['iam:PassRole'],
-          Resource: taskDefinition.executionRole.roleArn,
-        },
-      ],
+    const serviceToken = new Function(this, 'Function', {
+      runtime: Runtime.NODEJS_12_X,
+      code: Code.fromAsset(path.join(__dirname, 'lambdas', 'ecs-service')),
+      handler: 'index.handler',
+      timeout: Duration.minutes(15),
     });
 
+    serviceToken.addToRolePolicy(
+      new PolicyStatement({
+        effect: Effect.ALLOW,
+        actions: ['ecs:CreateService', 'ecs:UpdateService', 'ecs:DeleteService', 'ecs:DescribeServices'],
+        resources: ['*'],
+      }),
+    );
+
+    serviceToken.addToRolePolicy(
+      new PolicyStatement({
+        effect: Effect.ALLOW,
+        actions: ['iam:PassRole'],
+        resources: [taskDefinition.executionRole.roleArn],
+      }),
+    );
+
     const service = new CustomResource(this, 'CustomResource', {
-      serviceToken,
+      serviceToken: serviceToken.functionArn,
       resourceType: 'Custom::BlueGreenService',
       properties: {
         Cluster: cluster.clusterName,
diff --git a/packages/cdk-blue-green-container-deployment/src/lambdas/dummy-task-definition/index.ts b/packages/cdk-blue-green-container-deployment/src/lambdas/dummy-task-definition/index.ts
deleted file mode 100644
index b134cbd09..000000000
--- a/packages/cdk-blue-green-container-deployment/src/lambdas/dummy-task-definition/index.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import type { CloudFormationCustomResourceEvent, CloudFormationCustomResourceCreateEvent, CloudFormationCustomResourceDeleteEvent } from 'aws-lambda';
-import { ECS } from 'aws-sdk';
-
-interface HandlerReturn {
-  PhysicalResourceId: string;
-}
-
-export interface EcsTaskDefinitionProps {
-  family: string;
-  image: string;
-  executionRoleArn: string;
-  networkMode: string;
-  containerName: string;
-  containerPort: number;
-}
-
-const ecs = new ECS();
-
-const getProperties = (props: CloudFormationCustomResourceEvent['ResourceProperties']): EcsTaskDefinitionProps => ({
-  family: props.Family,
-  image: props.Image,
-  executionRoleArn: props.ExecutionRoleArn,
-  networkMode: props.NetworkMode,
-  containerName: props.ContainerName,
-  containerPort: props.ContainerPort,
-});
-
-const onCreate = async (event: CloudFormationCustomResourceCreateEvent): Promise<HandlerReturn> => {
-  const { family, image, executionRoleArn, networkMode, containerName, containerPort } = getProperties(event.ResourceProperties);
-
-  const { taskDefinition } = await ecs
-    .registerTaskDefinition({
-      requiresCompatibilities: ['FARGATE'],
-      family,
-      executionRoleArn,
-      networkMode,
-      cpu: '256',
-      memory: '512',
-      containerDefinitions: [
-        {
-          name: containerName,
-          image,
-          portMappings: [
-            {
-              hostPort: containerPort,
-              protocol: 'tcp',
-              containerPort: containerPort,
-            },
-          ],
-        },
-      ],
-    })
-    .promise();
-
-  if (!taskDefinition) throw Error('Taskdefinition could not be registerd');
-
-  return {
-    PhysicalResourceId: taskDefinition.taskDefinitionArn as string,
-  };
-};
-
-const onDelete = async (event: CloudFormationCustomResourceDeleteEvent): Promise<void> => {
-  const taskDefinition = event.PhysicalResourceId;
-
-  await ecs
-    .deregisterTaskDefinition({
-      taskDefinition,
-    })
-    .promise();
-};
-
-export const handler = async (event: CloudFormationCustomResourceEvent): Promise<HandlerReturn | void> => {
-  const requestType = event.RequestType;
-
-  switch (requestType) {
-    case 'Create':
-      return onCreate(event);
-    case 'Update':
-      // CodeDeploy is responsible for updates on the TaskDefinition
-      return;
-    case 'Delete':
-      return onDelete(event);
-    default:
-      throw new Error(`Invalid request type: ${requestType}`);
-  }
-};
diff --git a/packages/cdk-blue-green-container-deployment/src/lambdas/ecs-deployment-group/index.ts b/packages/cdk-blue-green-container-deployment/src/lambdas/ecs-deployment-group/index.ts
index 8db3d49a2..fe47f7bf9 100644
--- a/packages/cdk-blue-green-container-deployment/src/lambdas/ecs-deployment-group/index.ts
+++ b/packages/cdk-blue-green-container-deployment/src/lambdas/ecs-deployment-group/index.ts
@@ -1,10 +1,13 @@
-import type {
-  CloudFormationCustomResourceEvent,
-  CloudFormationCustomResourceCreateEvent,
-  CloudFormationCustomResourceUpdateEvent,
-  CloudFormationCustomResourceDeleteEvent,
-} from 'aws-lambda';
+import type { CloudFormationCustomResourceEvent, CloudFormationCustomResourceUpdateEvent } from 'aws-lambda';
 import { CodeDeploy } from 'aws-sdk';
+import {
+  customResourceHelper,
+  OnCreateHandler,
+  OnUpdateHandler,
+  OnDeleteHandler,
+  ResourceHandler,
+  ResourceHandlerReturn,
+} from 'custom-resource-helper';
 
 enum RollbackEvent {
   DEPLOYMENT_FAILURE = 'DEPLOYMENT_FAILURE',
@@ -12,10 +15,6 @@ enum RollbackEvent {
   DEPLOYMENT_STOP_ON_REQUEST = 'DEPLOYMENT_STOP_ON_REQUEST',
 }
 
-interface HandlerReturn {
-  PhysicalResourceId: string;
-}
-
 export interface EcsDeploymentGroupProps {
   applicationName: string;
   deploymentGroupName: string;
@@ -49,7 +48,7 @@ const getProperties = (
   deploymentConfigName: props.DeploymentConfigName,
 });
 
-const onCreate = async (event: CloudFormationCustomResourceCreateEvent): Promise<HandlerReturn> => {
+const handleCreate: OnCreateHandler = async (event): Promise<ResourceHandlerReturn> => {
   const {
     applicationName,
     deploymentGroupName,
@@ -106,11 +105,11 @@ const onCreate = async (event: CloudFormationCustomResourceCreateEvent): Promise
     .promise();
 
   return {
-    PhysicalResourceId: deploymentGroupName,
+    physicalResourceId: deploymentGroupName,
   };
 };
 
-const onUpdate = async (event: CloudFormationCustomResourceUpdateEvent): Promise<HandlerReturn> => {
+const handleUpdate: OnUpdateHandler = async (event): Promise<ResourceHandlerReturn> => {
   const newProps = getProperties(event.ResourceProperties);
   const oldProps = getProperties(event.OldResourceProperties);
 
@@ -153,11 +152,11 @@ const onUpdate = async (event: CloudFormationCustomResourceUpdateEvent): Promise
     .promise();
 
   return {
-    PhysicalResourceId: newProps.deploymentGroupName,
+    physicalResourceId: newProps.deploymentGroupName,
   };
 };
 
-const onDelete = async (event: CloudFormationCustomResourceDeleteEvent): Promise<void> => {
+const handleDelete: OnDeleteHandler = async (event): Promise<void> => {
   const { applicationName, deploymentGroupName } = getProperties(event.ResourceProperties);
 
   await codeDeploy
@@ -168,17 +167,10 @@ const onDelete = async (event: CloudFormationCustomResourceDeleteEvent): Promise
     .promise();
 };
 
-export const handler = async (event: CloudFormationCustomResourceEvent): Promise<HandlerReturn | void> => {
-  const requestType = event.RequestType;
-
-  switch (requestType) {
-    case 'Create':
-      return onCreate(event);
-    case 'Update':
-      return onUpdate(event);
-    case 'Delete':
-      return onDelete(event);
-    default:
-      throw new Error(`Invalid request type: ${requestType}`);
-  }
-};
+export const handler = customResourceHelper(
+  (): ResourceHandler => ({
+    onCreate: handleCreate,
+    onUpdate: handleUpdate,
+    onDelete: handleDelete,
+  }),
+);
diff --git a/packages/cdk-blue-green-container-deployment/src/lambdas/ecs-service/index.ts b/packages/cdk-blue-green-container-deployment/src/lambdas/ecs-service/index.ts
index 2d34ed836..3f0e6f8e2 100644
--- a/packages/cdk-blue-green-container-deployment/src/lambdas/ecs-service/index.ts
+++ b/packages/cdk-blue-green-container-deployment/src/lambdas/ecs-service/index.ts
@@ -1,17 +1,13 @@
-import type {
-  CloudFormationCustomResourceEvent,
-  CloudFormationCustomResourceCreateEvent,
-  CloudFormationCustomResourceUpdateEvent,
-  CloudFormationCustomResourceDeleteEvent,
-} from 'aws-lambda';
+import type { CloudFormationCustomResourceEvent } from 'aws-lambda';
 import { ECS } from 'aws-sdk';
-
-interface HandlerReturn {
-  PhysicalResourceId: string;
-  Data: {
-    ServiceName: string;
-  };
-}
+import {
+  customResourceHelper,
+  OnCreateHandler,
+  OnUpdateHandler,
+  OnDeleteHandler,
+  ResourceHandler,
+  ResourceHandlerReturn,
+} from 'custom-resource-helper';
 
 export interface BlueGreenServiceProps {
   cluster: string;
@@ -49,7 +45,7 @@ const getProperties = (props: CloudFormationCustomResourceEvent['ResourcePropert
   deploymentConfiguration: props.DeploymentConfiguration,
 });
 
-const onCreate = async (event: CloudFormationCustomResourceCreateEvent): Promise<HandlerReturn> => {
+const handleCreate: OnCreateHandler = async (event): Promise<ResourceHandlerReturn> => {
   const {
     cluster,
     serviceName,
@@ -100,8 +96,8 @@ const onCreate = async (event: CloudFormationCustomResourceCreateEvent): Promise
   if (!service) throw Error('Service could not be created');
 
   return {
-    PhysicalResourceId: service.serviceArn as string,
-    Data: {
+    physicalResourceId: service.serviceArn as string,
+    responseData: {
       ServiceName: service.serviceName as string,
     },
   };
@@ -115,7 +111,7 @@ const onCreate = async (event: CloudFormationCustomResourceCreateEvent): Promise
  * updated, a new AWS CodeDeploy deployment should be created.
  * For more information, see CreateDeployment in the AWS CodeDeploy API Reference.
  */
-const onUpdate = async (event: CloudFormationCustomResourceUpdateEvent): Promise<HandlerReturn> => {
+const handleUpdate: OnUpdateHandler = async (event): Promise<ResourceHandlerReturn> => {
   const { cluster, serviceName, desiredCount, deploymentConfiguration, healthCheckGracePeriodSeconds } = getProperties(event.ResourceProperties);
 
   const { service } = await ecs
@@ -131,14 +127,14 @@ const onUpdate = async (event: CloudFormationCustomResourceUpdateEvent): Promise
   if (!service) throw Error('Service could not be updated');
 
   return {
-    PhysicalResourceId: service.serviceArn as string,
-    Data: {
+    physicalResourceId: service.serviceArn as string,
+    responseData: {
       ServiceName: service.serviceName as string,
     },
   };
 };
 
-const onDelete = async (event: CloudFormationCustomResourceDeleteEvent): Promise<void> => {
+const handleDelete: OnDeleteHandler = async (event): Promise<void> => {
   const { cluster, serviceName } = getProperties(event.ResourceProperties);
 
   await ecs
@@ -157,17 +153,10 @@ const onDelete = async (event: CloudFormationCustomResourceDeleteEvent): Promise
     .promise();
 };
 
-export const handler = async (event: CloudFormationCustomResourceEvent): Promise<HandlerReturn | void> => {
-  const requestType = event.RequestType;
-
-  switch (requestType) {
-    case 'Create':
-      return onCreate(event);
-    case 'Update':
-      return onUpdate(event);
-    case 'Delete':
-      return onDelete(event);
-    default:
-      throw new Error(`Invalid request type: ${requestType}`);
-  }
-};
+export const handler = customResourceHelper(
+  (): ResourceHandler => ({
+    onCreate: handleCreate,
+    onUpdate: handleUpdate,
+    onDelete: handleDelete,
+  }),
+);
diff --git a/packages/cdk-s3-antivirus/src/layers/clamav/Dockerfile b/packages/cdk-s3-antivirus/src/layers/clamav/Dockerfile
index 8db588e9d..8cdeaa218 100644
--- a/packages/cdk-s3-antivirus/src/layers/clamav/Dockerfile
+++ b/packages/cdk-s3-antivirus/src/layers/clamav/Dockerfile
@@ -1,7 +1,7 @@
 FROM public.ecr.aws/lambda/provided:al2.2021.09.13.11
 
 ARG asset_name=layer
-ARG CLAMAV_VERSION=0.103.3-5.el7
+ARG CLAMAV_VERSION=0.103.3-8.el7
 
 USER root
 RUN mkdir -p /opt/{lib,clamav}
@@ -10,8 +10,7 @@ RUN mkdir -p /opt/{lib,clamav}
 # tools
 #
 RUN yum install -y zip yum-utils amazon-linux-extras
-RUN amazon-linux-extras install epel -y
-RUN yum-config-manager --enable epel
+RUN amazon-linux-extras install epel -y && yum-config-manager --enable epel
 
 #
 # layer


Test patch:
diff --git a/packages/cdk-blue-green-container-deployment/src/__tests__/__snapshots__/dummy-task-definition.test.ts.snap b/packages/cdk-blue-green-container-deployment/src/__tests__/__snapshots__/dummy-task-definition.test.ts.snap
new file mode 100644
index 000000000..f74c7188f
--- /dev/null
+++ b/packages/cdk-blue-green-container-deployment/src/__tests__/__snapshots__/dummy-task-definition.test.ts.snap
@@ -0,0 +1,156 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`default setup 1`] = `
+Object {
+  "Parameters": Any<Object>,
+  "Resources": Object {
+    "AWS679f53fac002430cb0da5b7982bd22872D164C4C": Object {
+      "DependsOn": Array [
+        "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2",
+      ],
+      "Properties": Object {
+        "Code": Any<Object>,
+        "Handler": "index.handler",
+        "Role": Object {
+          "Fn::GetAtt": Array [
+            "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2",
+            "Arn",
+          ],
+        },
+        "Runtime": "nodejs12.x",
+        "Timeout": 120,
+      },
+      "Type": "AWS::Lambda::Function",
+    },
+    "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2": Object {
+      "Properties": Object {
+        "AssumeRolePolicyDocument": Object {
+          "Statement": Array [
+            Object {
+              "Action": "sts:AssumeRole",
+              "Effect": "Allow",
+              "Principal": Object {
+                "Service": "lambda.amazonaws.com",
+              },
+            },
+          ],
+          "Version": "2012-10-17",
+        },
+        "ManagedPolicyArns": Array [
+          Object {
+            "Fn::Join": Array [
+              "",
+              Array [
+                "arn:",
+                Object {
+                  "Ref": "AWS::Partition",
+                },
+                ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
+              ],
+            ],
+          },
+        ],
+      },
+      "Type": "AWS::IAM::Role",
+    },
+    "DummyTaskDefinitionCustomResourcePolicyB5660701": Object {
+      "Properties": Object {
+        "PolicyDocument": Object {
+          "Statement": Array [
+            Object {
+              "Action": Array [
+                "ecs:RegisterTaskDefinition",
+                "ecs:DeregisterTaskDefinition",
+              ],
+              "Effect": "Allow",
+              "Resource": "*",
+            },
+            Object {
+              "Action": "iam:PassRole",
+              "Effect": "Allow",
+              "Resource": Object {
+                "Fn::GetAtt": Array [
+                  "DummyTaskDefinitionExecutionRole715DBD43",
+                  "Arn",
+                ],
+              },
+            },
+          ],
+          "Version": "2012-10-17",
+        },
+        "PolicyName": "DummyTaskDefinitionCustomResourcePolicyB5660701",
+        "Roles": Array [
+          Object {
+            "Ref": "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2",
+          },
+        ],
+      },
+      "Type": "AWS::IAM::Policy",
+    },
+    "DummyTaskDefinitionE3D9D432": Object {
+      "DeletionPolicy": "Delete",
+      "DependsOn": Array [
+        "DummyTaskDefinitionCustomResourcePolicyB5660701",
+      ],
+      "Properties": Object {
+        "Create": Object {
+          "Fn::Join": Array [
+            "",
+            Array [
+              "{\\"service\\":\\"ECS\\",\\"action\\":\\"registerTaskDefinition\\",\\"parameters\\":{\\"requiresCompatibilities\\":[\\"FARGATE\\"],\\"family\\":\\"c86e1e5d419d3c124c7e40411b7e7805f91621e162\\",\\"executionRoleArn\\":\\"",
+              Object {
+                "Fn::GetAtt": Array [
+                  "DummyTaskDefinitionExecutionRole715DBD43",
+                  "Arn",
+                ],
+              },
+              "\\",\\"networkMode\\":\\"awsvpc\\",\\"cpu\\":\\"256\\",\\"memory\\":\\"512\\",\\"containerDefinitions\\":[{\\"name\\":\\"sample-website\\",\\"image\\":\\"image\\",\\"portMappings\\":[{\\"hostPort\\":80,\\"protocol\\":\\"tcp\\",\\"containerPort\\":80}]}]},\\"physicalResourceId\\":{\\"responsePath\\":\\"taskDefinition.taskDefinitionArn\\"}}",
+            ],
+          ],
+        },
+        "Delete": "{\\"service\\":\\"ECS\\",\\"action\\":\\"deregisterTaskDefinition\\",\\"parameters\\":{\\"taskDefinition\\":\\"PHYSICAL:RESOURCEID:\\"}}",
+        "InstallLatestAwsSdk": true,
+        "ServiceToken": Object {
+          "Fn::GetAtt": Array [
+            "AWS679f53fac002430cb0da5b7982bd22872D164C4C",
+            "Arn",
+          ],
+        },
+      },
+      "Type": "Custom::DummyTaskDefinition",
+      "UpdateReplacePolicy": "Delete",
+    },
+    "DummyTaskDefinitionExecutionRole715DBD43": Object {
+      "Properties": Object {
+        "AssumeRolePolicyDocument": Object {
+          "Statement": Array [
+            Object {
+              "Action": "sts:AssumeRole",
+              "Effect": "Allow",
+              "Principal": Object {
+                "Service": "ecs-tasks.amazonaws.com",
+              },
+            },
+          ],
+          "Version": "2012-10-17",
+        },
+        "ManagedPolicyArns": Array [
+          Object {
+            "Fn::Join": Array [
+              "",
+              Array [
+                "arn:",
+                Object {
+                  "Ref": "AWS::Partition",
+                },
+                ":iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
+              ],
+            ],
+          },
+        ],
+      },
+      "Type": "AWS::IAM::Role",
+    },
+  },
+}
+`;
diff --git a/packages/cdk-blue-green-container-deployment/src/__tests__/dummy-task-definition.test.ts b/packages/cdk-blue-green-container-deployment/src/__tests__/dummy-task-definition.test.ts
new file mode 100644
index 000000000..29b97c7ae
--- /dev/null
+++ b/packages/cdk-blue-green-container-deployment/src/__tests__/dummy-task-definition.test.ts
@@ -0,0 +1,16 @@
+import { Stack } from '@aws-cdk/core';
+import 'jest-cdk-snapshot';
+
+import { DummyTaskDefinition } from '../dummy-task-definition';
+
+test('default setup', (): void => {
+  const stack = new Stack();
+
+  new DummyTaskDefinition(stack, 'DummyTaskDefinition', {
+    image: 'image',
+  });
+
+  expect(stack).toMatchCdkSnapshot({
+    ignoreAssets: true,
+  });
+});