From cc277c7809d604e11a37878c38cd01409001b41c Mon Sep 17 00:00:00 2001 From: Kishan Gajjar Date: Thu, 24 Jul 2025 10:33:21 -0400 Subject: [PATCH 1/8] feat(ecs): enable ECS native B/G in L2 constructs Co-authored-by: Sherlyn Saavedra --- .../aws-ecs-blue-green-deployment.assets.json | 6 +- ...ws-ecs-blue-green-deployment.template.json | 276 +++++++-------- .../integ.json | 2 +- .../manifest.json | 233 +++++++------ .../tree.json | 2 +- .../integ.blue-green-deployment-strategy.ts | 87 +---- packages/aws-cdk-lib/aws-ecs/README.md | 26 ++ .../lib/alternate-target-configuration.ts | 165 +++++++++ .../aws-ecs/lib/base/base-service.ts | 140 +++++++- .../lib/deployment-lifecycle-hook-target.ts | 117 +++++++ .../aws-ecs/lib/external/external-service.ts | 3 +- packages/aws-cdk-lib/aws-ecs/lib/index.ts | 3 + .../alternate-target-configuration.test.ts | 265 +++++++++++++++ .../aws-ecs/test/base-service.test.ts | 216 +++++++++++- .../deployment-lifecycle-hook-target.test.ts | 316 ++++++++++++++++++ 15 files changed, 1494 insertions(+), 363 deletions(-) create mode 100644 packages/aws-cdk-lib/aws-ecs/lib/alternate-target-configuration.ts create mode 100644 packages/aws-cdk-lib/aws-ecs/lib/deployment-lifecycle-hook-target.ts create mode 100644 packages/aws-cdk-lib/aws-ecs/test/alternate-target-configuration.test.ts create mode 100644 packages/aws-cdk-lib/aws-ecs/test/deployment-lifecycle-hook-target.test.ts diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/aws-ecs-blue-green-deployment.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/aws-ecs-blue-green-deployment.assets.json index a74f0b69cb02c..a014d4b1734fc 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/aws-ecs-blue-green-deployment.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/aws-ecs-blue-green-deployment.assets.json @@ -1,16 +1,16 @@ { "version": "45.0.0", "files": { - "fd615fb1e7e79dc8dbd0171fa9da178930b6f83121b4aab3b516529352a074f3": { + "be28df9e09f080f9ea9aa5e16d969648154dd356a673e352c382094ea3f269a2": { "displayName": "aws-ecs-blue-green-deployment Template", "source": { "path": "aws-ecs-blue-green-deployment.template.json", "packaging": "file" }, "destinations": { - "current_account-current_region-40aafb80": { + "current_account-current_region-ffebb95d": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "fd615fb1e7e79dc8dbd0171fa9da178930b6f83121b4aab3b516529352a074f3.json", + "objectKey": "be28df9e09f080f9ea9aa5e16d969648154dd356a673e352c382094ea3f269a2.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/aws-ecs-blue-green-deployment.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/aws-ecs-blue-green-deployment.template.json index 76e870cbef2ed..d69d625bd2ee2 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/aws-ecs-blue-green-deployment.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/aws-ecs-blue-green-deployment.template.json @@ -614,143 +614,6 @@ "Priority": 1 } }, - "EcsTaskExecutionRoleC0B6A84D": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ecs-tasks.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" - ] - ] - } - ] - } - }, - "ServiceRole4288B192": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ecs.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AmazonEC2ContainerServiceRole" - ] - ] - } - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": "*" - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "LambdaInvokePolicy" - }, - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "elasticloadbalancing:ModifyListener", - "elasticloadbalancing:ModifyRule" - ], - "Effect": "Allow", - "Resource": { - "Ref": "ALBProductionListenerRule243D0687" - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "ELBPolicy" - } - ] - } - }, - "ServiceRoleDefaultPolicy94CF55F6": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "LambdaHookBF1BC8B4", - "Arn" - ] - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "LambdaHookBF1BC8B4", - "Arn" - ] - }, - ":*" - ] - ] - } - ] - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "ServiceRoleDefaultPolicy94CF55F6", - "Roles": [ - { - "Ref": "ServiceRole4288B192" - } - ] - } - }, "LambdaHookServiceRole9AAAD33B": { "Type": "AWS::IAM::Role", "Properties": { @@ -856,12 +719,6 @@ } ], "Cpu": "256", - "ExecutionRoleArn": { - "Fn::GetAtt": [ - "EcsTaskExecutionRoleC0B6A84D", - "Arn" - ] - }, "Family": "awsecsbluegreendeploymentTaskDef51D80572", "Memory": "512", "NetworkMode": "awsvpc", @@ -883,11 +740,6 @@ "Ref": "FargateCluster7CCD5F93" }, "DeploymentConfiguration": { - "BakeTimeInMinutes": 0, - "DeploymentCircuitBreaker": { - "Enable": false, - "Rollback": false - }, "LifecycleHooks": [ { "HookTargetArn": { @@ -896,24 +748,21 @@ "Arn" ] }, + "LifecycleStages": [ + "PRE_SCALE_UP" + ], "RoleArn": { "Fn::GetAtt": [ - "ServiceRole4288B192", + "ServiceHook0RoleACE68A7A", "Arn" ] - }, - "LifecycleStages": [ - "POST_TEST_TRAFFIC_SHIFT" - ] + } } ], "MaximumPercent": 200, - "MinimumHealthyPercent": 100, + "MinimumHealthyPercent": 50, "Strategy": "BLUE_GREEN" }, - "DeploymentController": { - "Type": "ECS" - }, "EnableECSManagedTags": false, "HealthCheckGracePeriodSeconds": 60, "LaunchType": "FARGATE", @@ -923,14 +772,14 @@ "AlternateTargetGroupArn": { "Ref": "GreenTG71A27F2F" }, + "ProductionListenerRule": { + "Ref": "ALBProductionListenerRule243D0687" + }, "RoleArn": { "Fn::GetAtt": [ - "ServiceRole4288B192", + "ServiceLBAlternateOptionsRole06C91D94", "Arn" ] - }, - "ProductionListenerRule": { - "Ref": "ALBProductionListenerRule243D0687" } }, "ContainerName": "nginx", @@ -969,6 +818,111 @@ "ALBProductionListenerRule243D0687", "TaskDefTaskRole1EDB4A67" ] + }, + "ServiceLBAlternateOptionsRole06C91D94": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonECSInfrastructureRolePolicyForLoadBalancers" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonEC2ContainerServiceRole" + ] + ] + } + ] + }, + "DependsOn": [ + "TaskDefTaskRole1EDB4A67" + ] + }, + "ServiceHook0RoleACE68A7A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "ServiceHook0RoleDefaultPolicyD7C31604": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "LambdaHookBF1BC8B4", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "LambdaHookBF1BC8B4", + "Arn" + ] + }, + ":*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ServiceHook0RoleDefaultPolicyD7C31604", + "Roles": [ + { + "Ref": "ServiceHook0RoleACE68A7A" + } + ] + } } }, "Parameters": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/integ.json index 94b6400882816..0ea15481aba57 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/integ.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/integ.json @@ -9,5 +9,5 @@ "assertionStackName": "awsecsbluegreenDefaultTestDeployAssertA9C6C1DC" } }, - "minimumCliVersion": "2.1020.1" + "minimumCliVersion": "2.1020.2" } \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/manifest.json index edc0bbc792e06..d8289d9121bea 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/fd615fb1e7e79dc8dbd0171fa9da178930b6f83121b4aab3b516529352a074f3.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/be28df9e09f080f9ea9aa5e16d969648154dd356a673e352c382094ea3f269a2.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -498,123 +498,6 @@ "data": "ALBProductionListenerRule243D0687" } ], - "/aws-ecs-blue-green-deployment/EcsTaskExecutionRole": [ - { - "type": "aws:cdk:analytics:construct", - "data": { - "assumedBy": { - "principalAccount": "*", - "assumeRoleAction": "*" - }, - "managedPolicies": [ - { - "managedPolicyArn": "*" - } - ] - } - } - ], - "/aws-ecs-blue-green-deployment/EcsTaskExecutionRole/ImportEcsTaskExecutionRole": [ - { - "type": "aws:cdk:analytics:construct", - "data": "*" - } - ], - "/aws-ecs-blue-green-deployment/EcsTaskExecutionRole/Resource": [ - { - "type": "aws:cdk:logicalId", - "data": "EcsTaskExecutionRoleC0B6A84D" - } - ], - "/aws-ecs-blue-green-deployment/ServiceRole": [ - { - "type": "aws:cdk:analytics:construct", - "data": { - "assumedBy": { - "principalAccount": "*", - "assumeRoleAction": "*" - }, - "managedPolicies": [ - { - "managedPolicyArn": "*" - } - ], - "inlinePolicies": "*" - } - }, - { - "type": "aws:cdk:analytics:method", - "data": { - "addToPrincipalPolicy": [ - {} - ] - } - }, - { - "type": "aws:cdk:analytics:method", - "data": { - "attachInlinePolicy": [ - "*" - ] - } - }, - { - "type": "aws:cdk:analytics:method", - "data": { - "attachInlinePolicy": [ - "*" - ] - } - } - ], - "/aws-ecs-blue-green-deployment/ServiceRole/ImportServiceRole": [ - { - "type": "aws:cdk:analytics:construct", - "data": "*" - } - ], - "/aws-ecs-blue-green-deployment/ServiceRole/Resource": [ - { - "type": "aws:cdk:logicalId", - "data": "ServiceRole4288B192" - } - ], - "/aws-ecs-blue-green-deployment/ServiceRole/DefaultPolicy": [ - { - "type": "aws:cdk:analytics:construct", - "data": "*" - }, - { - "type": "aws:cdk:analytics:method", - "data": { - "attachToRole": [ - "*" - ] - } - }, - { - "type": "aws:cdk:analytics:method", - "data": { - "attachToRole": [ - "*" - ] - } - }, - { - "type": "aws:cdk:analytics:method", - "data": { - "addStatements": [ - {} - ] - } - } - ], - "/aws-ecs-blue-green-deployment/ServiceRole/DefaultPolicy/Resource": [ - { - "type": "aws:cdk:logicalId", - "data": "ServiceRoleDefaultPolicy94CF55F6" - } - ], "/aws-ecs-blue-green-deployment/LambdaHook": [ { "type": "aws:cdk:analytics:construct", @@ -736,6 +619,120 @@ "data": "ServiceD69D759B" } ], + "/aws-ecs-blue-green-deployment/Service/LBAlternateOptionsRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + }, + "managedPolicies": [ + { + "managedPolicyArn": "*" + }, + { + "managedPolicyArn": "*" + } + ] + } + } + ], + "/aws-ecs-blue-green-deployment/Service/LBAlternateOptionsRole/ImportLBAlternateOptionsRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-ecs-blue-green-deployment/Service/LBAlternateOptionsRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceLBAlternateOptionsRole06C91D94" + } + ], + "/aws-ecs-blue-green-deployment/Service/Hook0Role": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + } + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addToPrincipalPolicy": [ + {} + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachInlinePolicy": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachInlinePolicy": [ + "*" + ] + } + } + ], + "/aws-ecs-blue-green-deployment/Service/Hook0Role/ImportHook0Role": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-ecs-blue-green-deployment/Service/Hook0Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceHook0RoleACE68A7A" + } + ], + "/aws-ecs-blue-green-deployment/Service/Hook0Role/DefaultPolicy": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachToRole": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachToRole": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addStatements": [ + {} + ] + } + } + ], + "/aws-ecs-blue-green-deployment/Service/Hook0Role/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceHook0RoleDefaultPolicyD7C31604" + } + ], "/aws-ecs-blue-green-deployment/BootstrapVersion": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/tree.json index 707ef48bf14cc..cfb6c890f7554 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/tree.json @@ -1 +1 @@ -{"version":"tree-0.1","tree":{"id":"App","path":"","constructInfo":{"fqn":"aws-cdk-lib.App","version":"0.0.0"},"children":{"aws-ecs-blue-green-deployment":{"id":"aws-ecs-blue-green-deployment","path":"aws-ecs-blue-green-deployment","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"Vpc":{"id":"Vpc","path":"aws-ecs-blue-green-deployment/Vpc","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.Vpc","version":"0.0.0","metadata":[{"maxAzs":"*","restrictDefaultSecurityGroup":false}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/Vpc/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnVPC","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::VPC","aws:cdk:cloudformation:props":{"cidrBlock":"10.0.0.0/16","enableDnsHostnames":true,"enableDnsSupport":true,"instanceTenancy":"default","tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc"}]}}},"PublicSubnet1":{"id":"PublicSubnet1","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PublicSubnet","version":"0.0.0","metadata":[{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":true,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":true,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{},{"addNatGateway":["*"]}]},"children":{"Subnet":{"id":"Subnet","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[0,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.0.0/18","mapPublicIpOnLaunch":true,"tags":[{"key":"aws-cdk:subnet-name","value":"Public"},{"key":"aws-cdk:subnet-type","value":"Public"},{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"Acl":{"id":"Acl","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":[]}},"RouteTable":{"id":"RouteTable","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VpcPublicSubnet1RouteTable6C95E38E"},"subnetId":{"Ref":"VpcPublicSubnet1Subnet5C2D37C4"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","gatewayId":{"Ref":"VpcIGWD7BA715C"},"routeTableId":{"Ref":"VpcPublicSubnet1RouteTable6C95E38E"}}}},"EIP":{"id":"EIP","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/EIP","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnEIP","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::EIP","aws:cdk:cloudformation:props":{"domain":"vpc","tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1"}]}}},"NATGateway":{"id":"NATGateway","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/NATGateway","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnNatGateway","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::NatGateway","aws:cdk:cloudformation:props":{"allocationId":{"Fn::GetAtt":["VpcPublicSubnet1EIPD7E02669","AllocationId"]},"subnetId":{"Ref":"VpcPublicSubnet1Subnet5C2D37C4"},"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1"}]}}}}},"PublicSubnet2":{"id":"PublicSubnet2","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PublicSubnet","version":"0.0.0","metadata":[{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":true,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":true,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{},{"addNatGateway":["*"]}]},"children":{"Subnet":{"id":"Subnet","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[1,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.64.0/18","mapPublicIpOnLaunch":true,"tags":[{"key":"aws-cdk:subnet-name","value":"Public"},{"key":"aws-cdk:subnet-type","value":"Public"},{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"Acl":{"id":"Acl","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":[]}},"RouteTable":{"id":"RouteTable","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VpcPublicSubnet2RouteTable94F7E489"},"subnetId":{"Ref":"VpcPublicSubnet2Subnet691E08A3"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","gatewayId":{"Ref":"VpcIGWD7BA715C"},"routeTableId":{"Ref":"VpcPublicSubnet2RouteTable94F7E489"}}}},"EIP":{"id":"EIP","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/EIP","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnEIP","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::EIP","aws:cdk:cloudformation:props":{"domain":"vpc","tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2"}]}}},"NATGateway":{"id":"NATGateway","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/NATGateway","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnNatGateway","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::NatGateway","aws:cdk:cloudformation:props":{"allocationId":{"Fn::GetAtt":["VpcPublicSubnet2EIP3C605A87","AllocationId"]},"subnetId":{"Ref":"VpcPublicSubnet2Subnet691E08A3"},"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2"}]}}}}},"PrivateSubnet1":{"id":"PrivateSubnet1","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PrivateSubnet","version":"0.0.0","metadata":[{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":false,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":false,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{}]},"children":{"Subnet":{"id":"Subnet","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[0,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.128.0/18","mapPublicIpOnLaunch":false,"tags":[{"key":"aws-cdk:subnet-name","value":"Private"},{"key":"aws-cdk:subnet-type","value":"Private"},{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"Acl":{"id":"Acl","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":[]}},"RouteTable":{"id":"RouteTable","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VpcPrivateSubnet1RouteTableB2C5B500"},"subnetId":{"Ref":"VpcPrivateSubnet1Subnet536B997A"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","natGatewayId":{"Ref":"VpcPublicSubnet1NATGateway4D7517AA"},"routeTableId":{"Ref":"VpcPrivateSubnet1RouteTableB2C5B500"}}}}}},"PrivateSubnet2":{"id":"PrivateSubnet2","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PrivateSubnet","version":"0.0.0","metadata":[{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":false,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":false,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{}]},"children":{"Subnet":{"id":"Subnet","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[1,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.192.0/18","mapPublicIpOnLaunch":false,"tags":[{"key":"aws-cdk:subnet-name","value":"Private"},{"key":"aws-cdk:subnet-type","value":"Private"},{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"Acl":{"id":"Acl","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":[]}},"RouteTable":{"id":"RouteTable","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VpcPrivateSubnet2RouteTableA678073B"},"subnetId":{"Ref":"VpcPrivateSubnet2Subnet3788AAA1"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","natGatewayId":{"Ref":"VpcPublicSubnet2NATGateway9182C01D"},"routeTableId":{"Ref":"VpcPrivateSubnet2RouteTableA678073B"}}}}}},"IGW":{"id":"IGW","path":"aws-ecs-blue-green-deployment/Vpc/IGW","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnInternetGateway","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::InternetGateway","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc"}]}}},"VPCGW":{"id":"VPCGW","path":"aws-ecs-blue-green-deployment/Vpc/VPCGW","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnVPCGatewayAttachment","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::VPCGatewayAttachment","aws:cdk:cloudformation:props":{"internetGatewayId":{"Ref":"VpcIGWD7BA715C"},"vpcId":{"Ref":"Vpc8378EB38"}}}}}},"FargateCluster":{"id":"FargateCluster","path":"aws-ecs-blue-green-deployment/FargateCluster","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.Cluster","version":"0.0.0","metadata":[{"vpc":"*","defaultCloudMapNamespace":{"name":"*","useForServiceConnect":true}},{"addDefaultCloudMapNamespace":[{"name":"*","useForServiceConnect":true}]}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/FargateCluster/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.CfnCluster","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ECS::Cluster","aws:cdk:cloudformation:props":{"serviceConnectDefaults":{"namespace":{"Fn::GetAtt":["FargateClusterDefaultServiceDiscoveryNamespace04381E1E","Arn"]}}}}},"DefaultServiceDiscoveryNamespace":{"id":"DefaultServiceDiscoveryNamespace","path":"aws-ecs-blue-green-deployment/FargateCluster/DefaultServiceDiscoveryNamespace","constructInfo":{"fqn":"aws-cdk-lib.aws_servicediscovery.PrivateDnsNamespace","version":"0.0.0","metadata":[{"name":"*","vpc":"*"}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/FargateCluster/DefaultServiceDiscoveryNamespace/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_servicediscovery.CfnPrivateDnsNamespace","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ServiceDiscovery::PrivateDnsNamespace","aws:cdk:cloudformation:props":{"name":"bluegreendeployment.com","vpc":{"Ref":"Vpc8378EB38"}}}}}}}},"BlueTG":{"id":"BlueTG","path":"aws-ecs-blue-green-deployment/BlueTG","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationTargetGroup","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/BlueTG/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnTargetGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::TargetGroup","aws:cdk:cloudformation:props":{"healthCheckPath":"/","matcher":{"httpCode":"200"},"port":80,"protocol":"HTTP","targetGroupAttributes":[{"key":"stickiness.enabled","value":"false"}],"targetType":"ip","vpcId":{"Ref":"Vpc8378EB38"}}}}}},"GreenTG":{"id":"GreenTG","path":"aws-ecs-blue-green-deployment/GreenTG","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationTargetGroup","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/GreenTG/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnTargetGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::TargetGroup","aws:cdk:cloudformation:props":{"healthCheckPath":"/","matcher":{"httpCode":"200"},"port":80,"protocol":"HTTP","targetGroupAttributes":[{"key":"stickiness.enabled","value":"false"}],"targetType":"ip","vpcId":{"Ref":"Vpc8378EB38"}}}}}},"LBSecurityGroup":{"id":"LBSecurityGroup","path":"aws-ecs-blue-green-deployment/LBSecurityGroup","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.SecurityGroup","version":"0.0.0","metadata":[{"vpc":"*","allowAllOutbound":true},{"addIngressRule":[{"canInlineRule":true,"connections":"*","uniqueId":"*"},{}]},{"addIngressRule":[{"canInlineRule":true,"connections":"*","uniqueId":"*"},{},"*",false]},{"addEgressRule":["*",{},"*",true]}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/LBSecurityGroup/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSecurityGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SecurityGroup","aws:cdk:cloudformation:props":{"groupDescription":"aws-ecs-blue-green-deployment/LBSecurityGroup","securityGroupEgress":[{"cidrIp":"0.0.0.0/0","description":"Allow all outbound traffic by default","ipProtocol":"-1"}],"securityGroupIngress":[{"cidrIp":"0.0.0.0/0","ipProtocol":"tcp","fromPort":80,"toPort":80,"description":"from 0.0.0.0/0:80"}],"vpcId":{"Ref":"Vpc8378EB38"}}}}}},"ECSSecurityGroup":{"id":"ECSSecurityGroup","path":"aws-ecs-blue-green-deployment/ECSSecurityGroup","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.SecurityGroup","version":"0.0.0","metadata":[{"vpc":"*","allowAllOutbound":true},{"addIngressRule":["*",{}]},{"addIngressRule":["*",{},"*",false]}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/ECSSecurityGroup/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSecurityGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SecurityGroup","aws:cdk:cloudformation:props":{"groupDescription":"aws-ecs-blue-green-deployment/ECSSecurityGroup","securityGroupEgress":[{"cidrIp":"0.0.0.0/0","description":"Allow all outbound traffic by default","ipProtocol":"-1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"from awsecsbluegreendeploymentLBSecurityGroup67E4A783:80":{"id":"from awsecsbluegreendeploymentLBSecurityGroup67E4A783:80","path":"aws-ecs-blue-green-deployment/ECSSecurityGroup/from awsecsbluegreendeploymentLBSecurityGroup67E4A783:80","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSecurityGroupIngress","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SecurityGroupIngress","aws:cdk:cloudformation:props":{"description":"from awsecsbluegreendeploymentLBSecurityGroup67E4A783:80","fromPort":80,"groupId":{"Fn::GetAtt":["ECSSecurityGroupA14DBE7D","GroupId"]},"ipProtocol":"tcp","sourceSecurityGroupId":{"Fn::GetAtt":["LBSecurityGroup4464B654","GroupId"]},"toPort":80}}}}},"ALB":{"id":"ALB","path":"aws-ecs-blue-green-deployment/ALB","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationLoadBalancer","version":"0.0.0","metadata":["*","*"]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/ALB/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnLoadBalancer","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::LoadBalancer","aws:cdk:cloudformation:props":{"loadBalancerAttributes":[{"key":"deletion_protection.enabled","value":"false"},{"key":"idle_timeout.timeout_seconds","value":"60"}],"scheme":"internet-facing","securityGroups":[{"Fn::GetAtt":["LBSecurityGroup4464B654","GroupId"]}],"subnets":[{"Ref":"VpcPublicSubnet1Subnet5C2D37C4"},{"Ref":"VpcPublicSubnet2Subnet691E08A3"}],"type":"application"}}},"ALBListenerHTTP":{"id":"ALBListenerHTTP","path":"aws-ecs-blue-green-deployment/ALB/ALBListenerHTTP","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationListener","version":"0.0.0","metadata":["*","*"]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/ALB/ALBListenerHTTP/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnListener","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::Listener","aws:cdk:cloudformation:props":{"defaultActions":[{"type":"fixed-response","fixedResponseConfig":{"statusCode":"404"}}],"loadBalancerArn":{"Ref":"ALBAEE750D2"},"port":80,"protocol":"HTTP"}}}}}}},"ALBProductionListenerRule":{"id":"ALBProductionListenerRule","path":"aws-ecs-blue-green-deployment/ALBProductionListenerRule","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationListenerRule","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/ALBProductionListenerRule/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnListenerRule","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::ListenerRule","aws:cdk:cloudformation:props":{"actions":[{"type":"forward","forwardConfig":{"targetGroups":[{"targetGroupArn":{"Ref":"BlueTG7C058B2C"},"weight":100},{"targetGroupArn":{"Ref":"GreenTG71A27F2F"},"weight":0}]}}],"conditions":[{"field":"path-pattern","pathPatternConfig":{"values":["/*"]}}],"listenerArn":{"Ref":"ALBALBListenerHTTP413250C3"},"priority":1}}}}},"EcsTaskExecutionRole":{"id":"EcsTaskExecutionRole","path":"aws-ecs-blue-green-deployment/EcsTaskExecutionRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"},"managedPolicies":[{"managedPolicyArn":"*"}]}]},"children":{"ImportEcsTaskExecutionRole":{"id":"ImportEcsTaskExecutionRole","path":"aws-ecs-blue-green-deployment/EcsTaskExecutionRole/ImportEcsTaskExecutionRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/EcsTaskExecutionRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"ecs-tasks.amazonaws.com"}}],"Version":"2012-10-17"},"managedPolicyArns":[{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]]}]}}}}},"ServiceRole":{"id":"ServiceRole","path":"aws-ecs-blue-green-deployment/ServiceRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"},"managedPolicies":[{"managedPolicyArn":"*"}],"inlinePolicies":"*"},{"addToPrincipalPolicy":[{}]},{"attachInlinePolicy":["*"]},{"attachInlinePolicy":["*"]}]},"children":{"ImportServiceRole":{"id":"ImportServiceRole","path":"aws-ecs-blue-green-deployment/ServiceRole/ImportServiceRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/ServiceRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"ecs.amazonaws.com"}}],"Version":"2012-10-17"},"managedPolicyArns":[{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/service-role/AmazonEC2ContainerServiceRole"]]}],"policies":[{"policyName":"LambdaInvokePolicy","policyDocument":{"Statement":[{"Action":"lambda:InvokeFunction","Effect":"Allow","Resource":"*"}],"Version":"2012-10-17"}},{"policyName":"ELBPolicy","policyDocument":{"Statement":[{"Action":["elasticloadbalancing:ModifyListener","elasticloadbalancing:ModifyRule"],"Effect":"Allow","Resource":{"Ref":"ALBProductionListenerRule243D0687"}}],"Version":"2012-10-17"}}]}}},"DefaultPolicy":{"id":"DefaultPolicy","path":"aws-ecs-blue-green-deployment/ServiceRole/DefaultPolicy","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Policy","version":"0.0.0","metadata":["*",{"attachToRole":["*"]},{"attachToRole":["*"]},{"addStatements":[{}]}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/ServiceRole/DefaultPolicy/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnPolicy","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Policy","aws:cdk:cloudformation:props":{"policyDocument":{"Statement":[{"Action":"lambda:InvokeFunction","Effect":"Allow","Resource":[{"Fn::GetAtt":["LambdaHookBF1BC8B4","Arn"]},{"Fn::Join":["",[{"Fn::GetAtt":["LambdaHookBF1BC8B4","Arn"]},":*"]]}]}],"Version":"2012-10-17"},"policyName":"ServiceRoleDefaultPolicy94CF55F6","roles":[{"Ref":"ServiceRole4288B192"}]}}}}}}},"LambdaHook":{"id":"LambdaHook","path":"aws-ecs-blue-green-deployment/LambdaHook","constructInfo":{"fqn":"aws-cdk-lib.aws_lambda.Function","version":"0.0.0","metadata":[{"handler":"*","runtime":"*","code":"*"}]},"children":{"ServiceRole":{"id":"ServiceRole","path":"aws-ecs-blue-green-deployment/LambdaHook/ServiceRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"},"managedPolicies":[{"managedPolicyArn":"*"}]}]},"children":{"ImportServiceRole":{"id":"ImportServiceRole","path":"aws-ecs-blue-green-deployment/LambdaHook/ServiceRole/ImportServiceRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/LambdaHook/ServiceRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"}}],"Version":"2012-10-17"},"managedPolicyArns":[{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]]}]}}}}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/LambdaHook/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_lambda.CfnFunction","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Lambda::Function","aws:cdk:cloudformation:props":{"code":{"zipFile":"exports.handler = async (event, context) => {\n console.log('Event received:', JSON.stringify(event));\n return { hookStatus: 'SUCCEEDED' }; \n };"},"handler":"index.handler","role":{"Fn::GetAtt":["LambdaHookServiceRole9AAAD33B","Arn"]},"runtime":"nodejs22.x"}}},"LogGroup":{"id":"LogGroup","path":"aws-ecs-blue-green-deployment/LambdaHook/LogGroup","constructInfo":{"fqn":"aws-cdk-lib.aws_logs.LogGroup","version":"0.0.0","metadata":[{"logGroupName":"*"}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/LambdaHook/LogGroup/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_logs.CfnLogGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Logs::LogGroup","aws:cdk:cloudformation:props":{"logGroupName":{"Fn::Join":["",["/aws/lambda/",{"Ref":"LambdaHookBF1BC8B4"}]]},"retentionInDays":731}}}}}}},"TaskDef":{"id":"TaskDef","path":"aws-ecs-blue-green-deployment/TaskDef","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.FargateTaskDefinition","version":"0.0.0","metadata":["*","*","*","*"]},"children":{"TaskRole":{"id":"TaskRole","path":"aws-ecs-blue-green-deployment/TaskDef/TaskRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"}}]},"children":{"ImportTaskRole":{"id":"ImportTaskRole","path":"aws-ecs-blue-green-deployment/TaskDef/TaskRole/ImportTaskRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/TaskDef/TaskRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"ecs-tasks.amazonaws.com"}}],"Version":"2012-10-17"}}}}}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/TaskDef/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.CfnTaskDefinition","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ECS::TaskDefinition","aws:cdk:cloudformation:props":{"containerDefinitions":[{"essential":true,"image":"public.ecr.aws/nginx/nginx:latest","name":"nginx","portMappings":[{"containerPort":80,"protocol":"tcp","appProtocol":"http","name":"api"}]}],"cpu":"256","executionRoleArn":{"Fn::GetAtt":["EcsTaskExecutionRoleC0B6A84D","Arn"]},"family":"awsecsbluegreendeploymentTaskDef51D80572","memory":"512","networkMode":"awsvpc","requiresCompatibilities":["FARGATE"],"taskRoleArn":{"Fn::GetAtt":["TaskDefTaskRole1EDB4A67","Arn"]}}}},"container":{"id":"container","path":"aws-ecs-blue-green-deployment/TaskDef/container","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.ContainerDefinition","version":"0.0.0"}}}},"Service":{"id":"Service","path":"aws-ecs-blue-green-deployment/Service","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.FargateService","version":"0.0.0","metadata":["*"]},"children":{"Service":{"id":"Service","path":"aws-ecs-blue-green-deployment/Service/Service","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.CfnService","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ECS::Service","aws:cdk:cloudformation:props":{"cluster":{"Ref":"FargateCluster7CCD5F93"},"deploymentConfiguration":{"maximumPercent":200,"minimumHealthyPercent":50},"enableEcsManagedTags":false,"healthCheckGracePeriodSeconds":60,"launchType":"FARGATE","loadBalancers":[{"targetGroupArn":{"Ref":"BlueTG7C058B2C"},"containerName":"nginx","containerPort":80}],"networkConfiguration":{"awsvpcConfiguration":{"assignPublicIp":"DISABLED","subnets":[{"Ref":"VpcPrivateSubnet1Subnet536B997A"},{"Ref":"VpcPrivateSubnet2Subnet3788AAA1"}],"securityGroups":[{"Fn::GetAtt":["ECSSecurityGroupA14DBE7D","GroupId"]}]}},"taskDefinition":{"Ref":"TaskDef54694570"}}}}}},"BootstrapVersion":{"id":"BootstrapVersion","path":"aws-ecs-blue-green-deployment/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"aws-ecs-blue-green-deployment/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"aws-ecs-blue-green":{"id":"aws-ecs-blue-green","path":"aws-ecs-blue-green","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"},"children":{"DefaultTest":{"id":"DefaultTest","path":"aws-ecs-blue-green/DefaultTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"aws-ecs-blue-green/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"DeployAssert":{"id":"DeployAssert","path":"aws-ecs-blue-green/DefaultTest/DeployAssert","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"aws-ecs-blue-green/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"aws-ecs-blue-green/DefaultTest/DeployAssert/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}}}}}},"Tree":{"id":"Tree","path":"Tree","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}}}}} \ No newline at end of file +{"version":"tree-0.1","tree":{"id":"App","path":"","constructInfo":{"fqn":"aws-cdk-lib.App","version":"0.0.0"},"children":{"aws-ecs-blue-green-deployment":{"id":"aws-ecs-blue-green-deployment","path":"aws-ecs-blue-green-deployment","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"Vpc":{"id":"Vpc","path":"aws-ecs-blue-green-deployment/Vpc","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.Vpc","version":"0.0.0","metadata":[{"maxAzs":"*","restrictDefaultSecurityGroup":false}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/Vpc/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnVPC","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::VPC","aws:cdk:cloudformation:props":{"cidrBlock":"10.0.0.0/16","enableDnsHostnames":true,"enableDnsSupport":true,"instanceTenancy":"default","tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc"}]}}},"PublicSubnet1":{"id":"PublicSubnet1","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PublicSubnet","version":"0.0.0","metadata":[{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":true,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":true,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{},{"addNatGateway":["*"]}]},"children":{"Subnet":{"id":"Subnet","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[0,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.0.0/18","mapPublicIpOnLaunch":true,"tags":[{"key":"aws-cdk:subnet-name","value":"Public"},{"key":"aws-cdk:subnet-type","value":"Public"},{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"Acl":{"id":"Acl","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":[]}},"RouteTable":{"id":"RouteTable","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VpcPublicSubnet1RouteTable6C95E38E"},"subnetId":{"Ref":"VpcPublicSubnet1Subnet5C2D37C4"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","gatewayId":{"Ref":"VpcIGWD7BA715C"},"routeTableId":{"Ref":"VpcPublicSubnet1RouteTable6C95E38E"}}}},"EIP":{"id":"EIP","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/EIP","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnEIP","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::EIP","aws:cdk:cloudformation:props":{"domain":"vpc","tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1"}]}}},"NATGateway":{"id":"NATGateway","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/NATGateway","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnNatGateway","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::NatGateway","aws:cdk:cloudformation:props":{"allocationId":{"Fn::GetAtt":["VpcPublicSubnet1EIPD7E02669","AllocationId"]},"subnetId":{"Ref":"VpcPublicSubnet1Subnet5C2D37C4"},"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1"}]}}}}},"PublicSubnet2":{"id":"PublicSubnet2","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PublicSubnet","version":"0.0.0","metadata":[{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":true,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":true,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{},{"addNatGateway":["*"]}]},"children":{"Subnet":{"id":"Subnet","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[1,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.64.0/18","mapPublicIpOnLaunch":true,"tags":[{"key":"aws-cdk:subnet-name","value":"Public"},{"key":"aws-cdk:subnet-type","value":"Public"},{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"Acl":{"id":"Acl","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":[]}},"RouteTable":{"id":"RouteTable","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VpcPublicSubnet2RouteTable94F7E489"},"subnetId":{"Ref":"VpcPublicSubnet2Subnet691E08A3"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","gatewayId":{"Ref":"VpcIGWD7BA715C"},"routeTableId":{"Ref":"VpcPublicSubnet2RouteTable94F7E489"}}}},"EIP":{"id":"EIP","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/EIP","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnEIP","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::EIP","aws:cdk:cloudformation:props":{"domain":"vpc","tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2"}]}}},"NATGateway":{"id":"NATGateway","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/NATGateway","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnNatGateway","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::NatGateway","aws:cdk:cloudformation:props":{"allocationId":{"Fn::GetAtt":["VpcPublicSubnet2EIP3C605A87","AllocationId"]},"subnetId":{"Ref":"VpcPublicSubnet2Subnet691E08A3"},"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2"}]}}}}},"PrivateSubnet1":{"id":"PrivateSubnet1","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PrivateSubnet","version":"0.0.0","metadata":[{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":false,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":false,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{}]},"children":{"Subnet":{"id":"Subnet","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[0,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.128.0/18","mapPublicIpOnLaunch":false,"tags":[{"key":"aws-cdk:subnet-name","value":"Private"},{"key":"aws-cdk:subnet-type","value":"Private"},{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"Acl":{"id":"Acl","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":[]}},"RouteTable":{"id":"RouteTable","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VpcPrivateSubnet1RouteTableB2C5B500"},"subnetId":{"Ref":"VpcPrivateSubnet1Subnet536B997A"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","natGatewayId":{"Ref":"VpcPublicSubnet1NATGateway4D7517AA"},"routeTableId":{"Ref":"VpcPrivateSubnet1RouteTableB2C5B500"}}}}}},"PrivateSubnet2":{"id":"PrivateSubnet2","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PrivateSubnet","version":"0.0.0","metadata":[{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":false,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":false,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{}]},"children":{"Subnet":{"id":"Subnet","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[1,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.192.0/18","mapPublicIpOnLaunch":false,"tags":[{"key":"aws-cdk:subnet-name","value":"Private"},{"key":"aws-cdk:subnet-type","value":"Private"},{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"Acl":{"id":"Acl","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":[]}},"RouteTable":{"id":"RouteTable","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VpcPrivateSubnet2RouteTableA678073B"},"subnetId":{"Ref":"VpcPrivateSubnet2Subnet3788AAA1"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","natGatewayId":{"Ref":"VpcPublicSubnet2NATGateway9182C01D"},"routeTableId":{"Ref":"VpcPrivateSubnet2RouteTableA678073B"}}}}}},"IGW":{"id":"IGW","path":"aws-ecs-blue-green-deployment/Vpc/IGW","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnInternetGateway","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::InternetGateway","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc"}]}}},"VPCGW":{"id":"VPCGW","path":"aws-ecs-blue-green-deployment/Vpc/VPCGW","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnVPCGatewayAttachment","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::VPCGatewayAttachment","aws:cdk:cloudformation:props":{"internetGatewayId":{"Ref":"VpcIGWD7BA715C"},"vpcId":{"Ref":"Vpc8378EB38"}}}}}},"FargateCluster":{"id":"FargateCluster","path":"aws-ecs-blue-green-deployment/FargateCluster","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.Cluster","version":"0.0.0","metadata":[{"vpc":"*","defaultCloudMapNamespace":{"name":"*","useForServiceConnect":true}},{"addDefaultCloudMapNamespace":[{"name":"*","useForServiceConnect":true}]}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/FargateCluster/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.CfnCluster","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ECS::Cluster","aws:cdk:cloudformation:props":{"serviceConnectDefaults":{"namespace":{"Fn::GetAtt":["FargateClusterDefaultServiceDiscoveryNamespace04381E1E","Arn"]}}}}},"DefaultServiceDiscoveryNamespace":{"id":"DefaultServiceDiscoveryNamespace","path":"aws-ecs-blue-green-deployment/FargateCluster/DefaultServiceDiscoveryNamespace","constructInfo":{"fqn":"aws-cdk-lib.aws_servicediscovery.PrivateDnsNamespace","version":"0.0.0","metadata":[{"name":"*","vpc":"*"}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/FargateCluster/DefaultServiceDiscoveryNamespace/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_servicediscovery.CfnPrivateDnsNamespace","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ServiceDiscovery::PrivateDnsNamespace","aws:cdk:cloudformation:props":{"name":"bluegreendeployment.com","vpc":{"Ref":"Vpc8378EB38"}}}}}}}},"BlueTG":{"id":"BlueTG","path":"aws-ecs-blue-green-deployment/BlueTG","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationTargetGroup","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/BlueTG/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnTargetGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::TargetGroup","aws:cdk:cloudformation:props":{"healthCheckPath":"/","matcher":{"httpCode":"200"},"port":80,"protocol":"HTTP","targetGroupAttributes":[{"key":"stickiness.enabled","value":"false"}],"targetType":"ip","vpcId":{"Ref":"Vpc8378EB38"}}}}}},"GreenTG":{"id":"GreenTG","path":"aws-ecs-blue-green-deployment/GreenTG","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationTargetGroup","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/GreenTG/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnTargetGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::TargetGroup","aws:cdk:cloudformation:props":{"healthCheckPath":"/","matcher":{"httpCode":"200"},"port":80,"protocol":"HTTP","targetGroupAttributes":[{"key":"stickiness.enabled","value":"false"}],"targetType":"ip","vpcId":{"Ref":"Vpc8378EB38"}}}}}},"LBSecurityGroup":{"id":"LBSecurityGroup","path":"aws-ecs-blue-green-deployment/LBSecurityGroup","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.SecurityGroup","version":"0.0.0","metadata":[{"vpc":"*","allowAllOutbound":true},{"addIngressRule":[{"canInlineRule":true,"connections":"*","uniqueId":"*"},{}]},{"addIngressRule":[{"canInlineRule":true,"connections":"*","uniqueId":"*"},{},"*",false]},{"addEgressRule":["*",{},"*",true]}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/LBSecurityGroup/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSecurityGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SecurityGroup","aws:cdk:cloudformation:props":{"groupDescription":"aws-ecs-blue-green-deployment/LBSecurityGroup","securityGroupEgress":[{"cidrIp":"0.0.0.0/0","description":"Allow all outbound traffic by default","ipProtocol":"-1"}],"securityGroupIngress":[{"cidrIp":"0.0.0.0/0","ipProtocol":"tcp","fromPort":80,"toPort":80,"description":"from 0.0.0.0/0:80"}],"vpcId":{"Ref":"Vpc8378EB38"}}}}}},"ECSSecurityGroup":{"id":"ECSSecurityGroup","path":"aws-ecs-blue-green-deployment/ECSSecurityGroup","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.SecurityGroup","version":"0.0.0","metadata":[{"vpc":"*","allowAllOutbound":true},{"addIngressRule":["*",{}]},{"addIngressRule":["*",{},"*",false]}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/ECSSecurityGroup/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSecurityGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SecurityGroup","aws:cdk:cloudformation:props":{"groupDescription":"aws-ecs-blue-green-deployment/ECSSecurityGroup","securityGroupEgress":[{"cidrIp":"0.0.0.0/0","description":"Allow all outbound traffic by default","ipProtocol":"-1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"from awsecsbluegreendeploymentLBSecurityGroup67E4A783:80":{"id":"from awsecsbluegreendeploymentLBSecurityGroup67E4A783:80","path":"aws-ecs-blue-green-deployment/ECSSecurityGroup/from awsecsbluegreendeploymentLBSecurityGroup67E4A783:80","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSecurityGroupIngress","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SecurityGroupIngress","aws:cdk:cloudformation:props":{"description":"from awsecsbluegreendeploymentLBSecurityGroup67E4A783:80","fromPort":80,"groupId":{"Fn::GetAtt":["ECSSecurityGroupA14DBE7D","GroupId"]},"ipProtocol":"tcp","sourceSecurityGroupId":{"Fn::GetAtt":["LBSecurityGroup4464B654","GroupId"]},"toPort":80}}}}},"ALB":{"id":"ALB","path":"aws-ecs-blue-green-deployment/ALB","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationLoadBalancer","version":"0.0.0","metadata":["*","*"]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/ALB/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnLoadBalancer","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::LoadBalancer","aws:cdk:cloudformation:props":{"loadBalancerAttributes":[{"key":"deletion_protection.enabled","value":"false"},{"key":"idle_timeout.timeout_seconds","value":"60"}],"scheme":"internet-facing","securityGroups":[{"Fn::GetAtt":["LBSecurityGroup4464B654","GroupId"]}],"subnets":[{"Ref":"VpcPublicSubnet1Subnet5C2D37C4"},{"Ref":"VpcPublicSubnet2Subnet691E08A3"}],"type":"application"}}},"ALBListenerHTTP":{"id":"ALBListenerHTTP","path":"aws-ecs-blue-green-deployment/ALB/ALBListenerHTTP","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationListener","version":"0.0.0","metadata":["*","*"]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/ALB/ALBListenerHTTP/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnListener","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::Listener","aws:cdk:cloudformation:props":{"defaultActions":[{"type":"fixed-response","fixedResponseConfig":{"statusCode":"404"}}],"loadBalancerArn":{"Ref":"ALBAEE750D2"},"port":80,"protocol":"HTTP"}}}}}}},"ALBProductionListenerRule":{"id":"ALBProductionListenerRule","path":"aws-ecs-blue-green-deployment/ALBProductionListenerRule","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationListenerRule","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/ALBProductionListenerRule/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnListenerRule","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::ListenerRule","aws:cdk:cloudformation:props":{"actions":[{"type":"forward","forwardConfig":{"targetGroups":[{"targetGroupArn":{"Ref":"BlueTG7C058B2C"},"weight":100},{"targetGroupArn":{"Ref":"GreenTG71A27F2F"},"weight":0}]}}],"conditions":[{"field":"path-pattern","pathPatternConfig":{"values":["/*"]}}],"listenerArn":{"Ref":"ALBALBListenerHTTP413250C3"},"priority":1}}}}},"LambdaHook":{"id":"LambdaHook","path":"aws-ecs-blue-green-deployment/LambdaHook","constructInfo":{"fqn":"aws-cdk-lib.aws_lambda.Function","version":"0.0.0","metadata":[{"handler":"*","runtime":"*","code":"*"}]},"children":{"ServiceRole":{"id":"ServiceRole","path":"aws-ecs-blue-green-deployment/LambdaHook/ServiceRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"},"managedPolicies":[{"managedPolicyArn":"*"}]}]},"children":{"ImportServiceRole":{"id":"ImportServiceRole","path":"aws-ecs-blue-green-deployment/LambdaHook/ServiceRole/ImportServiceRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/LambdaHook/ServiceRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"}}],"Version":"2012-10-17"},"managedPolicyArns":[{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]]}]}}}}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/LambdaHook/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_lambda.CfnFunction","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Lambda::Function","aws:cdk:cloudformation:props":{"code":{"zipFile":"exports.handler = async (event, context) => {\n console.log('Event received:', JSON.stringify(event));\n return { hookStatus: 'SUCCEEDED' }; \n };"},"handler":"index.handler","role":{"Fn::GetAtt":["LambdaHookServiceRole9AAAD33B","Arn"]},"runtime":"nodejs22.x"}}},"LogGroup":{"id":"LogGroup","path":"aws-ecs-blue-green-deployment/LambdaHook/LogGroup","constructInfo":{"fqn":"aws-cdk-lib.aws_logs.LogGroup","version":"0.0.0","metadata":[{"logGroupName":"*"}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/LambdaHook/LogGroup/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_logs.CfnLogGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Logs::LogGroup","aws:cdk:cloudformation:props":{"logGroupName":{"Fn::Join":["",["/aws/lambda/",{"Ref":"LambdaHookBF1BC8B4"}]]},"retentionInDays":731}}}}}}},"TaskDef":{"id":"TaskDef","path":"aws-ecs-blue-green-deployment/TaskDef","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.FargateTaskDefinition","version":"0.0.0","metadata":["*","*","*","*"]},"children":{"TaskRole":{"id":"TaskRole","path":"aws-ecs-blue-green-deployment/TaskDef/TaskRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"}}]},"children":{"ImportTaskRole":{"id":"ImportTaskRole","path":"aws-ecs-blue-green-deployment/TaskDef/TaskRole/ImportTaskRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/TaskDef/TaskRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"ecs-tasks.amazonaws.com"}}],"Version":"2012-10-17"}}}}}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/TaskDef/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.CfnTaskDefinition","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ECS::TaskDefinition","aws:cdk:cloudformation:props":{"containerDefinitions":[{"essential":true,"image":"public.ecr.aws/nginx/nginx:latest","name":"nginx","portMappings":[{"containerPort":80,"protocol":"tcp","appProtocol":"http","name":"api"}]}],"cpu":"256","family":"awsecsbluegreendeploymentTaskDef51D80572","memory":"512","networkMode":"awsvpc","requiresCompatibilities":["FARGATE"],"taskRoleArn":{"Fn::GetAtt":["TaskDefTaskRole1EDB4A67","Arn"]}}}},"container":{"id":"container","path":"aws-ecs-blue-green-deployment/TaskDef/container","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.ContainerDefinition","version":"0.0.0"}}}},"Service":{"id":"Service","path":"aws-ecs-blue-green-deployment/Service","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.FargateService","version":"0.0.0","metadata":["*"]},"children":{"Service":{"id":"Service","path":"aws-ecs-blue-green-deployment/Service/Service","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.CfnService","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ECS::Service","aws:cdk:cloudformation:props":{"cluster":{"Ref":"FargateCluster7CCD5F93"},"deploymentConfiguration":{"maximumPercent":200,"minimumHealthyPercent":50,"strategy":"BLUE_GREEN","lifecycleHooks":[{"hookTargetArn":{"Fn::GetAtt":["LambdaHookBF1BC8B4","Arn"]},"roleArn":{"Fn::GetAtt":["ServiceHook0RoleACE68A7A","Arn"]},"lifecycleStages":["PRE_SCALE_UP"]}]},"enableEcsManagedTags":false,"healthCheckGracePeriodSeconds":60,"launchType":"FARGATE","loadBalancers":[{"targetGroupArn":{"Ref":"BlueTG7C058B2C"},"containerName":"nginx","containerPort":80,"advancedConfiguration":{"alternateTargetGroupArn":{"Ref":"GreenTG71A27F2F"},"roleArn":{"Fn::GetAtt":["ServiceLBAlternateOptionsRole06C91D94","Arn"]},"productionListenerRule":{"Ref":"ALBProductionListenerRule243D0687"}}}],"networkConfiguration":{"awsvpcConfiguration":{"assignPublicIp":"DISABLED","subnets":[{"Ref":"VpcPrivateSubnet1Subnet536B997A"},{"Ref":"VpcPrivateSubnet2Subnet3788AAA1"}],"securityGroups":[{"Fn::GetAtt":["ECSSecurityGroupA14DBE7D","GroupId"]}]}},"taskDefinition":{"Ref":"TaskDef54694570"}}}},"LBAlternateOptionsRole":{"id":"LBAlternateOptionsRole","path":"aws-ecs-blue-green-deployment/Service/LBAlternateOptionsRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"},"managedPolicies":[{"managedPolicyArn":"*"},{"managedPolicyArn":"*"}]}]},"children":{"ImportLBAlternateOptionsRole":{"id":"ImportLBAlternateOptionsRole","path":"aws-ecs-blue-green-deployment/Service/LBAlternateOptionsRole/ImportLBAlternateOptionsRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/Service/LBAlternateOptionsRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"ecs.amazonaws.com"}}],"Version":"2012-10-17"},"managedPolicyArns":[{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/AmazonECSInfrastructureRolePolicyForLoadBalancers"]]},{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/service-role/AmazonEC2ContainerServiceRole"]]}]}}}}},"Hook0Role":{"id":"Hook0Role","path":"aws-ecs-blue-green-deployment/Service/Hook0Role","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"}},{"addToPrincipalPolicy":[{}]},{"attachInlinePolicy":["*"]},{"attachInlinePolicy":["*"]}]},"children":{"ImportHook0Role":{"id":"ImportHook0Role","path":"aws-ecs-blue-green-deployment/Service/Hook0Role/ImportHook0Role","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/Service/Hook0Role/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"ecs.amazonaws.com"}}],"Version":"2012-10-17"}}}},"DefaultPolicy":{"id":"DefaultPolicy","path":"aws-ecs-blue-green-deployment/Service/Hook0Role/DefaultPolicy","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Policy","version":"0.0.0","metadata":["*",{"attachToRole":["*"]},{"attachToRole":["*"]},{"addStatements":[{}]}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/Service/Hook0Role/DefaultPolicy/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnPolicy","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Policy","aws:cdk:cloudformation:props":{"policyDocument":{"Statement":[{"Action":"lambda:InvokeFunction","Effect":"Allow","Resource":[{"Fn::GetAtt":["LambdaHookBF1BC8B4","Arn"]},{"Fn::Join":["",[{"Fn::GetAtt":["LambdaHookBF1BC8B4","Arn"]},":*"]]}]}],"Version":"2012-10-17"},"policyName":"ServiceHook0RoleDefaultPolicyD7C31604","roles":[{"Ref":"ServiceHook0RoleACE68A7A"}]}}}}}}}}},"BootstrapVersion":{"id":"BootstrapVersion","path":"aws-ecs-blue-green-deployment/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"aws-ecs-blue-green-deployment/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"aws-ecs-blue-green":{"id":"aws-ecs-blue-green","path":"aws-ecs-blue-green","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"},"children":{"DefaultTest":{"id":"DefaultTest","path":"aws-ecs-blue-green/DefaultTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"aws-ecs-blue-green/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"DeployAssert":{"id":"DeployAssert","path":"aws-ecs-blue-green/DefaultTest/DeployAssert","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"aws-ecs-blue-green/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"aws-ecs-blue-green/DefaultTest/DeployAssert/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}}}}}},"Tree":{"id":"Tree","path":"Tree","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}}}}} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.ts index a7c20dc113b60..51ef28255052d 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.ts @@ -1,6 +1,5 @@ import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; -import * as iam from 'aws-cdk-lib/aws-iam'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as cdk from 'aws-cdk-lib'; import * as ecs from 'aws-cdk-lib/aws-ecs'; @@ -89,45 +88,6 @@ const prodListenerRule = new elbv2.ApplicationListenerRule(stack, 'ALBProduction ]), }); -// Create granular IAM roles -const ecsTaskExecutionRole = new iam.Role(stack, 'EcsTaskExecutionRole', { - assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), - managedPolicies: [ - iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy'), - ], -}); - -// Create Blue/Green deployment service role -const ecsServiceRole = new iam.Role(stack, 'ServiceRole', { - assumedBy: new iam.CompositePrincipal( - new iam.ServicePrincipal('ecs.amazonaws.com'), - ), - managedPolicies: [ - iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonEC2ContainerServiceRole'), - ], - inlinePolicies: { - LambdaInvokePolicy: new iam.PolicyDocument({ - statements: [ - new iam.PolicyStatement({ - actions: ['lambda:InvokeFunction'], - resources: ['*'], - }), - ], - }), - ELBPolicy: new iam.PolicyDocument({ - statements: [ - new iam.PolicyStatement({ - actions: [ - 'elasticloadbalancing:ModifyRule', - 'elasticloadbalancing:ModifyListener', - ], - resources: [prodListenerRule.listenerRuleArn], - }), - ], - }), - }, -}); - // Create Lambda hook const lambdaHook = new lambda.Function(stack, 'LambdaHook', { handler: 'index.handler', @@ -138,13 +98,10 @@ const lambdaHook = new lambda.Function(stack, 'LambdaHook', { };`), }); -lambdaHook.grantInvoke(ecsServiceRole); - // Create task definition const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef', { memoryLimitMiB: 512, cpu: 256, - executionRole: ecsTaskExecutionRole, }); // Add container to task definition @@ -163,39 +120,23 @@ const service = new ecs.FargateService(stack, 'Service', { cluster, taskDefinition, securityGroups: [ecsSecurityGroup], + deploymentStrategy: ecs.DeploymentStrategy.BLUE_GREEN, }); -service.attachToApplicationTargetGroup(blueTargetGroup); +service.addLifecycleHook(new ecs.DeploymentLifecycleLambdaTarget(lambdaHook, { + lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], +})); -// Use escape hatching to set B/G deployment properties -const cfnService = service.node.defaultChild as ecs.CfnService; -cfnService.addPropertyOverride('DeploymentController', { - Type: 'ECS', -}); - -cfnService.addPropertyOverride('DeploymentConfiguration', { - DeploymentCircuitBreaker: { - Enable: false, - Rollback: false, - }, - MaximumPercent: 200, - MinimumHealthyPercent: 100, - Strategy: 'BLUE_GREEN', - BakeTimeInMinutes: 0, - LifecycleHooks: [{ - HookTargetArn: lambdaHook.functionArn, - RoleArn: ecsServiceRole.roleArn, - LifecycleStages: ['POST_TEST_TRAFFIC_SHIFT'], - }], -}); - -cfnService.addPropertyOverride('LoadBalancers.0', { - AdvancedConfiguration: { - AlternateTargetGroupArn: greenTargetGroup.targetGroupArn, - RoleArn: ecsServiceRole.roleArn, - ProductionListenerRule: prodListenerRule.listenerRuleArn, - }, -}); +const target = service.loadBalancerTarget({ + containerName: 'nginx', + containerPort: 80, + protocol: ecs.Protocol.TCP, +}, new ecs.AlternateTarget({ + alternateTargetGroup: greenTargetGroup, + productionListener: ecs.ListenerRuleConfiguration.applicationListenerRule(prodListenerRule), +})); + +target.attachToApplicationTargetGroup(blueTargetGroup); // Create integration test new integ.IntegTest(app, 'aws-ecs-blue-green', { diff --git a/packages/aws-cdk-lib/aws-ecs/README.md b/packages/aws-cdk-lib/aws-ecs/README.md index 375aa395cea26..49e91d89aa52c 100644 --- a/packages/aws-cdk-lib/aws-ecs/README.md +++ b/packages/aws-cdk-lib/aws-ecs/README.md @@ -2076,6 +2076,32 @@ Amazon ECS supports native blue/green deployments that allow you to deploy new v [Amazon ECS blue/green deployments](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/deployment-type-blue-green.html) +### Using Fargate L2 constructs for Blue/Green Feature + +```ts +const service = new ecs.FargateService(stack, 'Service', { + cluster, + taskDefinition, + securityGroups: [ecsSecurityGroup], + deploymentStrategy: ecs.DeploymentStrategy.BLUE_GREEN, +}); + +service.addLifecycleHook(new ecs.DeploymentLifecycleLambdaTarget(lambdaHook, { + lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], +})); + +const target = service.loadBalancerTarget({ + containerName: 'nginx', + containerPort: 80, + protocol: ecs.Protocol.TCP, +}, new ecs.AlternateTarget({ + alternateTargetGroup: greenTargetGroup, + productionListener: ecs.ListenerRuleConfiguration.applicationListenerRule(prodListenerRule), +})); + +target.attachToApplicationTargetGroup(blueTargetGroup); +``` + ### Using Escape Hatches for Blue/Green Features The new blue/green deployment features are added to CloudFormation but not yet available in the CDK L2 constructs, you can use escape hatches to access them through the L1 (CfnService) construct. diff --git a/packages/aws-cdk-lib/aws-ecs/lib/alternate-target-configuration.ts b/packages/aws-cdk-lib/aws-ecs/lib/alternate-target-configuration.ts new file mode 100644 index 0000000000000..b48654797603a --- /dev/null +++ b/packages/aws-cdk-lib/aws-ecs/lib/alternate-target-configuration.ts @@ -0,0 +1,165 @@ +import { IConstruct } from 'constructs'; +import * as elbv2 from '../../aws-elasticloadbalancingv2'; +import * as iam from '../../aws-iam'; + +/** + * Represents a listener configuration for advanced load balancer settings + */ +export abstract class ListenerRuleConfiguration { + /** + * Use an Application Load Balancer listener rule + */ + public static applicationListenerRule(rule: elbv2.ApplicationListenerRule): ListenerRuleConfiguration { + return new ApplicationListenerRuleConfiguration(rule); + } + + /** + * Use a Network Load Balancer listener + */ + public static networkListener(listener: elbv2.NetworkListener): ListenerRuleConfiguration { + return new NetworkListenerConfiguration(listener); + } + + /** + * @internal + */ + public abstract readonly _listenerArn: string; +} + +class ApplicationListenerRuleConfiguration extends ListenerRuleConfiguration { + constructor(private readonly rule: elbv2.ApplicationListenerRule) { + super(); + } + + public get _listenerArn(): string { + return this.rule.listenerRuleArn; + } +} + +class NetworkListenerConfiguration extends ListenerRuleConfiguration { + constructor(private readonly listener: elbv2.NetworkListener) { + super(); + } + + public get _listenerArn(): string { + return this.listener.listenerArn; + } +} + +/** + * Configuration returned by AlternateTargetConfiguration.bind() + */ +export interface AlternateTargetConfig { + /** + * The ARN of the alternate target group + */ + readonly alternateTargetGroupArn: string; + + /** + * The IAM role ARN for the configuration + * @default - a new role will be created + */ + readonly roleArn: string; + + /** + * The production listener rule ARN (ALB) or listener ARN (NLB) + * @default - none + */ + readonly productionListenerRule?: string; + + /** + * The test listener rule ARN (ALB) or listener ARN (NLB) + * @default - none + */ + readonly testListenerRule?: string; +} + +/** + * Interface for advanced configuration + */ +export interface IAlternateTarget { + /** + * Bind this configuration to a service + * + * @param scope The construct scope + * @returns The configuration to apply to the service + */ + bind(scope: IConstruct): AlternateTargetConfig; +} + +/** + * Options for AdvancedConfiguration + */ +export interface AlternateTargetOptions { + /** + * The IAM role for the configuration + * @default - a new role will be created + */ + readonly role?: iam.IRole; + + /** + * The test listener configuration + * @default - none + */ + readonly testListener?: ListenerRuleConfiguration; +} + +/** + * Properties for AdvancedConfiguration + */ +export interface AlternateTargetProps extends AlternateTargetOptions { + /** + * Id for alternate target options + * @default - none + */ + readonly id?: string; + + /** + * The alternate target group + */ + readonly alternateTargetGroup: elbv2.ITargetGroup; + + /** + * The production listener rule ARN (ALB) or listener ARN (NLB) + */ + readonly productionListener: ListenerRuleConfiguration; +} + +/** + * Advanced configuration for load balancer settings + */ +export class AlternateTarget implements IAlternateTarget { + constructor(private readonly props: AlternateTargetProps) {} + + /** + * Bind this configuration to a service + */ + public bind(scope: IConstruct): AlternateTargetConfig { + const resources: string[] = [ + this.props.alternateTargetGroup.targetGroupArn, + this.props.productionListener._listenerArn, + ]; + + if (this.props.testListener) { + resources.push(this.props.testListener._listenerArn); + } + + const roleId = this.props.id? `${this.props.id}Role`: 'LBAlternateOptionsRole'; + const role = this.props.role ?? new iam.Role(scope, roleId, { + assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonECSInfrastructureRolePolicyForLoadBalancers'), + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonEC2ContainerServiceRole'), + ], + }); + + const config: AlternateTargetConfig = { + alternateTargetGroupArn: this.props.alternateTargetGroup.targetGroupArn, + roleArn: role.roleArn, + productionListenerRule: this.props.productionListener._listenerArn, + testListenerRule: this.props.testListener?._listenerArn || undefined, + }; + + return config; + } +} diff --git a/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts b/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts index f2a73a0745274..3493471dcb70d 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts @@ -26,6 +26,7 @@ import { } from '../../../core'; import * as cxapi from '../../../cx-api'; import { RegionInfo } from '../../../region-info'; +import { IAlternateTarget } from '../alternate-target-configuration'; import { LoadBalancerTargetOptions, NetworkMode, @@ -34,6 +35,7 @@ import { } from '../base/task-definition'; import { ICluster, CapacityProviderStrategy, ExecuteCommandLogging, Cluster } from '../cluster'; import { ContainerDefinition, Protocol } from '../container-definition'; +import { IDeploymentLifecycleHookTarget } from '../deployment-lifecycle-hook-target'; import { CfnService } from '../ecs.generated'; import { LogDriver, LogDriverConfig } from '../log-drivers/log-driver'; @@ -435,6 +437,24 @@ export interface BaseServiceOptions { * @default - undefined */ readonly volumeConfigurations?: ServiceManagedVolume[]; + + /** + * The deployment strategy to use for the service. + * @default ROLLING + */ + readonly deploymentStrategy?: DeploymentStrategy; + + /** + * bake time minutes for service. + * @default - none + */ + readonly bakeTime?: Duration; + + /** + * The lifecycle hooks to execute during deployment stages + * @default - none; + */ + readonly lifecycleHooks?: IDeploymentLifecycleHookTarget[]; } /** @@ -655,6 +675,12 @@ export abstract class BaseService extends Resource */ protected _serviceConnectConfig?: CfnService.ServiceConnectConfigurationProperty; + /** + * Whether this service is using the ECS deployment controller. + * @internal + */ + private readonly isEcsDeploymentController: boolean; + private readonly resource: CfnService; private scalableTaskCount?: ScalableTaskCount; @@ -663,6 +689,12 @@ export abstract class BaseService extends Resource */ private readonly volumes: ServiceManagedVolume[] = []; + /** + * A deployment lifecycle hook runs custom logic at specific stages of the deployment process. + * @default - none + */ + private readonly lifecycleHooks: IDeploymentLifecycleHookTarget[] = []; + /** * Constructs a new instance of the BaseService class. */ @@ -689,6 +721,9 @@ export abstract class BaseService extends Resource const propagateTagsFromSource = props.propagateTaskTagsFrom ?? props.propagateTags ?? PropagatedTagSource.NONE; const deploymentController = this.getDeploymentController(props); + + // Determine if this service is using the ECS deployment controller + this.isEcsDeploymentController = !deploymentController || deploymentController.type === DeploymentControllerType.ECS; this.resource = new CfnService(this, 'Service', { desiredCount: props.desiredCount, serviceName: this.physicalName, @@ -701,6 +736,9 @@ export abstract class BaseService extends Resource rollback: props.circuitBreaker.rollback ?? false, } : undefined, alarms: Lazy.any({ produce: () => this.deploymentAlarms }, { omitEmptyArray: true }), + strategy: props.deploymentStrategy, + bakeTimeInMinutes: props.bakeTime?.toMinutes(), + lifecycleHooks: Lazy.any({ produce: () => this.renderLifecycleHooks() }), }, propagateTags: propagateTagsFromSource === PropagatedTagSource.NONE ? undefined : props.propagateTags, enableEcsManagedTags: props.enableECSManagedTags ?? false, @@ -817,9 +855,39 @@ export abstract class BaseService extends Resource } } + if (props.lifecycleHooks) { + if (this.isEcsDeploymentController) { + props.lifecycleHooks.forEach(target => this.addLifecycleHook(target)); + } else { + throw new ValidationError('Deployment lifecycle hooks requires the ECS deployment controller.', this); + } + } + this.node.defaultChild = this.resource; } + /** + * Add a deployment lifecycle hook target + * @param target The lifecycle hook target to add + */ + public addLifecycleHook(target: IDeploymentLifecycleHookTarget) { + if (!this.isEcsDeploymentController) { + throw new ValidationError('Deployment lifecycle hooks requires the ECS deployment controller.', this); + } + this.lifecycleHooks.push(target); + } + + private renderLifecycleHooks(): CfnService.DeploymentLifecycleHookProperty[] { + return this.lifecycleHooks.map((target, index) => { + const config = target.bind(this, `Hook${index}`); + return { + hookTargetArn: config.targetArn, + roleArn: config.role!.roleArn, + lifecycleStages: config.lifecycleStages.map(stage => stage.toString()), + }; + }); + } + /** * Adds a volume to the Service. */ @@ -1214,17 +1282,22 @@ export abstract class BaseService extends Resource * })], * }); */ - public loadBalancerTarget(options: LoadBalancerTargetOptions): IEcsLoadBalancerTarget { + public loadBalancerTarget(options: LoadBalancerTargetOptions, alternateOptions?: IAlternateTarget): IEcsLoadBalancerTarget { + if (alternateOptions && !this.isEcsDeploymentController) { + throw new ValidationError('Deployment lifecycle hooks requires the ECS deployment controller.', this); + } + const self = this; const target = this.taskDefinition._validateTarget(options); const connections = self.connections; + const advancedConfiguration = alternateOptions; return { attachToApplicationTargetGroup(targetGroup: elbv2.ApplicationTargetGroup): elbv2.LoadBalancerTargetProps { targetGroup.registerConnectable(self, self.taskDefinition._portRangeFromPortMapping(target.portMapping)); - return self.attachToELBv2(targetGroup, target.containerName, target.portMapping.containerPort!); + return self.attachToELBv2(targetGroup, target.containerName, target.portMapping.containerPort!, advancedConfiguration); }, attachToNetworkTargetGroup(targetGroup: elbv2.NetworkTargetGroup): elbv2.LoadBalancerTargetProps { - return self.attachToELBv2(targetGroup, target.containerName, target.portMapping.containerPort!); + return self.attachToELBv2(targetGroup, target.containerName, target.portMapping.containerPort!, advancedConfiguration); }, connections, attachToClassicLB(loadBalancer: elb.LoadBalancer): void { @@ -1486,15 +1559,21 @@ export abstract class BaseService extends Resource /** * Shared logic for attaching to an ELBv2 */ - private attachToELBv2(targetGroup: elbv2.ITargetGroup, containerName: string, containerPort: number): elbv2.LoadBalancerTargetProps { + private attachToELBv2( + targetGroup: elbv2.ITargetGroup, + containerName: string, + containerPort: number, + alternateTarget?: IAlternateTarget): elbv2.LoadBalancerTargetProps { if (this.taskDefinition.networkMode === NetworkMode.NONE) { throw new ValidationError('Cannot use a load balancer if NetworkMode is None. Use Bridge, Host or AwsVpc instead.', this); } + const advancedConfiguration = alternateTarget?.bind(this) || undefined; this.loadBalancers.push({ targetGroupArn: targetGroup.targetGroupArn, containerName, containerPort, + advancedConfiguration, }); // Service creation can only happen after the load balancer has @@ -1580,6 +1659,45 @@ export abstract class BaseService extends Resource perRequestTimeoutSeconds: perRequestTimeout?.toSeconds(), }; } + + /** + * Checks if the service is using the ECS deployment controller. + * @returns true if the service is using the ECS deployment controller or if no deployment controller is specified (defaults to ECS) + */ + public isUsingECSDeploymentController(): boolean { + return this.isEcsDeploymentController; + } + + /** + * Enables blue-green deployment strategy for the service with optional lifecycle hooks. + * @param alternateConfig alternate configuration for the deployment + * @param lifecycleHooks Optional lifecycle hooks to execute during deployment + */ + public enableBlueGreenDeployment(alternateConfig: IAlternateTarget, lifecycleHooks?: IDeploymentLifecycleHookTarget[]) { + if (!this.isEcsDeploymentController) { + throw new ValidationError('Blue-green deployment requires the ECS deployment controller.', this); + } + + // Set deployment strategy to BLUE_GREEN + this.resource.deploymentConfiguration = { + ...this.resource.deploymentConfiguration, + strategy: DeploymentStrategy.BLUE_GREEN, + }; + + // Add lifecycle hooks if provided + if (lifecycleHooks && lifecycleHooks.length > 0) { + lifecycleHooks.forEach(hook => this.addLifecycleHook(hook)); + } + + // Use the default container with the alternate configuration if provided + if (this.taskDefinition.defaultContainer) { + this.loadBalancerTarget({ + containerName: this.taskDefinition.defaultContainer.containerName, + }, alternateConfig); + } + + return this; + } } /** @@ -1726,6 +1844,20 @@ export enum DeploymentControllerType { EXTERNAL = 'EXTERNAL', } +/** + * The deployment stratergy to use for ECS controller + */ +export enum DeploymentStrategy { + /** + * Rolling update deployment + */ + ROLLING = 'ROLLING', + /** + * Blue/green deployment + */ + BLUE_GREEN = 'BLUE_GREEN', +} + /** * Propagate tags from either service or task definition */ diff --git a/packages/aws-cdk-lib/aws-ecs/lib/deployment-lifecycle-hook-target.ts b/packages/aws-cdk-lib/aws-ecs/lib/deployment-lifecycle-hook-target.ts new file mode 100644 index 0000000000000..de247960d10d5 --- /dev/null +++ b/packages/aws-cdk-lib/aws-ecs/lib/deployment-lifecycle-hook-target.ts @@ -0,0 +1,117 @@ +import { IConstruct } from 'constructs'; +import * as iam from '../../aws-iam'; +import * as lambda from '../../aws-lambda'; + +/** + * Deployment lifecycle stages where hooks can be executed + */ +export enum DeploymentLifecycleStage { + /** + * Execute during service reconciliation + */ + RECONCILE_SERVICE = 'RECONCILE_SERVICE', + /** + * Execute before scaling up tasks + */ + PRE_SCALE_UP = 'PRE_SCALE_UP', + /** + * Execute after scaling up tasks + */ + POST_SCALE_UP = 'POST_SCALE_UP', + /** + * Execute during test traffic shift + */ + TEST_TRAFFIC_SHIFT = 'TEST_TRAFFIC_SHIFT', + /** + * Execute after test traffic shift + */ + POST_TEST_TRAFFIC_SHIFT = 'POST_TEST_TRAFFIC_SHIFT', + /** + * Execute during production traffic shift + */ + PRODUCTION_TRAFFIC_SHIFT = 'PRODUCTION_TRAFFIC_SHIFT', + /** + * Execute after production traffic shift + */ + POST_PRODUCTION_TRAFFIC_SHIFT = 'POST_PRODUCTION_TRAFFIC_SHIFT', +} + +/** + * Configuration for a deployment lifecycle hook target + */ +export interface DeploymentLifecycleHookTargetConfig { + /** + * The ARN of the target resource + */ + readonly targetArn: string; + + /** + * The IAM role that grants permissions to invoke the target + * @default - a role will be created automatically + */ + readonly role?: iam.IRole; + + /** + * The lifecycle stages when this hook should be executed + */ + readonly lifecycleStages: DeploymentLifecycleStage[]; +} + +/** + * Interface for deployment lifecycle hook targets + */ +export interface IDeploymentLifecycleHookTarget { + /** + * Bind this target to a deployment lifecycle hook + * + * @param scope The construct scope + * @param id A unique identifier for this binding + */ + bind(scope: IConstruct, id?: string): DeploymentLifecycleHookTargetConfig; +} + +/** + * Configuration for a lambda deployment lifecycle hook + */ +export interface DeploymentLifecycleLambdaTargetProps { + /** + * The IAM role that grants permissions to invoke the lambda target + * @default - A unique role will be generated for this lambda function. + */ + readonly role?: iam.IRole; + + /** + * The lifecycle stages when this hook should be executed + */ + readonly lifecycleStages: DeploymentLifecycleStage[]; +} + +/** + * Use an AWS Lambda function as a deployment lifecycle hook target + */ +export class DeploymentLifecycleLambdaTarget implements IDeploymentLifecycleHookTarget { + constructor( + private readonly handler: lambda.IFunction, + private readonly props: DeploymentLifecycleLambdaTargetProps, + ) { } + + /** + * Bind this target to a deployment lifecycle hook + */ + public bind(scope: IConstruct, id?: string): DeploymentLifecycleHookTargetConfig { + // Create role if not provided + let role = this.props.role; + if (!role) { + role = new iam.Role(scope, `${id}Role`, { + assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'), + }); + this.handler.grantInvoke(role); + } + + return { + targetArn: this.handler.functionArn, + role: role, + lifecycleStages: this.props.lifecycleStages, + }; + } +} diff --git a/packages/aws-cdk-lib/aws-ecs/lib/external/external-service.ts b/packages/aws-cdk-lib/aws-ecs/lib/external/external-service.ts index 3c0ea94e18398..36b8e26ebd780 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/external/external-service.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/external/external-service.ts @@ -6,6 +6,7 @@ import * as cloudmap from '../../../aws-servicediscovery'; import { ArnFormat, Resource, Stack, Annotations, ValidationError } from '../../../core'; import { addConstructMetadata, MethodMetadata } from '../../../core/lib/metadata-resource'; import { propertyInjectable } from '../../../core/lib/prop-injectable'; +import { IAlternateTarget } from '../alternate-target-configuration'; import { AssociateCloudMapServiceOptions, BaseService, BaseServiceOptions, CloudMapOptions, DeploymentControllerType, EcsTarget, IBaseService, IEcsLoadBalancerTarget, IService, LaunchType, PropagatedTagSource } from '../base/base-service'; import { fromServiceAttributes } from '../base/from-service-attributes'; import { ScalableTaskCount } from '../base/scalable-task-count'; @@ -183,7 +184,7 @@ export class ExternalService extends BaseService implements IExternalService { * Overridden method to throw error as `loadBalancerTarget` is not supported for external service */ @MethodMetadata() - public loadBalancerTarget(_options: LoadBalancerTargetOptions): IEcsLoadBalancerTarget { + public loadBalancerTarget(_options: LoadBalancerTargetOptions, _alternateOptions?: IAlternateTarget): IEcsLoadBalancerTarget { throw new ValidationError('External service cannot be attached as load balancer targets', this); } diff --git a/packages/aws-cdk-lib/aws-ecs/lib/index.ts b/packages/aws-cdk-lib/aws-ecs/lib/index.ts index c60b22e95cdb5..44f77052f914f 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/index.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/index.ts @@ -47,6 +47,9 @@ export * from './proxy-configuration/proxy-configuration'; export * from './proxy-configuration/proxy-configurations'; export * from './runtime-platform'; +export * from './deployment-lifecycle-hook-target'; +export * from './alternate-target-configuration'; + // AWS::ECS CloudFormation Resources: // export * from './ecs.generated'; diff --git a/packages/aws-cdk-lib/aws-ecs/test/alternate-target-configuration.test.ts b/packages/aws-cdk-lib/aws-ecs/test/alternate-target-configuration.test.ts new file mode 100644 index 0000000000000..801912c2abd96 --- /dev/null +++ b/packages/aws-cdk-lib/aws-ecs/test/alternate-target-configuration.test.ts @@ -0,0 +1,265 @@ +import { Template, Match } from '../../assertions'; +import * as ec2 from '../../aws-ec2'; +import * as elbv2 from '../../aws-elasticloadbalancingv2'; +import * as iam from '../../aws-iam'; +import * as cdk from '../../core'; +import { App, Stack } from '../../core'; +import * as ecs from '../lib'; + +describe('AlternateTarget', () => { + let stack: cdk.Stack; + let vpc: ec2.Vpc; + let cluster: ecs.Cluster; + let taskDefinition: ecs.FargateTaskDefinition; + let blueTargetGroup: elbv2.ApplicationTargetGroup; + let greenTargetGroup: elbv2.ApplicationTargetGroup; + let alb: elbv2.ApplicationLoadBalancer; + let listener: elbv2.ApplicationListener; + let prodRule: elbv2.ApplicationListenerRule; + let testRule: elbv2.ApplicationListenerRule; + + beforeEach(() => { + stack = new cdk.Stack(); + vpc = new ec2.Vpc(stack, 'Vpc'); + cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + portMappings: [{ containerPort: 80 }], + }); + + blueTargetGroup = new elbv2.ApplicationTargetGroup(stack, 'BlueTG', { + vpc, + port: 80, + targetType: elbv2.TargetType.IP, + }); + + greenTargetGroup = new elbv2.ApplicationTargetGroup(stack, 'GreenTG', { + vpc, + port: 80, + targetType: elbv2.TargetType.IP, + }); + + alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc }); + listener = alb.addListener('Listener', { + port: 80, + defaultAction: elbv2.ListenerAction.fixedResponse(200), + }); + + prodRule = new elbv2.ApplicationListenerRule(stack, 'ProdRule', { + listener, + priority: 1, + conditions: [elbv2.ListenerCondition.pathPatterns(['/prod'])], + action: elbv2.ListenerAction.forward([blueTargetGroup]), + }); + + testRule = new elbv2.ApplicationListenerRule(stack, 'TestRule', { + listener, + priority: 2, + conditions: [elbv2.ListenerCondition.pathPatterns(['/test'])], + action: elbv2.ListenerAction.forward([blueTargetGroup]), + }); + }); + + test('AlternateTarget creates correct configuration with production listener only', () => { + // GIVEN + const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + }); + + // WHEN + const alternateTarget = new ecs.AlternateTarget({ + alternateTargetGroup: greenTargetGroup, + productionListener: ecs.ListenerRuleConfiguration.applicationListenerRule(prodRule), + }); + + const target = service.loadBalancerTarget( + { containerName: 'web', containerPort: 80 }, + alternateTarget, + ); + target.attachToApplicationTargetGroup(blueTargetGroup); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + LoadBalancers: [ + { + ContainerName: 'web', + ContainerPort: 80, + TargetGroupArn: { + Ref: Match.stringLikeRegexp('BlueTG'), + }, + AdvancedConfiguration: { + AlternateTargetGroupArn: { + Ref: Match.stringLikeRegexp('GreenTG'), + }, + ProductionListenerRule: { + Ref: Match.stringLikeRegexp('ProdRule'), + }, + TestListenerRule: Match.absent(), + }, + }, + ], + }); + }); + + test('AlternateTarget creates correct configuration with both production and test listeners', () => { + // GIVEN + const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + }); + + // WHEN + const alternateTarget = new ecs.AlternateTarget({ + alternateTargetGroup: greenTargetGroup, + productionListener: ecs.ListenerRuleConfiguration.applicationListenerRule(prodRule), + testListener: ecs.ListenerRuleConfiguration.applicationListenerRule(testRule), + }); + + const target = service.loadBalancerTarget( + { containerName: 'web', containerPort: 80 }, + alternateTarget, + ); + target.attachToApplicationTargetGroup(blueTargetGroup); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + LoadBalancers: [ + { + ContainerName: 'web', + ContainerPort: 80, + TargetGroupArn: { + Ref: Match.stringLikeRegexp('BlueTG'), + }, + AdvancedConfiguration: { + AlternateTargetGroupArn: { + Ref: Match.stringLikeRegexp('GreenTG'), + }, + ProductionListenerRule: { + Ref: Match.stringLikeRegexp('ProdRule'), + }, + TestListenerRule: { + Ref: Match.stringLikeRegexp('TestRule'), + }, + }, + }, + ], + }); + }); + + test('AlternateTarget creates correct configuration with custom role', () => { + // GIVEN + const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + }); + + const customRole = new iam.Role(stack, 'CustomRole', { + assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonECSInfrastructureRolePolicyForLoadBalancers'), + ], + }); + + // WHEN + const alternateTarget = new ecs.AlternateTarget({ + alternateTargetGroup: greenTargetGroup, + productionListener: ecs.ListenerRuleConfiguration.applicationListenerRule(prodRule), + role: customRole, + }); + + const target = service.loadBalancerTarget( + { containerName: 'web', containerPort: 80 }, + alternateTarget, + ); + target.attachToApplicationTargetGroup(blueTargetGroup); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + LoadBalancers: [ + { + ContainerName: 'web', + ContainerPort: 80, + TargetGroupArn: { + Ref: Match.stringLikeRegexp('BlueTG'), + }, + AdvancedConfiguration: { + AlternateTargetGroupArn: { + Ref: Match.stringLikeRegexp('GreenTG'), + }, + ProductionListenerRule: { + Ref: Match.stringLikeRegexp('ProdRule'), + }, + RoleArn: { + 'Fn::GetAtt': [ + 'CustomRole6D8E6809', + 'Arn', + ], + }, + }, + }, + ], + }); + }); + + test('NetworkListenerConfiguration works with NLB listeners', () => { + // GIVEN + const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + }); + + const nlb = new elbv2.NetworkLoadBalancer(stack, 'NLB', { vpc }); + const nlbListener = nlb.addListener('NlbListener', { port: 80 }); + + const nlbBlueTargetGroup = new elbv2.NetworkTargetGroup(stack, 'NlbBlueTG', { + vpc, + port: 80, + targetType: elbv2.TargetType.IP, + }); + + const nlbGreenTargetGroup = new elbv2.NetworkTargetGroup(stack, 'NlbGreenTG', { + vpc, + port: 80, + targetType: elbv2.TargetType.IP, + }); + + nlbListener.addAction('DefaultAction', { + action: elbv2.NetworkListenerAction.forward([nlbBlueTargetGroup]), + }); + + // WHEN + const alternateTarget = new ecs.AlternateTarget({ + alternateTargetGroup: nlbGreenTargetGroup, + productionListener: ecs.ListenerRuleConfiguration.networkListener(nlbListener), + }); + + const target = service.loadBalancerTarget( + { containerName: 'web', containerPort: 80 }, + alternateTarget, + ); + target.attachToNetworkTargetGroup(nlbBlueTargetGroup); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + LoadBalancers: [ + { + ContainerName: 'web', + ContainerPort: 80, + TargetGroupArn: { + Ref: Match.stringLikeRegexp('NlbBlueTG'), + }, + AdvancedConfiguration: { + AlternateTargetGroupArn: { + Ref: Match.stringLikeRegexp('NlbGreenTG'), + }, + ProductionListenerRule: { + Ref: Match.stringLikeRegexp('NlbListener'), + }, + }, + }, + ], + }); + }); +}); diff --git a/packages/aws-cdk-lib/aws-ecs/test/base-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/base-service.test.ts index ab2c65a7df87b..b3f520bdc8c8c 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/base-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/base-service.test.ts @@ -1,7 +1,9 @@ import { Template, Match } from '../../assertions'; import * as ec2 from '../../aws-ec2'; +import * as elbv2 from '../../aws-elasticloadbalancingv2'; import * as iam from '../../aws-iam'; import * as kms from '../../aws-kms'; +import * as lambda from '../../aws-lambda'; import * as cdk from '../../core'; import { App, Stack } from '../../core'; import * as cxapi from '../../cx-api'; @@ -398,7 +400,7 @@ test.each([ new ecs.FargateService(stack, 'FargateService', { cluster, taskDefinition, - circuitBreaker: circuitBreaker ? { } : undefined, + circuitBreaker: circuitBreaker ? {} : undefined, }); // THEN @@ -444,3 +446,215 @@ test.each([ }, }); }); + +describe('Blue/Green Deployment', () => { + let stack: cdk.Stack; + let vpc: ec2.Vpc; + let cluster: ecs.Cluster; + let taskDefinition: ecs.FargateTaskDefinition; + + beforeEach(() => { + stack = new cdk.Stack(); + vpc = new ec2.Vpc(stack, 'Vpc'); + cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + portMappings: [{ containerPort: 80 }], + }); + }); + + test('isUsingECSDeploymentController returns true when no deployment controller is specified', () => { + // GIVEN + const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + }); + + // THEN + expect(service.isUsingECSDeploymentController()).toBe(true); + }); + + test('isUsingECSDeploymentController returns true when ECS deployment controller is specified', () => { + // GIVEN + const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + deploymentController: { + type: ecs.DeploymentControllerType.ECS, + }, + }); + + // THEN + expect(service.isUsingECSDeploymentController()).toBe(true); + }); + + test('isUsingECSDeploymentController returns false when CODE_DEPLOY deployment controller is specified', () => { + // GIVEN + const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + deploymentController: { + type: ecs.DeploymentControllerType.CODE_DEPLOY, + }, + }); + + // THEN + expect(service.isUsingECSDeploymentController()).toBe(false); + }); + + test('isUsingECSDeploymentController returns false when EXTERNAL deployment controller is specified', () => { + // GIVEN + const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + deploymentController: { + type: ecs.DeploymentControllerType.EXTERNAL, + }, + }); + + // THEN + expect(service.isUsingECSDeploymentController()).toBe(false); + }); + + test('enableBlueGreenDeployment throws when not using ECS deployment controller', () => { + // GIVEN + const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + deploymentController: { + type: ecs.DeploymentControllerType.CODE_DEPLOY, + }, + }); + + const blueTargetGroup = new elbv2.ApplicationTargetGroup(stack, 'BlueTargetGroup', { + vpc, + port: 80, + }); + + const greenTargetGroup = new elbv2.ApplicationTargetGroup(stack, 'GreenTargetGroup', { + vpc, + port: 80, + }); + + const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc }); + const listener = alb.addListener('Listener', { + port: 80, + defaultAction: elbv2.ListenerAction.fixedResponse(200), + }); + + const rule = new elbv2.ApplicationListenerRule(stack, 'Rule', { + listener, + priority: 1, + conditions: [elbv2.ListenerCondition.pathPatterns(['/'])], + action: elbv2.ListenerAction.forward([blueTargetGroup]), + }); + + const alternateTarget = new ecs.AlternateTarget({ + alternateTargetGroup: greenTargetGroup, + productionListener: ecs.ListenerRuleConfiguration.applicationListenerRule(rule), + }); + + // THEN + expect(() => { + service.enableBlueGreenDeployment(alternateTarget); + }).toThrow(/Blue-green deployment requires the ECS deployment controller/); + }); + + test('enableBlueGreenDeployment configures alternate target when provided', () => { + // GIVEN + const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + }); + + const blueTargetGroup = new elbv2.ApplicationTargetGroup(stack, 'BlueTargetGroup', { + vpc, + port: 80, + }); + + const greenTargetGroup = new elbv2.ApplicationTargetGroup(stack, 'GreenTargetGroup', { + vpc, + port: 80, + }); + + const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc }); + const listener = alb.addListener('Listener', { + port: 80, + defaultAction: elbv2.ListenerAction.fixedResponse(200), + }); + + const rule = new elbv2.ApplicationListenerRule(stack, 'Rule', { + listener, + priority: 1, + conditions: [elbv2.ListenerCondition.pathPatterns(['/'])], + action: elbv2.ListenerAction.forward([blueTargetGroup]), + }); + + const alternateTarget = new ecs.AlternateTarget({ + alternateTargetGroup: greenTargetGroup, + productionListener: ecs.ListenerRuleConfiguration.applicationListenerRule(rule), + }); + + // Register the blue target group first + const target = service.loadBalancerTarget({ + containerName: 'web', + containerPort: 80, + }, alternateTarget); + target.attachToApplicationTargetGroup(blueTargetGroup); + + const lambdaFunction = new lambda.Function(stack, 'TestFunction', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async () => { return { hookStatus: "SUCCEEDED" }; }'), + }); + + const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, { + lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], + }); + + // WHEN + service.enableBlueGreenDeployment(alternateTarget, [hookTarget]); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DeploymentConfiguration: { + Strategy: 'BLUE_GREEN', + LifecycleHooks: [ + { + LifecycleStages: ['PRE_SCALE_UP'], + HookTargetArn: { + 'Fn::GetAtt': [ + Match.stringLikeRegexp('TestFunction'), + 'Arn', + ], + }, + }, + ], + }, + LoadBalancers: [ + { + ContainerName: 'web', + ContainerPort: 80, + TargetGroupArn: { + Ref: Match.stringLikeRegexp('BlueTargetGroup'), + }, + AdvancedConfiguration: { + AlternateTargetGroupArn: { + Ref: Match.stringLikeRegexp('GreenTargetGroup'), + }, + ProductionListenerRule: { + Ref: Match.stringLikeRegexp('Rule'), + }, + RoleArn: { + 'Fn::GetAtt': [ + Match.stringLikeRegexp('FargateServiceLBAlternateOptionsRole'), + 'Arn', + ], + }, + }, + }, + ], + }); + }); +}); diff --git a/packages/aws-cdk-lib/aws-ecs/test/deployment-lifecycle-hook-target.test.ts b/packages/aws-cdk-lib/aws-ecs/test/deployment-lifecycle-hook-target.test.ts new file mode 100644 index 0000000000000..c463bc7dec23d --- /dev/null +++ b/packages/aws-cdk-lib/aws-ecs/test/deployment-lifecycle-hook-target.test.ts @@ -0,0 +1,316 @@ +import { Template, Match } from '../../assertions'; +import * as ec2 from '../../aws-ec2'; +import * as iam from '../../aws-iam'; +import * as lambda from '../../aws-lambda'; +import * as cdk from '../../core'; +import { App, Stack } from '../../core'; +import * as ecs from '../lib'; + +describe('DeploymentLifecycleHookTarget', () => { + let stack: cdk.Stack; + let vpc: ec2.Vpc; + let cluster: ecs.Cluster; + let taskDefinition: ecs.FargateTaskDefinition; + let lambdaFunction: lambda.Function; + + beforeEach(() => { + stack = new cdk.Stack(); + vpc = new ec2.Vpc(stack, 'Vpc'); + cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + portMappings: [{ containerPort: 80 }], + }); + + lambdaFunction = new lambda.Function(stack, 'TestFunction', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async () => { return { hookStatus: "SUCCEEDED" }; }'), + }); + }); + + test('DeploymentLifecycleLambdaTarget creates default role when none provided', () => { + // GIVEN + const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + }); + + // WHEN + const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, { + lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], + }); + service.addLifecycleHook(hookTarget); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'ecs.amazonaws.com', + }, + }, + ], + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'lambda:InvokeFunction', + Effect: 'Allow', + Resource: [ + { + 'Fn::GetAtt': [ + Match.stringLikeRegexp('TestFunction'), + 'Arn', + ], + }, + { + 'Fn::Join': [ + '', + [ + { + 'Fn::GetAtt': [ + Match.stringLikeRegexp('TestFunction'), + 'Arn', + ], + }, + ':*', + ], + ], + }, + ], + }, + ], + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DeploymentConfiguration: { + LifecycleHooks: [ + { + LifecycleStages: ['PRE_SCALE_UP'], + HookTargetArn: { + 'Fn::GetAtt': [ + Match.stringLikeRegexp('TestFunction'), + 'Arn', + ], + }, + RoleArn: { + 'Fn::GetAtt': [ + Match.stringLikeRegexp('Role'), + 'Arn', + ], + }, + }, + ], + }, + }); + }); + + test('DeploymentLifecycleLambdaTarget uses provided role', () => { + // GIVEN + const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + }); + + const customRole = new iam.Role(stack, 'CustomRole', { + assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'), + }); + lambdaFunction.grantInvoke(customRole); + + // WHEN + const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, { + lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], + role: customRole, + }); + service.addLifecycleHook(hookTarget); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DeploymentConfiguration: { + LifecycleHooks: [ + { + LifecycleStages: ['PRE_SCALE_UP'], + HookTargetArn: { + 'Fn::GetAtt': [ + Match.stringLikeRegexp('TestFunction'), + 'Arn', + ], + }, + RoleArn: { + 'Fn::GetAtt': [ + Match.stringLikeRegexp('CustomRole'), + 'Arn', + ], + }, + }, + ], + }, + }); + }); + + test('DeploymentLifecycleLambdaTarget supports multiple lifecycle stages', () => { + // GIVEN + const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + }); + + // WHEN + const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, { + lifecycleStages: [ + ecs.DeploymentLifecycleStage.PRE_SCALE_UP, + ecs.DeploymentLifecycleStage.POST_SCALE_UP, + ecs.DeploymentLifecycleStage.TEST_TRAFFIC_SHIFT, + ], + }); + service.addLifecycleHook(hookTarget); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DeploymentConfiguration: { + LifecycleHooks: [ + { + LifecycleStages: ['PRE_SCALE_UP', 'POST_SCALE_UP', 'TEST_TRAFFIC_SHIFT'], + HookTargetArn: { + 'Fn::GetAtt': [ + Match.stringLikeRegexp('TestFunction'), + 'Arn', + ], + }, + }, + ], + }, + }); + }); + + test('addLifecycleHook throws when not using ECS deployment controller', () => { + // GIVEN + const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + deploymentController: { + type: ecs.DeploymentControllerType.CODE_DEPLOY, + }, + }); + + const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, { + lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], + }); + + // THEN + expect(() => { + service.addLifecycleHook(hookTarget); + }).toThrow(/Deployment lifecycle hooks requires the ECS deployment controller/); + }); + + test('multiple lifecycle hooks can be added to a service', () => { + // GIVEN + const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + }); + + const secondLambda = new lambda.Function(stack, 'SecondFunction', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async () => { return { hookStatus: "SUCCEEDED" }; }'), + }); + + // WHEN + const firstHook = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, { + lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], + }); + + const secondHook = new ecs.DeploymentLifecycleLambdaTarget(secondLambda, { + lifecycleStages: [ecs.DeploymentLifecycleStage.POST_SCALE_UP], + }); + + service.addLifecycleHook(firstHook); + service.addLifecycleHook(secondHook); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DeploymentConfiguration: { + LifecycleHooks: Match.arrayWith([ + Match.objectLike({ + LifecycleStages: ['PRE_SCALE_UP'], + HookTargetArn: { + 'Fn::GetAtt': [ + Match.stringLikeRegexp('TestFunction'), + 'Arn', + ], + }, + }), + Match.objectLike({ + LifecycleStages: ['POST_SCALE_UP'], + HookTargetArn: { + 'Fn::GetAtt': [ + Match.stringLikeRegexp('SecondFunction'), + 'Arn', + ], + }, + }), + ]), + }, + }); + }); + + test('lifecycle hooks can be added during service creation', () => { + // GIVEN + const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, { + lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], + }); + + // WHEN + const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + lifecycleHooks: [hookTarget], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DeploymentConfiguration: { + LifecycleHooks: [ + { + LifecycleStages: ['PRE_SCALE_UP'], + HookTargetArn: { + 'Fn::GetAtt': [ + Match.stringLikeRegexp('TestFunction'), + 'Arn', + ], + }, + }, + ], + }, + }); + }); + + test('lifecycle hooks cannot be added during service creation with non-ECS deployment controller', () => { + // GIVEN + const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, { + lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], + }); + + // THEN + expect(() => { + new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + deploymentController: { + type: ecs.DeploymentControllerType.CODE_DEPLOY, + }, + lifecycleHooks: [hookTarget], + }); + }).toThrow(/Deployment lifecycle hooks requires the ECS deployment controller/); + }); +}); From 3be5d33d713d07c48117a860d1e436c51a63b2a7 Mon Sep 17 00:00:00 2001 From: Kishan Gajjar Date: Mon, 28 Jul 2025 13:44:19 -0400 Subject: [PATCH 2/8] address CR feedback --- .../aws-ecs-blue-green-deployment.assets.json | 6 ++--- ...ws-ecs-blue-green-deployment.template.json | 22 ++++------------- .../manifest.json | 19 +++++++-------- .../tree.json | 2 +- .../integ.blue-green-deployment-strategy.ts | 2 +- .../lib/alternate-target-configuration.ts | 11 ++++----- .../aws-ecs/lib/base/base-service.ts | 17 +++++-------- .../lib/deployment-lifecycle-hook-target.ts | 24 +++++++++++++------ .../aws-ecs/test/base-service.test.ts | 2 +- .../deployment-lifecycle-hook-target.test.ts | 16 ++++++------- 10 files changed, 55 insertions(+), 66 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/aws-ecs-blue-green-deployment.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/aws-ecs-blue-green-deployment.assets.json index a014d4b1734fc..b2f5d1d1de5eb 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/aws-ecs-blue-green-deployment.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/aws-ecs-blue-green-deployment.assets.json @@ -1,16 +1,16 @@ { "version": "45.0.0", "files": { - "be28df9e09f080f9ea9aa5e16d969648154dd356a673e352c382094ea3f269a2": { + "3f2de3687de753e01df8af1f3f5db3418f528a660a93dfdd7cb90e7ed75c213f": { "displayName": "aws-ecs-blue-green-deployment Template", "source": { "path": "aws-ecs-blue-green-deployment.template.json", "packaging": "file" }, "destinations": { - "current_account-current_region-ffebb95d": { + "current_account-current_region-289748b6": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "be28df9e09f080f9ea9aa5e16d969648154dd356a673e352c382094ea3f269a2.json", + "objectKey": "3f2de3687de753e01df8af1f3f5db3418f528a660a93dfdd7cb90e7ed75c213f.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/aws-ecs-blue-green-deployment.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/aws-ecs-blue-green-deployment.template.json index d69d625bd2ee2..cc03e2ba30041 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/aws-ecs-blue-green-deployment.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/aws-ecs-blue-green-deployment.template.json @@ -753,7 +753,7 @@ ], "RoleArn": { "Fn::GetAtt": [ - "ServiceHook0RoleACE68A7A", + "ServicePreScaleUpRoleD002A553", "Arn" ] } @@ -846,18 +846,6 @@ ":iam::aws:policy/AmazonECSInfrastructureRolePolicyForLoadBalancers" ] ] - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AmazonEC2ContainerServiceRole" - ] - ] } ] }, @@ -865,7 +853,7 @@ "TaskDefTaskRole1EDB4A67" ] }, - "ServiceHook0RoleACE68A7A": { + "ServicePreScaleUpRoleD002A553": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -882,7 +870,7 @@ } } }, - "ServiceHook0RoleDefaultPolicyD7C31604": { + "ServicePreScaleUpRoleDefaultPolicyF1032E86": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { @@ -916,10 +904,10 @@ ], "Version": "2012-10-17" }, - "PolicyName": "ServiceHook0RoleDefaultPolicyD7C31604", + "PolicyName": "ServicePreScaleUpRoleDefaultPolicyF1032E86", "Roles": [ { - "Ref": "ServiceHook0RoleACE68A7A" + "Ref": "ServicePreScaleUpRoleD002A553" } ] } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/manifest.json index d8289d9121bea..8bfa7b161f7db 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/be28df9e09f080f9ea9aa5e16d969648154dd356a673e352c382094ea3f269a2.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/3f2de3687de753e01df8af1f3f5db3418f528a660a93dfdd7cb90e7ed75c213f.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -628,9 +628,6 @@ "assumeRoleAction": "*" }, "managedPolicies": [ - { - "managedPolicyArn": "*" - }, { "managedPolicyArn": "*" } @@ -650,7 +647,7 @@ "data": "ServiceLBAlternateOptionsRole06C91D94" } ], - "/aws-ecs-blue-green-deployment/Service/Hook0Role": [ + "/aws-ecs-blue-green-deployment/Service/PreScaleUpRole": [ { "type": "aws:cdk:analytics:construct", "data": { @@ -685,19 +682,19 @@ } } ], - "/aws-ecs-blue-green-deployment/Service/Hook0Role/ImportHook0Role": [ + "/aws-ecs-blue-green-deployment/Service/PreScaleUpRole/ImportPreScaleUpRole": [ { "type": "aws:cdk:analytics:construct", "data": "*" } ], - "/aws-ecs-blue-green-deployment/Service/Hook0Role/Resource": [ + "/aws-ecs-blue-green-deployment/Service/PreScaleUpRole/Resource": [ { "type": "aws:cdk:logicalId", - "data": "ServiceHook0RoleACE68A7A" + "data": "ServicePreScaleUpRoleD002A553" } ], - "/aws-ecs-blue-green-deployment/Service/Hook0Role/DefaultPolicy": [ + "/aws-ecs-blue-green-deployment/Service/PreScaleUpRole/DefaultPolicy": [ { "type": "aws:cdk:analytics:construct", "data": "*" @@ -727,10 +724,10 @@ } } ], - "/aws-ecs-blue-green-deployment/Service/Hook0Role/DefaultPolicy/Resource": [ + "/aws-ecs-blue-green-deployment/Service/PreScaleUpRole/DefaultPolicy/Resource": [ { "type": "aws:cdk:logicalId", - "data": "ServiceHook0RoleDefaultPolicyD7C31604" + "data": "ServicePreScaleUpRoleDefaultPolicyF1032E86" } ], "/aws-ecs-blue-green-deployment/BootstrapVersion": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/tree.json index cfb6c890f7554..c6da4e094e80f 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.js.snapshot/tree.json @@ -1 +1 @@ -{"version":"tree-0.1","tree":{"id":"App","path":"","constructInfo":{"fqn":"aws-cdk-lib.App","version":"0.0.0"},"children":{"aws-ecs-blue-green-deployment":{"id":"aws-ecs-blue-green-deployment","path":"aws-ecs-blue-green-deployment","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"Vpc":{"id":"Vpc","path":"aws-ecs-blue-green-deployment/Vpc","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.Vpc","version":"0.0.0","metadata":[{"maxAzs":"*","restrictDefaultSecurityGroup":false}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/Vpc/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnVPC","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::VPC","aws:cdk:cloudformation:props":{"cidrBlock":"10.0.0.0/16","enableDnsHostnames":true,"enableDnsSupport":true,"instanceTenancy":"default","tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc"}]}}},"PublicSubnet1":{"id":"PublicSubnet1","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PublicSubnet","version":"0.0.0","metadata":[{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":true,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":true,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{},{"addNatGateway":["*"]}]},"children":{"Subnet":{"id":"Subnet","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[0,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.0.0/18","mapPublicIpOnLaunch":true,"tags":[{"key":"aws-cdk:subnet-name","value":"Public"},{"key":"aws-cdk:subnet-type","value":"Public"},{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"Acl":{"id":"Acl","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":[]}},"RouteTable":{"id":"RouteTable","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VpcPublicSubnet1RouteTable6C95E38E"},"subnetId":{"Ref":"VpcPublicSubnet1Subnet5C2D37C4"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","gatewayId":{"Ref":"VpcIGWD7BA715C"},"routeTableId":{"Ref":"VpcPublicSubnet1RouteTable6C95E38E"}}}},"EIP":{"id":"EIP","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/EIP","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnEIP","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::EIP","aws:cdk:cloudformation:props":{"domain":"vpc","tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1"}]}}},"NATGateway":{"id":"NATGateway","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/NATGateway","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnNatGateway","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::NatGateway","aws:cdk:cloudformation:props":{"allocationId":{"Fn::GetAtt":["VpcPublicSubnet1EIPD7E02669","AllocationId"]},"subnetId":{"Ref":"VpcPublicSubnet1Subnet5C2D37C4"},"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1"}]}}}}},"PublicSubnet2":{"id":"PublicSubnet2","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PublicSubnet","version":"0.0.0","metadata":[{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":true,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":true,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{},{"addNatGateway":["*"]}]},"children":{"Subnet":{"id":"Subnet","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[1,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.64.0/18","mapPublicIpOnLaunch":true,"tags":[{"key":"aws-cdk:subnet-name","value":"Public"},{"key":"aws-cdk:subnet-type","value":"Public"},{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"Acl":{"id":"Acl","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":[]}},"RouteTable":{"id":"RouteTable","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VpcPublicSubnet2RouteTable94F7E489"},"subnetId":{"Ref":"VpcPublicSubnet2Subnet691E08A3"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","gatewayId":{"Ref":"VpcIGWD7BA715C"},"routeTableId":{"Ref":"VpcPublicSubnet2RouteTable94F7E489"}}}},"EIP":{"id":"EIP","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/EIP","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnEIP","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::EIP","aws:cdk:cloudformation:props":{"domain":"vpc","tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2"}]}}},"NATGateway":{"id":"NATGateway","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/NATGateway","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnNatGateway","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::NatGateway","aws:cdk:cloudformation:props":{"allocationId":{"Fn::GetAtt":["VpcPublicSubnet2EIP3C605A87","AllocationId"]},"subnetId":{"Ref":"VpcPublicSubnet2Subnet691E08A3"},"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2"}]}}}}},"PrivateSubnet1":{"id":"PrivateSubnet1","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PrivateSubnet","version":"0.0.0","metadata":[{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":false,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":false,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{}]},"children":{"Subnet":{"id":"Subnet","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[0,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.128.0/18","mapPublicIpOnLaunch":false,"tags":[{"key":"aws-cdk:subnet-name","value":"Private"},{"key":"aws-cdk:subnet-type","value":"Private"},{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"Acl":{"id":"Acl","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":[]}},"RouteTable":{"id":"RouteTable","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VpcPrivateSubnet1RouteTableB2C5B500"},"subnetId":{"Ref":"VpcPrivateSubnet1Subnet536B997A"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","natGatewayId":{"Ref":"VpcPublicSubnet1NATGateway4D7517AA"},"routeTableId":{"Ref":"VpcPrivateSubnet1RouteTableB2C5B500"}}}}}},"PrivateSubnet2":{"id":"PrivateSubnet2","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PrivateSubnet","version":"0.0.0","metadata":[{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":false,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":false,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{}]},"children":{"Subnet":{"id":"Subnet","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[1,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.192.0/18","mapPublicIpOnLaunch":false,"tags":[{"key":"aws-cdk:subnet-name","value":"Private"},{"key":"aws-cdk:subnet-type","value":"Private"},{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"Acl":{"id":"Acl","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":[]}},"RouteTable":{"id":"RouteTable","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VpcPrivateSubnet2RouteTableA678073B"},"subnetId":{"Ref":"VpcPrivateSubnet2Subnet3788AAA1"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","natGatewayId":{"Ref":"VpcPublicSubnet2NATGateway9182C01D"},"routeTableId":{"Ref":"VpcPrivateSubnet2RouteTableA678073B"}}}}}},"IGW":{"id":"IGW","path":"aws-ecs-blue-green-deployment/Vpc/IGW","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnInternetGateway","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::InternetGateway","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc"}]}}},"VPCGW":{"id":"VPCGW","path":"aws-ecs-blue-green-deployment/Vpc/VPCGW","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnVPCGatewayAttachment","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::VPCGatewayAttachment","aws:cdk:cloudformation:props":{"internetGatewayId":{"Ref":"VpcIGWD7BA715C"},"vpcId":{"Ref":"Vpc8378EB38"}}}}}},"FargateCluster":{"id":"FargateCluster","path":"aws-ecs-blue-green-deployment/FargateCluster","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.Cluster","version":"0.0.0","metadata":[{"vpc":"*","defaultCloudMapNamespace":{"name":"*","useForServiceConnect":true}},{"addDefaultCloudMapNamespace":[{"name":"*","useForServiceConnect":true}]}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/FargateCluster/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.CfnCluster","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ECS::Cluster","aws:cdk:cloudformation:props":{"serviceConnectDefaults":{"namespace":{"Fn::GetAtt":["FargateClusterDefaultServiceDiscoveryNamespace04381E1E","Arn"]}}}}},"DefaultServiceDiscoveryNamespace":{"id":"DefaultServiceDiscoveryNamespace","path":"aws-ecs-blue-green-deployment/FargateCluster/DefaultServiceDiscoveryNamespace","constructInfo":{"fqn":"aws-cdk-lib.aws_servicediscovery.PrivateDnsNamespace","version":"0.0.0","metadata":[{"name":"*","vpc":"*"}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/FargateCluster/DefaultServiceDiscoveryNamespace/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_servicediscovery.CfnPrivateDnsNamespace","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ServiceDiscovery::PrivateDnsNamespace","aws:cdk:cloudformation:props":{"name":"bluegreendeployment.com","vpc":{"Ref":"Vpc8378EB38"}}}}}}}},"BlueTG":{"id":"BlueTG","path":"aws-ecs-blue-green-deployment/BlueTG","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationTargetGroup","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/BlueTG/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnTargetGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::TargetGroup","aws:cdk:cloudformation:props":{"healthCheckPath":"/","matcher":{"httpCode":"200"},"port":80,"protocol":"HTTP","targetGroupAttributes":[{"key":"stickiness.enabled","value":"false"}],"targetType":"ip","vpcId":{"Ref":"Vpc8378EB38"}}}}}},"GreenTG":{"id":"GreenTG","path":"aws-ecs-blue-green-deployment/GreenTG","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationTargetGroup","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/GreenTG/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnTargetGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::TargetGroup","aws:cdk:cloudformation:props":{"healthCheckPath":"/","matcher":{"httpCode":"200"},"port":80,"protocol":"HTTP","targetGroupAttributes":[{"key":"stickiness.enabled","value":"false"}],"targetType":"ip","vpcId":{"Ref":"Vpc8378EB38"}}}}}},"LBSecurityGroup":{"id":"LBSecurityGroup","path":"aws-ecs-blue-green-deployment/LBSecurityGroup","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.SecurityGroup","version":"0.0.0","metadata":[{"vpc":"*","allowAllOutbound":true},{"addIngressRule":[{"canInlineRule":true,"connections":"*","uniqueId":"*"},{}]},{"addIngressRule":[{"canInlineRule":true,"connections":"*","uniqueId":"*"},{},"*",false]},{"addEgressRule":["*",{},"*",true]}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/LBSecurityGroup/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSecurityGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SecurityGroup","aws:cdk:cloudformation:props":{"groupDescription":"aws-ecs-blue-green-deployment/LBSecurityGroup","securityGroupEgress":[{"cidrIp":"0.0.0.0/0","description":"Allow all outbound traffic by default","ipProtocol":"-1"}],"securityGroupIngress":[{"cidrIp":"0.0.0.0/0","ipProtocol":"tcp","fromPort":80,"toPort":80,"description":"from 0.0.0.0/0:80"}],"vpcId":{"Ref":"Vpc8378EB38"}}}}}},"ECSSecurityGroup":{"id":"ECSSecurityGroup","path":"aws-ecs-blue-green-deployment/ECSSecurityGroup","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.SecurityGroup","version":"0.0.0","metadata":[{"vpc":"*","allowAllOutbound":true},{"addIngressRule":["*",{}]},{"addIngressRule":["*",{},"*",false]}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/ECSSecurityGroup/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSecurityGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SecurityGroup","aws:cdk:cloudformation:props":{"groupDescription":"aws-ecs-blue-green-deployment/ECSSecurityGroup","securityGroupEgress":[{"cidrIp":"0.0.0.0/0","description":"Allow all outbound traffic by default","ipProtocol":"-1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"from awsecsbluegreendeploymentLBSecurityGroup67E4A783:80":{"id":"from awsecsbluegreendeploymentLBSecurityGroup67E4A783:80","path":"aws-ecs-blue-green-deployment/ECSSecurityGroup/from awsecsbluegreendeploymentLBSecurityGroup67E4A783:80","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSecurityGroupIngress","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SecurityGroupIngress","aws:cdk:cloudformation:props":{"description":"from awsecsbluegreendeploymentLBSecurityGroup67E4A783:80","fromPort":80,"groupId":{"Fn::GetAtt":["ECSSecurityGroupA14DBE7D","GroupId"]},"ipProtocol":"tcp","sourceSecurityGroupId":{"Fn::GetAtt":["LBSecurityGroup4464B654","GroupId"]},"toPort":80}}}}},"ALB":{"id":"ALB","path":"aws-ecs-blue-green-deployment/ALB","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationLoadBalancer","version":"0.0.0","metadata":["*","*"]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/ALB/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnLoadBalancer","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::LoadBalancer","aws:cdk:cloudformation:props":{"loadBalancerAttributes":[{"key":"deletion_protection.enabled","value":"false"},{"key":"idle_timeout.timeout_seconds","value":"60"}],"scheme":"internet-facing","securityGroups":[{"Fn::GetAtt":["LBSecurityGroup4464B654","GroupId"]}],"subnets":[{"Ref":"VpcPublicSubnet1Subnet5C2D37C4"},{"Ref":"VpcPublicSubnet2Subnet691E08A3"}],"type":"application"}}},"ALBListenerHTTP":{"id":"ALBListenerHTTP","path":"aws-ecs-blue-green-deployment/ALB/ALBListenerHTTP","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationListener","version":"0.0.0","metadata":["*","*"]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/ALB/ALBListenerHTTP/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnListener","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::Listener","aws:cdk:cloudformation:props":{"defaultActions":[{"type":"fixed-response","fixedResponseConfig":{"statusCode":"404"}}],"loadBalancerArn":{"Ref":"ALBAEE750D2"},"port":80,"protocol":"HTTP"}}}}}}},"ALBProductionListenerRule":{"id":"ALBProductionListenerRule","path":"aws-ecs-blue-green-deployment/ALBProductionListenerRule","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationListenerRule","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/ALBProductionListenerRule/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnListenerRule","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::ListenerRule","aws:cdk:cloudformation:props":{"actions":[{"type":"forward","forwardConfig":{"targetGroups":[{"targetGroupArn":{"Ref":"BlueTG7C058B2C"},"weight":100},{"targetGroupArn":{"Ref":"GreenTG71A27F2F"},"weight":0}]}}],"conditions":[{"field":"path-pattern","pathPatternConfig":{"values":["/*"]}}],"listenerArn":{"Ref":"ALBALBListenerHTTP413250C3"},"priority":1}}}}},"LambdaHook":{"id":"LambdaHook","path":"aws-ecs-blue-green-deployment/LambdaHook","constructInfo":{"fqn":"aws-cdk-lib.aws_lambda.Function","version":"0.0.0","metadata":[{"handler":"*","runtime":"*","code":"*"}]},"children":{"ServiceRole":{"id":"ServiceRole","path":"aws-ecs-blue-green-deployment/LambdaHook/ServiceRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"},"managedPolicies":[{"managedPolicyArn":"*"}]}]},"children":{"ImportServiceRole":{"id":"ImportServiceRole","path":"aws-ecs-blue-green-deployment/LambdaHook/ServiceRole/ImportServiceRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/LambdaHook/ServiceRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"}}],"Version":"2012-10-17"},"managedPolicyArns":[{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]]}]}}}}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/LambdaHook/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_lambda.CfnFunction","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Lambda::Function","aws:cdk:cloudformation:props":{"code":{"zipFile":"exports.handler = async (event, context) => {\n console.log('Event received:', JSON.stringify(event));\n return { hookStatus: 'SUCCEEDED' }; \n };"},"handler":"index.handler","role":{"Fn::GetAtt":["LambdaHookServiceRole9AAAD33B","Arn"]},"runtime":"nodejs22.x"}}},"LogGroup":{"id":"LogGroup","path":"aws-ecs-blue-green-deployment/LambdaHook/LogGroup","constructInfo":{"fqn":"aws-cdk-lib.aws_logs.LogGroup","version":"0.0.0","metadata":[{"logGroupName":"*"}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/LambdaHook/LogGroup/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_logs.CfnLogGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Logs::LogGroup","aws:cdk:cloudformation:props":{"logGroupName":{"Fn::Join":["",["/aws/lambda/",{"Ref":"LambdaHookBF1BC8B4"}]]},"retentionInDays":731}}}}}}},"TaskDef":{"id":"TaskDef","path":"aws-ecs-blue-green-deployment/TaskDef","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.FargateTaskDefinition","version":"0.0.0","metadata":["*","*","*","*"]},"children":{"TaskRole":{"id":"TaskRole","path":"aws-ecs-blue-green-deployment/TaskDef/TaskRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"}}]},"children":{"ImportTaskRole":{"id":"ImportTaskRole","path":"aws-ecs-blue-green-deployment/TaskDef/TaskRole/ImportTaskRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/TaskDef/TaskRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"ecs-tasks.amazonaws.com"}}],"Version":"2012-10-17"}}}}}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/TaskDef/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.CfnTaskDefinition","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ECS::TaskDefinition","aws:cdk:cloudformation:props":{"containerDefinitions":[{"essential":true,"image":"public.ecr.aws/nginx/nginx:latest","name":"nginx","portMappings":[{"containerPort":80,"protocol":"tcp","appProtocol":"http","name":"api"}]}],"cpu":"256","family":"awsecsbluegreendeploymentTaskDef51D80572","memory":"512","networkMode":"awsvpc","requiresCompatibilities":["FARGATE"],"taskRoleArn":{"Fn::GetAtt":["TaskDefTaskRole1EDB4A67","Arn"]}}}},"container":{"id":"container","path":"aws-ecs-blue-green-deployment/TaskDef/container","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.ContainerDefinition","version":"0.0.0"}}}},"Service":{"id":"Service","path":"aws-ecs-blue-green-deployment/Service","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.FargateService","version":"0.0.0","metadata":["*"]},"children":{"Service":{"id":"Service","path":"aws-ecs-blue-green-deployment/Service/Service","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.CfnService","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ECS::Service","aws:cdk:cloudformation:props":{"cluster":{"Ref":"FargateCluster7CCD5F93"},"deploymentConfiguration":{"maximumPercent":200,"minimumHealthyPercent":50,"strategy":"BLUE_GREEN","lifecycleHooks":[{"hookTargetArn":{"Fn::GetAtt":["LambdaHookBF1BC8B4","Arn"]},"roleArn":{"Fn::GetAtt":["ServiceHook0RoleACE68A7A","Arn"]},"lifecycleStages":["PRE_SCALE_UP"]}]},"enableEcsManagedTags":false,"healthCheckGracePeriodSeconds":60,"launchType":"FARGATE","loadBalancers":[{"targetGroupArn":{"Ref":"BlueTG7C058B2C"},"containerName":"nginx","containerPort":80,"advancedConfiguration":{"alternateTargetGroupArn":{"Ref":"GreenTG71A27F2F"},"roleArn":{"Fn::GetAtt":["ServiceLBAlternateOptionsRole06C91D94","Arn"]},"productionListenerRule":{"Ref":"ALBProductionListenerRule243D0687"}}}],"networkConfiguration":{"awsvpcConfiguration":{"assignPublicIp":"DISABLED","subnets":[{"Ref":"VpcPrivateSubnet1Subnet536B997A"},{"Ref":"VpcPrivateSubnet2Subnet3788AAA1"}],"securityGroups":[{"Fn::GetAtt":["ECSSecurityGroupA14DBE7D","GroupId"]}]}},"taskDefinition":{"Ref":"TaskDef54694570"}}}},"LBAlternateOptionsRole":{"id":"LBAlternateOptionsRole","path":"aws-ecs-blue-green-deployment/Service/LBAlternateOptionsRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"},"managedPolicies":[{"managedPolicyArn":"*"},{"managedPolicyArn":"*"}]}]},"children":{"ImportLBAlternateOptionsRole":{"id":"ImportLBAlternateOptionsRole","path":"aws-ecs-blue-green-deployment/Service/LBAlternateOptionsRole/ImportLBAlternateOptionsRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/Service/LBAlternateOptionsRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"ecs.amazonaws.com"}}],"Version":"2012-10-17"},"managedPolicyArns":[{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/AmazonECSInfrastructureRolePolicyForLoadBalancers"]]},{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/service-role/AmazonEC2ContainerServiceRole"]]}]}}}}},"Hook0Role":{"id":"Hook0Role","path":"aws-ecs-blue-green-deployment/Service/Hook0Role","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"}},{"addToPrincipalPolicy":[{}]},{"attachInlinePolicy":["*"]},{"attachInlinePolicy":["*"]}]},"children":{"ImportHook0Role":{"id":"ImportHook0Role","path":"aws-ecs-blue-green-deployment/Service/Hook0Role/ImportHook0Role","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/Service/Hook0Role/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"ecs.amazonaws.com"}}],"Version":"2012-10-17"}}}},"DefaultPolicy":{"id":"DefaultPolicy","path":"aws-ecs-blue-green-deployment/Service/Hook0Role/DefaultPolicy","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Policy","version":"0.0.0","metadata":["*",{"attachToRole":["*"]},{"attachToRole":["*"]},{"addStatements":[{}]}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/Service/Hook0Role/DefaultPolicy/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnPolicy","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Policy","aws:cdk:cloudformation:props":{"policyDocument":{"Statement":[{"Action":"lambda:InvokeFunction","Effect":"Allow","Resource":[{"Fn::GetAtt":["LambdaHookBF1BC8B4","Arn"]},{"Fn::Join":["",[{"Fn::GetAtt":["LambdaHookBF1BC8B4","Arn"]},":*"]]}]}],"Version":"2012-10-17"},"policyName":"ServiceHook0RoleDefaultPolicyD7C31604","roles":[{"Ref":"ServiceHook0RoleACE68A7A"}]}}}}}}}}},"BootstrapVersion":{"id":"BootstrapVersion","path":"aws-ecs-blue-green-deployment/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"aws-ecs-blue-green-deployment/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"aws-ecs-blue-green":{"id":"aws-ecs-blue-green","path":"aws-ecs-blue-green","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"},"children":{"DefaultTest":{"id":"DefaultTest","path":"aws-ecs-blue-green/DefaultTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"aws-ecs-blue-green/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"DeployAssert":{"id":"DeployAssert","path":"aws-ecs-blue-green/DefaultTest/DeployAssert","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"aws-ecs-blue-green/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"aws-ecs-blue-green/DefaultTest/DeployAssert/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}}}}}},"Tree":{"id":"Tree","path":"Tree","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}}}}} \ No newline at end of file +{"version":"tree-0.1","tree":{"id":"App","path":"","constructInfo":{"fqn":"aws-cdk-lib.App","version":"0.0.0"},"children":{"aws-ecs-blue-green-deployment":{"id":"aws-ecs-blue-green-deployment","path":"aws-ecs-blue-green-deployment","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"Vpc":{"id":"Vpc","path":"aws-ecs-blue-green-deployment/Vpc","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.Vpc","version":"0.0.0","metadata":[{"maxAzs":"*","restrictDefaultSecurityGroup":false}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/Vpc/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnVPC","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::VPC","aws:cdk:cloudformation:props":{"cidrBlock":"10.0.0.0/16","enableDnsHostnames":true,"enableDnsSupport":true,"instanceTenancy":"default","tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc"}]}}},"PublicSubnet1":{"id":"PublicSubnet1","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PublicSubnet","version":"0.0.0","metadata":[{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":true,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":true,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{},{"addNatGateway":["*"]}]},"children":{"Subnet":{"id":"Subnet","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[0,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.0.0/18","mapPublicIpOnLaunch":true,"tags":[{"key":"aws-cdk:subnet-name","value":"Public"},{"key":"aws-cdk:subnet-type","value":"Public"},{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"Acl":{"id":"Acl","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":[]}},"RouteTable":{"id":"RouteTable","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VpcPublicSubnet1RouteTable6C95E38E"},"subnetId":{"Ref":"VpcPublicSubnet1Subnet5C2D37C4"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","gatewayId":{"Ref":"VpcIGWD7BA715C"},"routeTableId":{"Ref":"VpcPublicSubnet1RouteTable6C95E38E"}}}},"EIP":{"id":"EIP","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/EIP","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnEIP","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::EIP","aws:cdk:cloudformation:props":{"domain":"vpc","tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1"}]}}},"NATGateway":{"id":"NATGateway","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1/NATGateway","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnNatGateway","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::NatGateway","aws:cdk:cloudformation:props":{"allocationId":{"Fn::GetAtt":["VpcPublicSubnet1EIPD7E02669","AllocationId"]},"subnetId":{"Ref":"VpcPublicSubnet1Subnet5C2D37C4"},"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet1"}]}}}}},"PublicSubnet2":{"id":"PublicSubnet2","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PublicSubnet","version":"0.0.0","metadata":[{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":true,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":true,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{},{"addNatGateway":["*"]}]},"children":{"Subnet":{"id":"Subnet","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[1,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.64.0/18","mapPublicIpOnLaunch":true,"tags":[{"key":"aws-cdk:subnet-name","value":"Public"},{"key":"aws-cdk:subnet-type","value":"Public"},{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"Acl":{"id":"Acl","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":[]}},"RouteTable":{"id":"RouteTable","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VpcPublicSubnet2RouteTable94F7E489"},"subnetId":{"Ref":"VpcPublicSubnet2Subnet691E08A3"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","gatewayId":{"Ref":"VpcIGWD7BA715C"},"routeTableId":{"Ref":"VpcPublicSubnet2RouteTable94F7E489"}}}},"EIP":{"id":"EIP","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/EIP","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnEIP","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::EIP","aws:cdk:cloudformation:props":{"domain":"vpc","tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2"}]}}},"NATGateway":{"id":"NATGateway","path":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2/NATGateway","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnNatGateway","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::NatGateway","aws:cdk:cloudformation:props":{"allocationId":{"Fn::GetAtt":["VpcPublicSubnet2EIP3C605A87","AllocationId"]},"subnetId":{"Ref":"VpcPublicSubnet2Subnet691E08A3"},"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PublicSubnet2"}]}}}}},"PrivateSubnet1":{"id":"PrivateSubnet1","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PrivateSubnet","version":"0.0.0","metadata":[{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":false,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":false,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{}]},"children":{"Subnet":{"id":"Subnet","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[0,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.128.0/18","mapPublicIpOnLaunch":false,"tags":[{"key":"aws-cdk:subnet-name","value":"Private"},{"key":"aws-cdk:subnet-type","value":"Private"},{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"Acl":{"id":"Acl","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":[]}},"RouteTable":{"id":"RouteTable","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VpcPrivateSubnet1RouteTableB2C5B500"},"subnetId":{"Ref":"VpcPrivateSubnet1Subnet536B997A"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet1/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","natGatewayId":{"Ref":"VpcPublicSubnet1NATGateway4D7517AA"},"routeTableId":{"Ref":"VpcPrivateSubnet1RouteTableB2C5B500"}}}}}},"PrivateSubnet2":{"id":"PrivateSubnet2","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.PrivateSubnet","version":"0.0.0","metadata":[{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":false,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{"availabilityZone":"*","vpcId":"*","cidrBlock":"*","mapPublicIpOnLaunch":false,"ipv6CidrBlock":"*","assignIpv6AddressOnCreation":"*"},{}]},"children":{"Subnet":{"id":"Subnet","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/Subnet","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnet","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Subnet","aws:cdk:cloudformation:props":{"availabilityZone":{"Fn::Select":[1,{"Fn::GetAZs":""}]},"cidrBlock":"10.0.192.0/18","mapPublicIpOnLaunch":false,"tags":[{"key":"aws-cdk:subnet-name","value":"Private"},{"key":"aws-cdk:subnet-type","value":"Private"},{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"Acl":{"id":"Acl","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/Acl","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":[]}},"RouteTable":{"id":"RouteTable","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/RouteTable","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRouteTable","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::RouteTable","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"RouteTableAssociation":{"id":"RouteTableAssociation","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/RouteTableAssociation","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSubnetRouteTableAssociation","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SubnetRouteTableAssociation","aws:cdk:cloudformation:props":{"routeTableId":{"Ref":"VpcPrivateSubnet2RouteTableA678073B"},"subnetId":{"Ref":"VpcPrivateSubnet2Subnet3788AAA1"}}}},"DefaultRoute":{"id":"DefaultRoute","path":"aws-ecs-blue-green-deployment/Vpc/PrivateSubnet2/DefaultRoute","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnRoute","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::Route","aws:cdk:cloudformation:props":{"destinationCidrBlock":"0.0.0.0/0","natGatewayId":{"Ref":"VpcPublicSubnet2NATGateway9182C01D"},"routeTableId":{"Ref":"VpcPrivateSubnet2RouteTableA678073B"}}}}}},"IGW":{"id":"IGW","path":"aws-ecs-blue-green-deployment/Vpc/IGW","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnInternetGateway","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::InternetGateway","aws:cdk:cloudformation:props":{"tags":[{"key":"Name","value":"aws-ecs-blue-green-deployment/Vpc"}]}}},"VPCGW":{"id":"VPCGW","path":"aws-ecs-blue-green-deployment/Vpc/VPCGW","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnVPCGatewayAttachment","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::VPCGatewayAttachment","aws:cdk:cloudformation:props":{"internetGatewayId":{"Ref":"VpcIGWD7BA715C"},"vpcId":{"Ref":"Vpc8378EB38"}}}}}},"FargateCluster":{"id":"FargateCluster","path":"aws-ecs-blue-green-deployment/FargateCluster","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.Cluster","version":"0.0.0","metadata":[{"vpc":"*","defaultCloudMapNamespace":{"name":"*","useForServiceConnect":true}},{"addDefaultCloudMapNamespace":[{"name":"*","useForServiceConnect":true}]}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/FargateCluster/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.CfnCluster","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ECS::Cluster","aws:cdk:cloudformation:props":{"serviceConnectDefaults":{"namespace":{"Fn::GetAtt":["FargateClusterDefaultServiceDiscoveryNamespace04381E1E","Arn"]}}}}},"DefaultServiceDiscoveryNamespace":{"id":"DefaultServiceDiscoveryNamespace","path":"aws-ecs-blue-green-deployment/FargateCluster/DefaultServiceDiscoveryNamespace","constructInfo":{"fqn":"aws-cdk-lib.aws_servicediscovery.PrivateDnsNamespace","version":"0.0.0","metadata":[{"name":"*","vpc":"*"}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/FargateCluster/DefaultServiceDiscoveryNamespace/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_servicediscovery.CfnPrivateDnsNamespace","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ServiceDiscovery::PrivateDnsNamespace","aws:cdk:cloudformation:props":{"name":"bluegreendeployment.com","vpc":{"Ref":"Vpc8378EB38"}}}}}}}},"BlueTG":{"id":"BlueTG","path":"aws-ecs-blue-green-deployment/BlueTG","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationTargetGroup","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/BlueTG/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnTargetGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::TargetGroup","aws:cdk:cloudformation:props":{"healthCheckPath":"/","matcher":{"httpCode":"200"},"port":80,"protocol":"HTTP","targetGroupAttributes":[{"key":"stickiness.enabled","value":"false"}],"targetType":"ip","vpcId":{"Ref":"Vpc8378EB38"}}}}}},"GreenTG":{"id":"GreenTG","path":"aws-ecs-blue-green-deployment/GreenTG","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationTargetGroup","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/GreenTG/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnTargetGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::TargetGroup","aws:cdk:cloudformation:props":{"healthCheckPath":"/","matcher":{"httpCode":"200"},"port":80,"protocol":"HTTP","targetGroupAttributes":[{"key":"stickiness.enabled","value":"false"}],"targetType":"ip","vpcId":{"Ref":"Vpc8378EB38"}}}}}},"LBSecurityGroup":{"id":"LBSecurityGroup","path":"aws-ecs-blue-green-deployment/LBSecurityGroup","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.SecurityGroup","version":"0.0.0","metadata":[{"vpc":"*","allowAllOutbound":true},{"addIngressRule":[{"canInlineRule":true,"connections":"*","uniqueId":"*"},{}]},{"addIngressRule":[{"canInlineRule":true,"connections":"*","uniqueId":"*"},{},"*",false]},{"addEgressRule":["*",{},"*",true]}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/LBSecurityGroup/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSecurityGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SecurityGroup","aws:cdk:cloudformation:props":{"groupDescription":"aws-ecs-blue-green-deployment/LBSecurityGroup","securityGroupEgress":[{"cidrIp":"0.0.0.0/0","description":"Allow all outbound traffic by default","ipProtocol":"-1"}],"securityGroupIngress":[{"cidrIp":"0.0.0.0/0","ipProtocol":"tcp","fromPort":80,"toPort":80,"description":"from 0.0.0.0/0:80"}],"vpcId":{"Ref":"Vpc8378EB38"}}}}}},"ECSSecurityGroup":{"id":"ECSSecurityGroup","path":"aws-ecs-blue-green-deployment/ECSSecurityGroup","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.SecurityGroup","version":"0.0.0","metadata":[{"vpc":"*","allowAllOutbound":true},{"addIngressRule":["*",{}]},{"addIngressRule":["*",{},"*",false]}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/ECSSecurityGroup/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSecurityGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SecurityGroup","aws:cdk:cloudformation:props":{"groupDescription":"aws-ecs-blue-green-deployment/ECSSecurityGroup","securityGroupEgress":[{"cidrIp":"0.0.0.0/0","description":"Allow all outbound traffic by default","ipProtocol":"-1"}],"vpcId":{"Ref":"Vpc8378EB38"}}}},"from awsecsbluegreendeploymentLBSecurityGroup67E4A783:80":{"id":"from awsecsbluegreendeploymentLBSecurityGroup67E4A783:80","path":"aws-ecs-blue-green-deployment/ECSSecurityGroup/from awsecsbluegreendeploymentLBSecurityGroup67E4A783:80","constructInfo":{"fqn":"aws-cdk-lib.aws_ec2.CfnSecurityGroupIngress","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::EC2::SecurityGroupIngress","aws:cdk:cloudformation:props":{"description":"from awsecsbluegreendeploymentLBSecurityGroup67E4A783:80","fromPort":80,"groupId":{"Fn::GetAtt":["ECSSecurityGroupA14DBE7D","GroupId"]},"ipProtocol":"tcp","sourceSecurityGroupId":{"Fn::GetAtt":["LBSecurityGroup4464B654","GroupId"]},"toPort":80}}}}},"ALB":{"id":"ALB","path":"aws-ecs-blue-green-deployment/ALB","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationLoadBalancer","version":"0.0.0","metadata":["*","*"]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/ALB/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnLoadBalancer","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::LoadBalancer","aws:cdk:cloudformation:props":{"loadBalancerAttributes":[{"key":"deletion_protection.enabled","value":"false"},{"key":"idle_timeout.timeout_seconds","value":"60"}],"scheme":"internet-facing","securityGroups":[{"Fn::GetAtt":["LBSecurityGroup4464B654","GroupId"]}],"subnets":[{"Ref":"VpcPublicSubnet1Subnet5C2D37C4"},{"Ref":"VpcPublicSubnet2Subnet691E08A3"}],"type":"application"}}},"ALBListenerHTTP":{"id":"ALBListenerHTTP","path":"aws-ecs-blue-green-deployment/ALB/ALBListenerHTTP","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationListener","version":"0.0.0","metadata":["*","*"]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/ALB/ALBListenerHTTP/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnListener","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::Listener","aws:cdk:cloudformation:props":{"defaultActions":[{"type":"fixed-response","fixedResponseConfig":{"statusCode":"404"}}],"loadBalancerArn":{"Ref":"ALBAEE750D2"},"port":80,"protocol":"HTTP"}}}}}}},"ALBProductionListenerRule":{"id":"ALBProductionListenerRule","path":"aws-ecs-blue-green-deployment/ALBProductionListenerRule","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationListenerRule","version":"0.0.0"},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/ALBProductionListenerRule/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_elasticloadbalancingv2.CfnListenerRule","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ElasticLoadBalancingV2::ListenerRule","aws:cdk:cloudformation:props":{"actions":[{"type":"forward","forwardConfig":{"targetGroups":[{"targetGroupArn":{"Ref":"BlueTG7C058B2C"},"weight":100},{"targetGroupArn":{"Ref":"GreenTG71A27F2F"},"weight":0}]}}],"conditions":[{"field":"path-pattern","pathPatternConfig":{"values":["/*"]}}],"listenerArn":{"Ref":"ALBALBListenerHTTP413250C3"},"priority":1}}}}},"LambdaHook":{"id":"LambdaHook","path":"aws-ecs-blue-green-deployment/LambdaHook","constructInfo":{"fqn":"aws-cdk-lib.aws_lambda.Function","version":"0.0.0","metadata":[{"handler":"*","runtime":"*","code":"*"}]},"children":{"ServiceRole":{"id":"ServiceRole","path":"aws-ecs-blue-green-deployment/LambdaHook/ServiceRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"},"managedPolicies":[{"managedPolicyArn":"*"}]}]},"children":{"ImportServiceRole":{"id":"ImportServiceRole","path":"aws-ecs-blue-green-deployment/LambdaHook/ServiceRole/ImportServiceRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/LambdaHook/ServiceRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"}}],"Version":"2012-10-17"},"managedPolicyArns":[{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]]}]}}}}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/LambdaHook/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_lambda.CfnFunction","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Lambda::Function","aws:cdk:cloudformation:props":{"code":{"zipFile":"exports.handler = async (event, context) => {\n console.log('Event received:', JSON.stringify(event));\n return { hookStatus: 'SUCCEEDED' }; \n };"},"handler":"index.handler","role":{"Fn::GetAtt":["LambdaHookServiceRole9AAAD33B","Arn"]},"runtime":"nodejs22.x"}}},"LogGroup":{"id":"LogGroup","path":"aws-ecs-blue-green-deployment/LambdaHook/LogGroup","constructInfo":{"fqn":"aws-cdk-lib.aws_logs.LogGroup","version":"0.0.0","metadata":[{"logGroupName":"*"}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/LambdaHook/LogGroup/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_logs.CfnLogGroup","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Logs::LogGroup","aws:cdk:cloudformation:props":{"logGroupName":{"Fn::Join":["",["/aws/lambda/",{"Ref":"LambdaHookBF1BC8B4"}]]},"retentionInDays":731}}}}}}},"TaskDef":{"id":"TaskDef","path":"aws-ecs-blue-green-deployment/TaskDef","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.FargateTaskDefinition","version":"0.0.0","metadata":["*","*","*","*"]},"children":{"TaskRole":{"id":"TaskRole","path":"aws-ecs-blue-green-deployment/TaskDef/TaskRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"}}]},"children":{"ImportTaskRole":{"id":"ImportTaskRole","path":"aws-ecs-blue-green-deployment/TaskDef/TaskRole/ImportTaskRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/TaskDef/TaskRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"ecs-tasks.amazonaws.com"}}],"Version":"2012-10-17"}}}}}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/TaskDef/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.CfnTaskDefinition","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ECS::TaskDefinition","aws:cdk:cloudformation:props":{"containerDefinitions":[{"essential":true,"image":"public.ecr.aws/nginx/nginx:latest","name":"nginx","portMappings":[{"containerPort":80,"protocol":"tcp","appProtocol":"http","name":"api"}]}],"cpu":"256","family":"awsecsbluegreendeploymentTaskDef51D80572","memory":"512","networkMode":"awsvpc","requiresCompatibilities":["FARGATE"],"taskRoleArn":{"Fn::GetAtt":["TaskDefTaskRole1EDB4A67","Arn"]}}}},"container":{"id":"container","path":"aws-ecs-blue-green-deployment/TaskDef/container","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.ContainerDefinition","version":"0.0.0"}}}},"Service":{"id":"Service","path":"aws-ecs-blue-green-deployment/Service","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.FargateService","version":"0.0.0","metadata":["*"]},"children":{"Service":{"id":"Service","path":"aws-ecs-blue-green-deployment/Service/Service","constructInfo":{"fqn":"aws-cdk-lib.aws_ecs.CfnService","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::ECS::Service","aws:cdk:cloudformation:props":{"cluster":{"Ref":"FargateCluster7CCD5F93"},"deploymentConfiguration":{"maximumPercent":200,"minimumHealthyPercent":50,"strategy":"BLUE_GREEN","lifecycleHooks":[{"hookTargetArn":{"Fn::GetAtt":["LambdaHookBF1BC8B4","Arn"]},"roleArn":{"Fn::GetAtt":["ServicePreScaleUpRoleD002A553","Arn"]},"lifecycleStages":["PRE_SCALE_UP"]}]},"enableEcsManagedTags":false,"healthCheckGracePeriodSeconds":60,"launchType":"FARGATE","loadBalancers":[{"targetGroupArn":{"Ref":"BlueTG7C058B2C"},"containerName":"nginx","containerPort":80,"advancedConfiguration":{"alternateTargetGroupArn":{"Ref":"GreenTG71A27F2F"},"roleArn":{"Fn::GetAtt":["ServiceLBAlternateOptionsRole06C91D94","Arn"]},"productionListenerRule":{"Ref":"ALBProductionListenerRule243D0687"}}}],"networkConfiguration":{"awsvpcConfiguration":{"assignPublicIp":"DISABLED","subnets":[{"Ref":"VpcPrivateSubnet1Subnet536B997A"},{"Ref":"VpcPrivateSubnet2Subnet3788AAA1"}],"securityGroups":[{"Fn::GetAtt":["ECSSecurityGroupA14DBE7D","GroupId"]}]}},"taskDefinition":{"Ref":"TaskDef54694570"}}}},"LBAlternateOptionsRole":{"id":"LBAlternateOptionsRole","path":"aws-ecs-blue-green-deployment/Service/LBAlternateOptionsRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"},"managedPolicies":[{"managedPolicyArn":"*"}]}]},"children":{"ImportLBAlternateOptionsRole":{"id":"ImportLBAlternateOptionsRole","path":"aws-ecs-blue-green-deployment/Service/LBAlternateOptionsRole/ImportLBAlternateOptionsRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/Service/LBAlternateOptionsRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"ecs.amazonaws.com"}}],"Version":"2012-10-17"},"managedPolicyArns":[{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/AmazonECSInfrastructureRolePolicyForLoadBalancers"]]}]}}}}},"PreScaleUpRole":{"id":"PreScaleUpRole","path":"aws-ecs-blue-green-deployment/Service/PreScaleUpRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"}},{"addToPrincipalPolicy":[{}]},{"attachInlinePolicy":["*"]},{"attachInlinePolicy":["*"]}]},"children":{"ImportPreScaleUpRole":{"id":"ImportPreScaleUpRole","path":"aws-ecs-blue-green-deployment/Service/PreScaleUpRole/ImportPreScaleUpRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/Service/PreScaleUpRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"ecs.amazonaws.com"}}],"Version":"2012-10-17"}}}},"DefaultPolicy":{"id":"DefaultPolicy","path":"aws-ecs-blue-green-deployment/Service/PreScaleUpRole/DefaultPolicy","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Policy","version":"0.0.0","metadata":["*",{"attachToRole":["*"]},{"attachToRole":["*"]},{"addStatements":[{}]}]},"children":{"Resource":{"id":"Resource","path":"aws-ecs-blue-green-deployment/Service/PreScaleUpRole/DefaultPolicy/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnPolicy","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Policy","aws:cdk:cloudformation:props":{"policyDocument":{"Statement":[{"Action":"lambda:InvokeFunction","Effect":"Allow","Resource":[{"Fn::GetAtt":["LambdaHookBF1BC8B4","Arn"]},{"Fn::Join":["",[{"Fn::GetAtt":["LambdaHookBF1BC8B4","Arn"]},":*"]]}]}],"Version":"2012-10-17"},"policyName":"ServicePreScaleUpRoleDefaultPolicyF1032E86","roles":[{"Ref":"ServicePreScaleUpRoleD002A553"}]}}}}}}}}},"BootstrapVersion":{"id":"BootstrapVersion","path":"aws-ecs-blue-green-deployment/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"aws-ecs-blue-green-deployment/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"aws-ecs-blue-green":{"id":"aws-ecs-blue-green","path":"aws-ecs-blue-green","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"},"children":{"DefaultTest":{"id":"DefaultTest","path":"aws-ecs-blue-green/DefaultTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"aws-ecs-blue-green/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"DeployAssert":{"id":"DeployAssert","path":"aws-ecs-blue-green/DefaultTest/DeployAssert","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"aws-ecs-blue-green/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"aws-ecs-blue-green/DefaultTest/DeployAssert/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}}}}}},"Tree":{"id":"Tree","path":"Tree","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}}}}} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.ts index 51ef28255052d..b72508efa92d5 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.ts @@ -123,7 +123,7 @@ const service = new ecs.FargateService(stack, 'Service', { deploymentStrategy: ecs.DeploymentStrategy.BLUE_GREEN, }); -service.addLifecycleHook(new ecs.DeploymentLifecycleLambdaTarget(lambdaHook, { +service.addLifecycleHook(new ecs.DeploymentLifecycleLambdaTarget(lambdaHook, 'PreScaleUp', { lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], })); diff --git a/packages/aws-cdk-lib/aws-ecs/lib/alternate-target-configuration.ts b/packages/aws-cdk-lib/aws-ecs/lib/alternate-target-configuration.ts index b48654797603a..816e15289ab1e 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/alternate-target-configuration.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/alternate-target-configuration.ts @@ -75,7 +75,7 @@ export interface AlternateTargetConfig { } /** - * Interface for advanced configuration + * Interface for configuring alternate target groups for blue/green deployments */ export interface IAlternateTarget { /** @@ -88,7 +88,7 @@ export interface IAlternateTarget { } /** - * Options for AdvancedConfiguration + * Options for AlternateTarget configuration */ export interface AlternateTargetOptions { /** @@ -105,7 +105,7 @@ export interface AlternateTargetOptions { } /** - * Properties for AdvancedConfiguration + * Properties for AlternateTarget configuration */ export interface AlternateTargetProps extends AlternateTargetOptions { /** @@ -126,7 +126,7 @@ export interface AlternateTargetProps extends AlternateTargetOptions { } /** - * Advanced configuration for load balancer settings + * Configuration for alternate target groups used in blue/green deployments with load balancers */ export class AlternateTarget implements IAlternateTarget { constructor(private readonly props: AlternateTargetProps) {} @@ -149,7 +149,6 @@ export class AlternateTarget implements IAlternateTarget { assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'), managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonECSInfrastructureRolePolicyForLoadBalancers'), - iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonEC2ContainerServiceRole'), ], }); @@ -157,7 +156,7 @@ export class AlternateTarget implements IAlternateTarget { alternateTargetGroupArn: this.props.alternateTargetGroup.targetGroupArn, roleArn: role.roleArn, productionListenerRule: this.props.productionListener._listenerArn, - testListenerRule: this.props.testListener?._listenerArn || undefined, + testListenerRule: this.props.testListener?._listenerArn, }; return config; diff --git a/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts b/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts index 3493471dcb70d..6092ec329f058 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts @@ -761,15 +761,11 @@ export abstract class BaseService extends Resource Annotations.of(this).addWarningV2('@aws-cdk/aws-ecs:externalDeploymentController', 'taskDefinition and launchType are blanked out when using external deployment controller.'); } - if (props.circuitBreaker - && deploymentController - && deploymentController.type !== DeploymentControllerType.ECS) { + if (props.circuitBreaker && !this.isEcsDeploymentController) { Annotations.of(this).addError('Deployment circuit breaker requires the ECS deployment controller.'); } - if (props.deploymentAlarms - && deploymentController - && deploymentController.type !== DeploymentControllerType.ECS) { + if (props.deploymentAlarms && !this.isEcsDeploymentController) { throw new ValidationError('Deployment alarms requires the ECS deployment controller.', this); } @@ -843,8 +839,7 @@ export abstract class BaseService extends Resource rollback: props.deploymentAlarms.behavior !== AlarmBehavior.FAIL_ON_ALARM, }; // CloudWatch alarms is only supported for Amazon ECS services that use the rolling update (ECS) deployment controller. - } else if ((!props.deploymentController || - props.deploymentController?.type === DeploymentControllerType.ECS) && this.deploymentAlarmsAvailableInRegion()) { + } else if (this.isEcsDeploymentController && this.deploymentAlarmsAvailableInRegion()) { // Only set default deployment alarms settings when feature flag is not enabled. if (!FeatureFlags.of(this).isEnabled(cxapi.ECS_REMOVE_DEFAULT_DEPLOYMENT_ALARM)) { this.deploymentAlarms = { @@ -878,8 +873,8 @@ export abstract class BaseService extends Resource } private renderLifecycleHooks(): CfnService.DeploymentLifecycleHookProperty[] { - return this.lifecycleHooks.map((target, index) => { - const config = target.bind(this, `Hook${index}`); + return this.lifecycleHooks.map((target) => { + const config = target.bind(this); return { hookTargetArn: config.targetArn, roleArn: config.role!.roleArn, @@ -1568,7 +1563,7 @@ export abstract class BaseService extends Resource throw new ValidationError('Cannot use a load balancer if NetworkMode is None. Use Bridge, Host or AwsVpc instead.', this); } - const advancedConfiguration = alternateTarget?.bind(this) || undefined; + const advancedConfiguration = alternateTarget?.bind(this); this.loadBalancers.push({ targetGroupArn: targetGroup.targetGroupArn, containerName, diff --git a/packages/aws-cdk-lib/aws-ecs/lib/deployment-lifecycle-hook-target.ts b/packages/aws-cdk-lib/aws-ecs/lib/deployment-lifecycle-hook-target.ts index de247960d10d5..87c736fbe554e 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/deployment-lifecycle-hook-target.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/deployment-lifecycle-hook-target.ts @@ -67,7 +67,7 @@ export interface IDeploymentLifecycleHookTarget { * @param scope The construct scope * @param id A unique identifier for this binding */ - bind(scope: IConstruct, id?: string): DeploymentLifecycleHookTargetConfig; + bind(scope: IConstruct): DeploymentLifecycleHookTargetConfig; } /** @@ -90,27 +90,37 @@ export interface DeploymentLifecycleLambdaTargetProps { * Use an AWS Lambda function as a deployment lifecycle hook target */ export class DeploymentLifecycleLambdaTarget implements IDeploymentLifecycleHookTarget { + private _role?: iam.IRole; + constructor( private readonly handler: lambda.IFunction, + private readonly id: string, private readonly props: DeploymentLifecycleLambdaTargetProps, ) { } + /** + * The IAM role for the deployment lifecycle hook target + */ + public get role(): iam.IRole { + return this._role!; + } + /** * Bind this target to a deployment lifecycle hook */ - public bind(scope: IConstruct, id?: string): DeploymentLifecycleHookTargetConfig { + public bind(scope: IConstruct): DeploymentLifecycleHookTargetConfig { // Create role if not provided - let role = this.props.role; - if (!role) { - role = new iam.Role(scope, `${id}Role`, { + this._role = this.props.role; + if (!this._role) { + this._role = new iam.Role(scope, `${this.id}Role`, { assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'), }); - this.handler.grantInvoke(role); + this.handler.grantInvoke(this._role); } return { targetArn: this.handler.functionArn, - role: role, + role: this._role, lifecycleStages: this.props.lifecycleStages, }; } diff --git a/packages/aws-cdk-lib/aws-ecs/test/base-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/base-service.test.ts index b3f520bdc8c8c..fd1bb8bf66179 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/base-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/base-service.test.ts @@ -609,7 +609,7 @@ describe('Blue/Green Deployment', () => { code: lambda.Code.fromInline('exports.handler = async () => { return { hookStatus: "SUCCEEDED" }; }'), }); - const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, { + const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, 'PreScaleUp', { lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], }); diff --git a/packages/aws-cdk-lib/aws-ecs/test/deployment-lifecycle-hook-target.test.ts b/packages/aws-cdk-lib/aws-ecs/test/deployment-lifecycle-hook-target.test.ts index c463bc7dec23d..88dc8423a4d9d 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/deployment-lifecycle-hook-target.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/deployment-lifecycle-hook-target.test.ts @@ -38,7 +38,7 @@ describe('DeploymentLifecycleHookTarget', () => { }); // WHEN - const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, { + const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, 'PreScaleUpHook', { lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], }); service.addLifecycleHook(hookTarget); @@ -127,7 +127,7 @@ describe('DeploymentLifecycleHookTarget', () => { lambdaFunction.grantInvoke(customRole); // WHEN - const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, { + const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, 'PreScaleUpHook', { lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], role: customRole, }); @@ -165,7 +165,7 @@ describe('DeploymentLifecycleHookTarget', () => { }); // WHEN - const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, { + const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, 'PreScaleUpHook', { lifecycleStages: [ ecs.DeploymentLifecycleStage.PRE_SCALE_UP, ecs.DeploymentLifecycleStage.POST_SCALE_UP, @@ -202,7 +202,7 @@ describe('DeploymentLifecycleHookTarget', () => { }, }); - const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, { + const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, 'PreScaleUpHook', { lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], }); @@ -226,11 +226,11 @@ describe('DeploymentLifecycleHookTarget', () => { }); // WHEN - const firstHook = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, { + const firstHook = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, 'PreScaleUpHook', { lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], }); - const secondHook = new ecs.DeploymentLifecycleLambdaTarget(secondLambda, { + const secondHook = new ecs.DeploymentLifecycleLambdaTarget(secondLambda, 'PostScaleUpHook', { lifecycleStages: [ecs.DeploymentLifecycleStage.POST_SCALE_UP], }); @@ -266,7 +266,7 @@ describe('DeploymentLifecycleHookTarget', () => { test('lifecycle hooks can be added during service creation', () => { // GIVEN - const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, { + const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, 'PreScaleUpHook', { lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], }); @@ -297,7 +297,7 @@ describe('DeploymentLifecycleHookTarget', () => { test('lifecycle hooks cannot be added during service creation with non-ECS deployment controller', () => { // GIVEN - const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, { + const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, 'PreScaleUpHook', { lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], }); From 4cfb04f369cf29767109b6db0f33e5a0d8b6e8f4 Mon Sep 17 00:00:00 2001 From: Kishan Gajjar Date: Wed, 30 Jul 2025 11:34:45 -0400 Subject: [PATCH 3/8] address CR feedback --- .../aws-ecs/lib/base/base-service.ts | 39 +---- .../aws-ecs/test/base-service.test.ts | 141 ------------------ .../deployment-lifecycle-hook-target.test.ts | 35 +---- 3 files changed, 6 insertions(+), 209 deletions(-) diff --git a/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts b/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts index 6092ec329f058..034c9f5020847 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts @@ -738,7 +738,7 @@ export abstract class BaseService extends Resource alarms: Lazy.any({ produce: () => this.deploymentAlarms }, { omitEmptyArray: true }), strategy: props.deploymentStrategy, bakeTimeInMinutes: props.bakeTime?.toMinutes(), - lifecycleHooks: Lazy.any({ produce: () => this.renderLifecycleHooks() }), + lifecycleHooks: Lazy.any({ produce: () => this.renderLifecycleHooks() }, { omitEmptyArray: true }), }, propagateTags: propagateTagsFromSource === PropagatedTagSource.NONE ? undefined : props.propagateTags, enableEcsManagedTags: props.enableECSManagedTags ?? false, @@ -1285,14 +1285,14 @@ export abstract class BaseService extends Resource const self = this; const target = this.taskDefinition._validateTarget(options); const connections = self.connections; - const advancedConfiguration = alternateOptions; + const alternateOption = alternateOptions; return { attachToApplicationTargetGroup(targetGroup: elbv2.ApplicationTargetGroup): elbv2.LoadBalancerTargetProps { targetGroup.registerConnectable(self, self.taskDefinition._portRangeFromPortMapping(target.portMapping)); - return self.attachToELBv2(targetGroup, target.containerName, target.portMapping.containerPort!, advancedConfiguration); + return self.attachToELBv2(targetGroup, target.containerName, target.portMapping.containerPort!, alternateOption); }, attachToNetworkTargetGroup(targetGroup: elbv2.NetworkTargetGroup): elbv2.LoadBalancerTargetProps { - return self.attachToELBv2(targetGroup, target.containerName, target.portMapping.containerPort!, advancedConfiguration); + return self.attachToELBv2(targetGroup, target.containerName, target.portMapping.containerPort!, alternateOption); }, connections, attachToClassicLB(loadBalancer: elb.LoadBalancer): void { @@ -1662,37 +1662,6 @@ export abstract class BaseService extends Resource public isUsingECSDeploymentController(): boolean { return this.isEcsDeploymentController; } - - /** - * Enables blue-green deployment strategy for the service with optional lifecycle hooks. - * @param alternateConfig alternate configuration for the deployment - * @param lifecycleHooks Optional lifecycle hooks to execute during deployment - */ - public enableBlueGreenDeployment(alternateConfig: IAlternateTarget, lifecycleHooks?: IDeploymentLifecycleHookTarget[]) { - if (!this.isEcsDeploymentController) { - throw new ValidationError('Blue-green deployment requires the ECS deployment controller.', this); - } - - // Set deployment strategy to BLUE_GREEN - this.resource.deploymentConfiguration = { - ...this.resource.deploymentConfiguration, - strategy: DeploymentStrategy.BLUE_GREEN, - }; - - // Add lifecycle hooks if provided - if (lifecycleHooks && lifecycleHooks.length > 0) { - lifecycleHooks.forEach(hook => this.addLifecycleHook(hook)); - } - - // Use the default container with the alternate configuration if provided - if (this.taskDefinition.defaultContainer) { - this.loadBalancerTarget({ - containerName: this.taskDefinition.defaultContainer.containerName, - }, alternateConfig); - } - - return this; - } } /** diff --git a/packages/aws-cdk-lib/aws-ecs/test/base-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/base-service.test.ts index fd1bb8bf66179..d2b332c95dd52 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/base-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/base-service.test.ts @@ -516,145 +516,4 @@ describe('Blue/Green Deployment', () => { // THEN expect(service.isUsingECSDeploymentController()).toBe(false); }); - - test('enableBlueGreenDeployment throws when not using ECS deployment controller', () => { - // GIVEN - const service = new ecs.FargateService(stack, 'FargateService', { - cluster, - taskDefinition, - deploymentController: { - type: ecs.DeploymentControllerType.CODE_DEPLOY, - }, - }); - - const blueTargetGroup = new elbv2.ApplicationTargetGroup(stack, 'BlueTargetGroup', { - vpc, - port: 80, - }); - - const greenTargetGroup = new elbv2.ApplicationTargetGroup(stack, 'GreenTargetGroup', { - vpc, - port: 80, - }); - - const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc }); - const listener = alb.addListener('Listener', { - port: 80, - defaultAction: elbv2.ListenerAction.fixedResponse(200), - }); - - const rule = new elbv2.ApplicationListenerRule(stack, 'Rule', { - listener, - priority: 1, - conditions: [elbv2.ListenerCondition.pathPatterns(['/'])], - action: elbv2.ListenerAction.forward([blueTargetGroup]), - }); - - const alternateTarget = new ecs.AlternateTarget({ - alternateTargetGroup: greenTargetGroup, - productionListener: ecs.ListenerRuleConfiguration.applicationListenerRule(rule), - }); - - // THEN - expect(() => { - service.enableBlueGreenDeployment(alternateTarget); - }).toThrow(/Blue-green deployment requires the ECS deployment controller/); - }); - - test('enableBlueGreenDeployment configures alternate target when provided', () => { - // GIVEN - const service = new ecs.FargateService(stack, 'FargateService', { - cluster, - taskDefinition, - }); - - const blueTargetGroup = new elbv2.ApplicationTargetGroup(stack, 'BlueTargetGroup', { - vpc, - port: 80, - }); - - const greenTargetGroup = new elbv2.ApplicationTargetGroup(stack, 'GreenTargetGroup', { - vpc, - port: 80, - }); - - const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc }); - const listener = alb.addListener('Listener', { - port: 80, - defaultAction: elbv2.ListenerAction.fixedResponse(200), - }); - - const rule = new elbv2.ApplicationListenerRule(stack, 'Rule', { - listener, - priority: 1, - conditions: [elbv2.ListenerCondition.pathPatterns(['/'])], - action: elbv2.ListenerAction.forward([blueTargetGroup]), - }); - - const alternateTarget = new ecs.AlternateTarget({ - alternateTargetGroup: greenTargetGroup, - productionListener: ecs.ListenerRuleConfiguration.applicationListenerRule(rule), - }); - - // Register the blue target group first - const target = service.loadBalancerTarget({ - containerName: 'web', - containerPort: 80, - }, alternateTarget); - target.attachToApplicationTargetGroup(blueTargetGroup); - - const lambdaFunction = new lambda.Function(stack, 'TestFunction', { - runtime: lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - code: lambda.Code.fromInline('exports.handler = async () => { return { hookStatus: "SUCCEEDED" }; }'), - }); - - const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, 'PreScaleUp', { - lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], - }); - - // WHEN - service.enableBlueGreenDeployment(alternateTarget, [hookTarget]); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DeploymentConfiguration: { - Strategy: 'BLUE_GREEN', - LifecycleHooks: [ - { - LifecycleStages: ['PRE_SCALE_UP'], - HookTargetArn: { - 'Fn::GetAtt': [ - Match.stringLikeRegexp('TestFunction'), - 'Arn', - ], - }, - }, - ], - }, - LoadBalancers: [ - { - ContainerName: 'web', - ContainerPort: 80, - TargetGroupArn: { - Ref: Match.stringLikeRegexp('BlueTargetGroup'), - }, - AdvancedConfiguration: { - AlternateTargetGroupArn: { - Ref: Match.stringLikeRegexp('GreenTargetGroup'), - }, - ProductionListenerRule: { - Ref: Match.stringLikeRegexp('Rule'), - }, - RoleArn: { - 'Fn::GetAtt': [ - Match.stringLikeRegexp('FargateServiceLBAlternateOptionsRole'), - 'Arn', - ], - }, - }, - }, - ], - }); - }); }); diff --git a/packages/aws-cdk-lib/aws-ecs/test/deployment-lifecycle-hook-target.test.ts b/packages/aws-cdk-lib/aws-ecs/test/deployment-lifecycle-hook-target.test.ts index 88dc8423a4d9d..0ca099da28e69 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/deployment-lifecycle-hook-target.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/deployment-lifecycle-hook-target.test.ts @@ -264,37 +264,6 @@ describe('DeploymentLifecycleHookTarget', () => { }); }); - test('lifecycle hooks can be added during service creation', () => { - // GIVEN - const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, 'PreScaleUpHook', { - lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], - }); - - // WHEN - const service = new ecs.FargateService(stack, 'FargateService', { - cluster, - taskDefinition, - lifecycleHooks: [hookTarget], - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DeploymentConfiguration: { - LifecycleHooks: [ - { - LifecycleStages: ['PRE_SCALE_UP'], - HookTargetArn: { - 'Fn::GetAtt': [ - Match.stringLikeRegexp('TestFunction'), - 'Arn', - ], - }, - }, - ], - }, - }); - }); - test('lifecycle hooks cannot be added during service creation with non-ECS deployment controller', () => { // GIVEN const hookTarget = new ecs.DeploymentLifecycleLambdaTarget(lambdaFunction, 'PreScaleUpHook', { @@ -303,14 +272,14 @@ describe('DeploymentLifecycleHookTarget', () => { // THEN expect(() => { - new ecs.FargateService(stack, 'FargateService', { + const service = new ecs.FargateService(stack, 'FargateService', { cluster, taskDefinition, deploymentController: { type: ecs.DeploymentControllerType.CODE_DEPLOY, }, - lifecycleHooks: [hookTarget], }); + service.addLifecycleHook(hookTarget); }).toThrow(/Deployment lifecycle hooks requires the ECS deployment controller/); }); }); From fc82ca460aef81047262e334d0de95b545b587cc Mon Sep 17 00:00:00 2001 From: Kishan Gajjar Date: Wed, 30 Jul 2025 12:11:27 -0400 Subject: [PATCH 4/8] fix Readme file --- packages/aws-cdk-lib/aws-ecs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-ecs/README.md b/packages/aws-cdk-lib/aws-ecs/README.md index 49e91d89aa52c..7497aae56c708 100644 --- a/packages/aws-cdk-lib/aws-ecs/README.md +++ b/packages/aws-cdk-lib/aws-ecs/README.md @@ -2086,7 +2086,7 @@ const service = new ecs.FargateService(stack, 'Service', { deploymentStrategy: ecs.DeploymentStrategy.BLUE_GREEN, }); -service.addLifecycleHook(new ecs.DeploymentLifecycleLambdaTarget(lambdaHook, { +service.addLifecycleHook(new ecs.DeploymentLifecycleLambdaTarget(lambdaHook, 'PreScaleHook', { lifecycleStages: [ecs.DeploymentLifecycleStage.PRE_SCALE_UP], })); From 1430e52ffa596c821a299279381ffe056e22f6b0 Mon Sep 17 00:00:00 2001 From: Kishan Gajjar Date: Wed, 30 Jul 2025 13:00:54 -0400 Subject: [PATCH 5/8] fix Readme file --- packages/aws-cdk-lib/aws-ecs/README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/aws-cdk-lib/aws-ecs/README.md b/packages/aws-cdk-lib/aws-ecs/README.md index 7497aae56c708..89a3fb360a7ed 100644 --- a/packages/aws-cdk-lib/aws-ecs/README.md +++ b/packages/aws-cdk-lib/aws-ecs/README.md @@ -2079,10 +2079,15 @@ Amazon ECS supports native blue/green deployments that allow you to deploy new v ### Using Fargate L2 constructs for Blue/Green Feature ```ts -const service = new ecs.FargateService(stack, 'Service', { +declare const cluster: ecs.Cluster; +declare const taskDefinition: ecs.TaskDefinition; +declare const lambdaHook: lambda.Function; +declare const greenTargetGroup: elbv2.ApplicationTargetGroup; +declare const prodListenerRule: elbv2.ApplicationListenerRule; + +const service = new ecs.FargateService(this, 'Service', { cluster, taskDefinition, - securityGroups: [ecsSecurityGroup], deploymentStrategy: ecs.DeploymentStrategy.BLUE_GREEN, }); From f20aeef051b99fa495a17121a67ecf1f61c7a124 Mon Sep 17 00:00:00 2001 From: Kishan Gajjar Date: Wed, 30 Jul 2025 16:28:13 -0400 Subject: [PATCH 6/8] fix Readme file --- packages/aws-cdk-lib/aws-ecs/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/aws-cdk-lib/aws-ecs/README.md b/packages/aws-cdk-lib/aws-ecs/README.md index 89a3fb360a7ed..7ed4a3ab1d09b 100644 --- a/packages/aws-cdk-lib/aws-ecs/README.md +++ b/packages/aws-cdk-lib/aws-ecs/README.md @@ -2079,9 +2079,12 @@ Amazon ECS supports native blue/green deployments that allow you to deploy new v ### Using Fargate L2 constructs for Blue/Green Feature ```ts +import * as lambda from 'aws-cdk-lib/aws-lambda'; + declare const cluster: ecs.Cluster; declare const taskDefinition: ecs.TaskDefinition; declare const lambdaHook: lambda.Function; +declare const blueTargetGroup: elbv2.ApplicationTargetGroup; declare const greenTargetGroup: elbv2.ApplicationTargetGroup; declare const prodListenerRule: elbv2.ApplicationListenerRule; From ead8e5ff35337761651ff3b73a8ec180f5349b8e Mon Sep 17 00:00:00 2001 From: Kishan Gajjar Date: Fri, 1 Aug 2025 09:41:14 -0400 Subject: [PATCH 7/8] address CR feedback --- .../test/base/integ.blue-green-deployment-strategy.ts | 2 +- .../aws-ecs/lib/alternate-target-configuration.ts | 10 ++-------- packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts | 5 ++--- .../test/alternate-target-configuration.test.ts | 8 ++++---- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.ts index b72508efa92d5..5e839e5cdab30 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.blue-green-deployment-strategy.ts @@ -131,7 +131,7 @@ const target = service.loadBalancerTarget({ containerName: 'nginx', containerPort: 80, protocol: ecs.Protocol.TCP, -}, new ecs.AlternateTarget({ +}, new ecs.AlternateTarget('LBAlternateOptions', { alternateTargetGroup: greenTargetGroup, productionListener: ecs.ListenerRuleConfiguration.applicationListenerRule(prodListenerRule), })); diff --git a/packages/aws-cdk-lib/aws-ecs/lib/alternate-target-configuration.ts b/packages/aws-cdk-lib/aws-ecs/lib/alternate-target-configuration.ts index 816e15289ab1e..20c274dd59f27 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/alternate-target-configuration.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/alternate-target-configuration.ts @@ -108,12 +108,6 @@ export interface AlternateTargetOptions { * Properties for AlternateTarget configuration */ export interface AlternateTargetProps extends AlternateTargetOptions { - /** - * Id for alternate target options - * @default - none - */ - readonly id?: string; - /** * The alternate target group */ @@ -129,7 +123,7 @@ export interface AlternateTargetProps extends AlternateTargetOptions { * Configuration for alternate target groups used in blue/green deployments with load balancers */ export class AlternateTarget implements IAlternateTarget { - constructor(private readonly props: AlternateTargetProps) {} + constructor(private readonly id: string, private readonly props: AlternateTargetProps) { } /** * Bind this configuration to a service @@ -144,7 +138,7 @@ export class AlternateTarget implements IAlternateTarget { resources.push(this.props.testListener._listenerArn); } - const roleId = this.props.id? `${this.props.id}Role`: 'LBAlternateOptionsRole'; + const roleId = `${this.id}Role`; const role = this.props.role ?? new iam.Role(scope, roleId, { assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'), managedPolicies: [ diff --git a/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts b/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts index 034c9f5020847..e5ca814ff7957 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/base/base-service.ts @@ -1285,14 +1285,13 @@ export abstract class BaseService extends Resource const self = this; const target = this.taskDefinition._validateTarget(options); const connections = self.connections; - const alternateOption = alternateOptions; return { attachToApplicationTargetGroup(targetGroup: elbv2.ApplicationTargetGroup): elbv2.LoadBalancerTargetProps { targetGroup.registerConnectable(self, self.taskDefinition._portRangeFromPortMapping(target.portMapping)); - return self.attachToELBv2(targetGroup, target.containerName, target.portMapping.containerPort!, alternateOption); + return self.attachToELBv2(targetGroup, target.containerName, target.portMapping.containerPort!, alternateOptions); }, attachToNetworkTargetGroup(targetGroup: elbv2.NetworkTargetGroup): elbv2.LoadBalancerTargetProps { - return self.attachToELBv2(targetGroup, target.containerName, target.portMapping.containerPort!, alternateOption); + return self.attachToELBv2(targetGroup, target.containerName, target.portMapping.containerPort!, alternateOptions); }, connections, attachToClassicLB(loadBalancer: elb.LoadBalancer): void { diff --git a/packages/aws-cdk-lib/aws-ecs/test/alternate-target-configuration.test.ts b/packages/aws-cdk-lib/aws-ecs/test/alternate-target-configuration.test.ts index 801912c2abd96..5cfc4179d1748 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/alternate-target-configuration.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/alternate-target-configuration.test.ts @@ -69,7 +69,7 @@ describe('AlternateTarget', () => { }); // WHEN - const alternateTarget = new ecs.AlternateTarget({ + const alternateTarget = new ecs.AlternateTarget('GreenTG', { alternateTargetGroup: greenTargetGroup, productionListener: ecs.ListenerRuleConfiguration.applicationListenerRule(prodRule), }); @@ -111,7 +111,7 @@ describe('AlternateTarget', () => { }); // WHEN - const alternateTarget = new ecs.AlternateTarget({ + const alternateTarget = new ecs.AlternateTarget('GreenTG', { alternateTargetGroup: greenTargetGroup, productionListener: ecs.ListenerRuleConfiguration.applicationListenerRule(prodRule), testListener: ecs.ListenerRuleConfiguration.applicationListenerRule(testRule), @@ -163,7 +163,7 @@ describe('AlternateTarget', () => { }); // WHEN - const alternateTarget = new ecs.AlternateTarget({ + const alternateTarget = new ecs.AlternateTarget('GreenTG', { alternateTargetGroup: greenTargetGroup, productionListener: ecs.ListenerRuleConfiguration.applicationListenerRule(prodRule), role: customRole, @@ -230,7 +230,7 @@ describe('AlternateTarget', () => { }); // WHEN - const alternateTarget = new ecs.AlternateTarget({ + const alternateTarget = new ecs.AlternateTarget('GreenTG', { alternateTargetGroup: nlbGreenTargetGroup, productionListener: ecs.ListenerRuleConfiguration.networkListener(nlbListener), }); From 66cdfc4cf75008491eb135823040db56bafc06b4 Mon Sep 17 00:00:00 2001 From: Kishan Gajjar Date: Fri, 1 Aug 2025 10:18:22 -0400 Subject: [PATCH 8/8] fix Readme file --- packages/aws-cdk-lib/aws-ecs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-ecs/README.md b/packages/aws-cdk-lib/aws-ecs/README.md index 7ed4a3ab1d09b..5dc0119b25376 100644 --- a/packages/aws-cdk-lib/aws-ecs/README.md +++ b/packages/aws-cdk-lib/aws-ecs/README.md @@ -2102,7 +2102,7 @@ const target = service.loadBalancerTarget({ containerName: 'nginx', containerPort: 80, protocol: ecs.Protocol.TCP, -}, new ecs.AlternateTarget({ +}, new ecs.AlternateTarget('AlternateTarget', { alternateTargetGroup: greenTargetGroup, productionListener: ecs.ListenerRuleConfiguration.applicationListenerRule(prodListenerRule), }));