diff --git a/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts b/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts index 07e369d2464de..b0cc895c7c4a1 100644 --- a/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts +++ b/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts @@ -36,829 +36,836 @@ const enableExecuteCommandPermissions = { Version: '2012-10-17', }; -describe('When Application Load Balancer', () => { - test('test Fargate loadbalanced construct with default settings', () => { - // GIVEN - const stack = new Stack(); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ApplicationMultipleTargetGroupsFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - }, - }); - - // THEN - stack contains a load balancer and a service - Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 1); - - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - LaunchType: 'FARGATE', - LoadBalancers: [ - { - ContainerName: 'web', - ContainerPort: 80, - TargetGroupArn: { - Ref: 'ServiceLBPublicListenerECSGroup0CC8688C', - }, +describe('Application Load Balancer', () => { + describe('ApplicationLoadBalancedFargateService', () => { + test('construct with application load balancer name set', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), }, - ], - }); + loadBalancerName: 'alb-test-load-balancer', + }); - Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { - ContainerDefinitions: [ - Match.objectLike({ - Image: 'test', - LogConfiguration: { - LogDriver: 'awslogs', - Options: { - 'awslogs-group': { - Ref: 'ServiceTaskDefwebLogGroup2A898F61', - }, - 'awslogs-stream-prefix': 'Service', - 'awslogs-region': { - Ref: 'AWS::Region', - }, + // THEN - stack contains a load balancer and a service + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { + Name: 'alb-test-load-balancer', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + LaunchType: 'FARGATE', + LoadBalancers: [ + { + ContainerName: 'web', + ContainerPort: 80, + TargetGroupArn: { + Ref: 'ServiceLBPublicListenerECSGroup0CC8688C', }, }, - Name: 'web', - PortMappings: [ - { - ContainerPort: 80, - Protocol: 'tcp', + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + Match.objectLike({ + Image: 'test', + LogConfiguration: { + LogDriver: 'awslogs', + Options: { + 'awslogs-group': { + Ref: 'ServiceTaskDefwebLogGroup2A898F61', + }, + 'awslogs-stream-prefix': 'Service', + 'awslogs-region': { + Ref: 'AWS::Region', + }, + }, }, + Name: 'web', + PortMappings: [ + { + ContainerPort: 80, + Protocol: 'tcp', + }, + ], + }), + ], + Cpu: '256', + ExecutionRoleArn: { + 'Fn::GetAtt': [ + 'ServiceTaskDefExecutionRole919F7BE3', + 'Arn', ], - }), - ], - Cpu: '256', - ExecutionRoleArn: { - 'Fn::GetAtt': [ - 'ServiceTaskDefExecutionRole919F7BE3', - 'Arn', + }, + Family: 'ServiceTaskDef79D79521', + Memory: '512', + NetworkMode: 'awsvpc', + RequiresCompatibilities: [ + 'FARGATE', ], - }, - Family: 'ServiceTaskDef79D79521', - Memory: '512', - NetworkMode: 'awsvpc', - RequiresCompatibilities: [ - 'FARGATE', - ], + }); }); }); - test('test Fargate loadbalanced construct with all settings', () => { - // GIVEN - const stack = new Stack(); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + describe('ApplicationMultipleTargetGroupsFargateService', () => { + test('construct with default settings', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - // WHEN - new ApplicationMultipleTargetGroupsFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - containerName: 'hello', - containerPorts: [80, 90], - enableLogging: false, - environment: { - TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', - TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', - }, - logDriver: new ecs.AwsLogDriver({ - streamPrefix: 'TestStream', - }), - family: 'Ec2TaskDef', - executionRole: new Role(stack, 'ExecutionRole', { - path: '/', - assumedBy: new CompositePrincipal( - new ServicePrincipal('ecs.amazonaws.com'), - new ServicePrincipal('ecs-tasks.amazonaws.com'), - ), - }), - taskRole: new Role(stack, 'TaskRole', { - assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'), - }), - dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, - }, - cpu: 256, - assignPublicIp: true, - memoryLimitMiB: 512, - desiredCount: 3, - enableECSManagedTags: true, - enableExecuteCommand: true, - healthCheckGracePeriod: Duration.millis(2000), - platformVersion: ecs.FargatePlatformVersion.VERSION1_4, - propagateTags: ecs.PropagatedTagSource.SERVICE, - serviceName: 'myService', - targetGroups: [ - { - containerPort: 80, - }, - { - containerPort: 90, - pathPattern: 'a/b/c', - priority: 10, - protocol: ecs.Protocol.TCP, + // WHEN + new ApplicationMultipleTargetGroupsFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), }, - ], - }); + }); - // THEN - stack contains a load balancer and a service - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DesiredCount: 3, - EnableECSManagedTags: true, - HealthCheckGracePeriodSeconds: 2, - LaunchType: 'FARGATE', - LoadBalancers: [ - { - ContainerName: 'hello', - ContainerPort: 80, - TargetGroupArn: { - Ref: 'ServiceLBPublicListenerECSTargetGrouphello80Group233A4D54', - }, - }, - { - ContainerName: 'hello', - ContainerPort: 90, - TargetGroupArn: { - Ref: 'ServiceLBPublicListenerECSTargetGrouphello90GroupE58E4EAB', - }, - }, - ], - NetworkConfiguration: { - AwsvpcConfiguration: { - AssignPublicIp: 'ENABLED', - SecurityGroups: [ - { - 'Fn::GetAtt': [ - 'ServiceSecurityGroupEEA09B68', - 'GroupId', - ], - }, - ], - Subnets: [ - { - Ref: 'VPCPublicSubnet1SubnetB4246D30', + // THEN - stack contains a load balancer and a service + Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 1); + + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + LaunchType: 'FARGATE', + LoadBalancers: [ + { + ContainerName: 'web', + ContainerPort: 80, + TargetGroupArn: { + Ref: 'ServiceLBPublicListenerECSGroup0CC8688C', }, - { - Ref: 'VPCPublicSubnet2Subnet74179F39', + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + Match.objectLike({ + Image: 'test', + LogConfiguration: { + LogDriver: 'awslogs', + Options: { + 'awslogs-group': { + Ref: 'ServiceTaskDefwebLogGroup2A898F61', + }, + 'awslogs-stream-prefix': 'Service', + 'awslogs-region': { + Ref: 'AWS::Region', + }, + }, }, + Name: 'web', + PortMappings: [ + { + ContainerPort: 80, + Protocol: 'tcp', + }, + ], + }), + ], + Cpu: '256', + ExecutionRoleArn: { + 'Fn::GetAtt': [ + 'ServiceTaskDefExecutionRole919F7BE3', + 'Arn', ], }, - }, - PlatformVersion: ecs.FargatePlatformVersion.VERSION1_4, - PropagateTags: 'SERVICE', - ServiceName: 'myService', + Family: 'ServiceTaskDef79D79521', + Memory: '512', + NetworkMode: 'awsvpc', + RequiresCompatibilities: [ + 'FARGATE', + ], + }); }); - // ECS Exec - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { - PolicyDocument: enableExecuteCommandPermissions, - PolicyName: 'TaskRoleDefaultPolicy07FC53DE', - Roles: [ - { - Ref: 'TaskRole30FC0FBB', + test('construct with all settings', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ApplicationMultipleTargetGroupsFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + containerName: 'hello', + containerPorts: [80, 90], + enableLogging: false, + environment: { + TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', + TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', + }, + logDriver: new ecs.AwsLogDriver({ + streamPrefix: 'TestStream', + }), + family: 'Ec2TaskDef', + executionRole: new Role(stack, 'ExecutionRole', { + path: '/', + assumedBy: new CompositePrincipal( + new ServicePrincipal('ecs.amazonaws.com'), + new ServicePrincipal('ecs-tasks.amazonaws.com'), + ), + }), + taskRole: new Role(stack, 'TaskRole', { + assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'), + }), + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, }, - ], - }); + cpu: 256, + assignPublicIp: true, + memoryLimitMiB: 512, + desiredCount: 3, + enableECSManagedTags: true, + enableExecuteCommand: true, + healthCheckGracePeriod: Duration.millis(2000), + platformVersion: ecs.FargatePlatformVersion.VERSION1_4, + propagateTags: ecs.PropagatedTagSource.SERVICE, + serviceName: 'myService', + targetGroups: [ + { + containerPort: 80, + }, + { + containerPort: 90, + pathPattern: 'a/b/c', + priority: 10, + protocol: ecs.Protocol.TCP, + }, + ], + }); - Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { - ContainerDefinitions: [ - { - Environment: [ - { - Name: 'TEST_ENVIRONMENT_VARIABLE1', - Value: 'test environment variable 1 value', + // THEN - stack contains a load balancer and a service + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DesiredCount: 3, + EnableECSManagedTags: true, + HealthCheckGracePeriodSeconds: 2, + LaunchType: 'FARGATE', + LoadBalancers: [ + { + ContainerName: 'hello', + ContainerPort: 80, + TargetGroupArn: { + Ref: 'ServiceLBPublicListenerECSTargetGrouphello80Group233A4D54', }, - { - Name: 'TEST_ENVIRONMENT_VARIABLE2', - Value: 'test environment variable 2 value', + }, + { + ContainerName: 'hello', + ContainerPort: 90, + TargetGroupArn: { + Ref: 'ServiceLBPublicListenerECSTargetGrouphello90GroupE58E4EAB', }, - ], - Essential: true, - Image: 'test', - LogConfiguration: { - LogDriver: 'awslogs', - Options: { - 'awslogs-group': { - Ref: 'ServiceTaskDefhelloLogGroup44519781', + }, + ], + NetworkConfiguration: { + AwsvpcConfiguration: { + AssignPublicIp: 'ENABLED', + SecurityGroups: [ + { + 'Fn::GetAtt': [ + 'ServiceSecurityGroupEEA09B68', + 'GroupId', + ], }, - 'awslogs-stream-prefix': 'TestStream', - 'awslogs-region': { - Ref: 'AWS::Region', + ], + Subnets: [ + { + Ref: 'VPCPublicSubnet1SubnetB4246D30', }, - }, + { + Ref: 'VPCPublicSubnet2Subnet74179F39', + }, + ], + }, + }, + PlatformVersion: ecs.FargatePlatformVersion.VERSION1_4, + PropagateTags: 'SERVICE', + ServiceName: 'myService', + }); + + // ECS Exec + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: enableExecuteCommandPermissions, + PolicyName: 'TaskRoleDefaultPolicy07FC53DE', + Roles: [ + { + Ref: 'TaskRole30FC0FBB', }, - Name: 'hello', - PortMappings: [ - { - ContainerPort: 80, - Protocol: 'tcp', + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Environment: [ + { + Name: 'TEST_ENVIRONMENT_VARIABLE1', + Value: 'test environment variable 1 value', + }, + { + Name: 'TEST_ENVIRONMENT_VARIABLE2', + Value: 'test environment variable 2 value', + }, + ], + Essential: true, + Image: 'test', + LogConfiguration: { + LogDriver: 'awslogs', + Options: { + 'awslogs-group': { + Ref: 'ServiceTaskDefhelloLogGroup44519781', + }, + 'awslogs-stream-prefix': 'TestStream', + 'awslogs-region': { + Ref: 'AWS::Region', + }, + }, }, - { - ContainerPort: 90, - Protocol: 'tcp', + Name: 'hello', + PortMappings: [ + { + ContainerPort: 80, + Protocol: 'tcp', + }, + { + ContainerPort: 90, + Protocol: 'tcp', + }, + ], + DockerLabels: { + label1: 'labelValue1', + label2: 'labelValue2', }, - ], - DockerLabels: { - label1: 'labelValue1', - label2: 'labelValue2', }, - }, - ], - Cpu: '256', - ExecutionRoleArn: { - 'Fn::GetAtt': [ - 'ExecutionRole605A040B', - 'Arn', ], - }, - Family: 'Ec2TaskDef', - Memory: '512', - NetworkMode: 'awsvpc', - RequiresCompatibilities: [ - 'FARGATE', - ], - TaskRoleArn: { - 'Fn::GetAtt': [ - 'TaskRole30FC0FBB', - 'Arn', + Cpu: '256', + ExecutionRoleArn: { + 'Fn::GetAtt': [ + 'ExecutionRole605A040B', + 'Arn', + ], + }, + Family: 'Ec2TaskDef', + Memory: '512', + NetworkMode: 'awsvpc', + RequiresCompatibilities: [ + 'FARGATE', ], - }, + TaskRoleArn: { + 'Fn::GetAtt': [ + 'TaskRole30FC0FBB', + 'Arn', + ], + }, + }); }); - }); - test('errors if no essential container in pre-defined task definition', () => { - // GIVEN - const stack = new Stack(); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + test('errors if no essential container in pre-defined task definition', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + // THEN + expect(() => { + new ApplicationMultipleTargetGroupsFargateService(stack, 'Service', { + cluster, + taskDefinition, + }); + }).toThrow(/At least one essential container must be specified/); + }); - const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + test('errors when setting both taskDefinition and taskImageOptions', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'Ec2TaskDef'); + + // THEN + expect(() => { + new ApplicationMultipleTargetGroupsFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + }, + taskDefinition, + }); + }).toThrow(/You must specify only one of TaskDefinition or TaskImageOptions./); + }); - // THEN - expect(() => { - new ApplicationMultipleTargetGroupsFargateService(stack, 'Service', { - cluster, - taskDefinition, - }); - }).toThrow(/At least one essential container must be specified/); + test('errors when setting neither taskDefinition nor taskImageOptions', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // THEN + expect(() => { + new ApplicationMultipleTargetGroupsFargateService(stack, 'Service', { + cluster, + }); + }).toThrow(/You must specify one of: taskDefinition or image/); + }); }); +}); - test('errors when setting both taskDefinition and taskImageOptions', () => { - // GIVEN - const stack = new Stack(); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - const taskDefinition = new ecs.FargateTaskDefinition(stack, 'Ec2TaskDef'); - - // THEN - expect(() => { - new ApplicationMultipleTargetGroupsFargateService(stack, 'Service', { - cluster, +describe('Network Load Balancer', () => { + describe('NetworkLoadBalancedFargateService', () => { + test('construct with custom port', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + new NetworkLoadBalancedFargateService(stack, 'NLBService', { + cluster: cluster, + memoryLimitMiB: 1024, + cpu: 512, taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), + image: ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + containerPort: 81, }, - taskDefinition, + listenerPort: 8181, }); - }).toThrow(/You must specify only one of TaskDefinition or TaskImageOptions./); - }); - test('errors when setting neither taskDefinition nor taskImageOptions', () => { - // GIVEN - const stack = new Stack(); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // THEN - expect(() => { - new ApplicationMultipleTargetGroupsFargateService(stack, 'Service', { - cluster, + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { + Port: 81, + Protocol: 'TCP', + TargetType: 'ip', + VpcId: { + Ref: 'VPCB9E5F0B4', + }, }); - }).toThrow(/You must specify one of: taskDefinition or image/); - }); - - test('test Fargate loadbalancer construct with application load balancer name set', () => { - // GIVEN - const stack = new Stack(); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ApplicationLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - }, - loadBalancerName: 'alb-test-load-balancer', }); + }); - // THEN - stack contains a load balancer and a service - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { - Name: 'alb-test-load-balancer', - }); + describe('NetworkMultipleTargetGroupsFargateService', () => { + test('construct with default settings', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - LaunchType: 'FARGATE', - LoadBalancers: [ - { - ContainerName: 'web', - ContainerPort: 80, - TargetGroupArn: { - Ref: 'ServiceLBPublicListenerECSGroup0CC8688C', - }, + // WHEN + new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), }, - ], - }); + }); - Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { - ContainerDefinitions: [ - Match.objectLike({ - Image: 'test', - LogConfiguration: { - LogDriver: 'awslogs', - Options: { - 'awslogs-group': { - Ref: 'ServiceTaskDefwebLogGroup2A898F61', - }, - 'awslogs-stream-prefix': 'Service', - 'awslogs-region': { - Ref: 'AWS::Region', - }, + // THEN - stack contains a load balancer and a service + Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 1); + + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + LaunchType: 'FARGATE', + LoadBalancers: [ + { + ContainerName: 'web', + ContainerPort: 80, + TargetGroupArn: { + Ref: 'ServiceLBPublicListenerECSGroup0CC8688C', }, }, - Name: 'web', - PortMappings: [ - { - ContainerPort: 80, - Protocol: 'tcp', - }, - ], - }), - ], - Cpu: '256', - ExecutionRoleArn: { - 'Fn::GetAtt': [ - 'ServiceTaskDefExecutionRole919F7BE3', - 'Arn', ], - }, - Family: 'ServiceTaskDef79D79521', - Memory: '512', - NetworkMode: 'awsvpc', - RequiresCompatibilities: [ - 'FARGATE', - ], - }); - }); -}); - -describe('When Network Load Balancer', () => { - test('test Fargate loadbalanced construct with default settings', () => { - // GIVEN - const stack = new Stack(); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - }, - }); - - // THEN - stack contains a load balancer and a service - Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 1); - - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - LaunchType: 'FARGATE', - LoadBalancers: [ - { - ContainerName: 'web', - ContainerPort: 80, - TargetGroupArn: { - Ref: 'ServiceLBPublicListenerECSGroup0CC8688C', - }, - }, - ], - }); + }); - Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { - ContainerDefinitions: [ - Match.objectLike({ - Image: 'test', - LogConfiguration: { - LogDriver: 'awslogs', - Options: { - 'awslogs-group': { - Ref: 'ServiceTaskDefwebLogGroup2A898F61', - }, - 'awslogs-stream-prefix': 'Service', - 'awslogs-region': { - Ref: 'AWS::Region', + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + Match.objectLike({ + Image: 'test', + LogConfiguration: { + LogDriver: 'awslogs', + Options: { + 'awslogs-group': { + Ref: 'ServiceTaskDefwebLogGroup2A898F61', + }, + 'awslogs-stream-prefix': 'Service', + 'awslogs-region': { + Ref: 'AWS::Region', + }, }, }, - }, - Name: 'web', - PortMappings: [ - { - ContainerPort: 80, - Protocol: 'tcp', - }, + Name: 'web', + PortMappings: [ + { + ContainerPort: 80, + Protocol: 'tcp', + }, + ], + }), + ], + Cpu: '256', + ExecutionRoleArn: { + 'Fn::GetAtt': [ + 'ServiceTaskDefExecutionRole919F7BE3', + 'Arn', ], - }), - ], - Cpu: '256', - ExecutionRoleArn: { - 'Fn::GetAtt': [ - 'ServiceTaskDefExecutionRole919F7BE3', - 'Arn', + }, + Family: 'ServiceTaskDef79D79521', + Memory: '512', + NetworkMode: 'awsvpc', + RequiresCompatibilities: [ + 'FARGATE', ], - }, - Family: 'ServiceTaskDef79D79521', - Memory: '512', - NetworkMode: 'awsvpc', - RequiresCompatibilities: [ - 'FARGATE', - ], + }); }); - }); - - test('test Fargate loadbalanced construct with all settings', () => { - // GIVEN - const stack = new Stack(); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - // WHEN - new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - containerName: 'hello', - containerPorts: [80, 90], - enableLogging: false, - environment: { - TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', - TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', - }, - logDriver: new ecs.AwsLogDriver({ - streamPrefix: 'TestStream', - }), - family: 'Ec2TaskDef', - executionRole: new Role(stack, 'ExecutionRole', { - path: '/', - assumedBy: new CompositePrincipal( - new ServicePrincipal('ecs.amazonaws.com'), - new ServicePrincipal('ecs-tasks.amazonaws.com'), - ), - }), - taskRole: new Role(stack, 'TaskRole', { - assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'), - }), - dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, - }, - cpu: 256, - assignPublicIp: true, - memoryLimitMiB: 512, - desiredCount: 3, - enableECSManagedTags: true, - enableExecuteCommand: true, - healthCheckGracePeriod: Duration.millis(2000), - propagateTags: ecs.PropagatedTagSource.SERVICE, - serviceName: 'myService', - targetGroups: [ - { - containerPort: 80, - }, - { - containerPort: 90, - }, - ], - }); + test('construct with all settings', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - // THEN - stack contains a load balancer and a service - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DesiredCount: 3, - EnableECSManagedTags: true, - HealthCheckGracePeriodSeconds: 2, - LaunchType: 'FARGATE', - LoadBalancers: [ - { - ContainerName: 'hello', - ContainerPort: 80, - TargetGroupArn: { - Ref: 'ServiceLBPublicListenerECSTargetGrouphello80Group233A4D54', + // WHEN + new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + containerName: 'hello', + containerPorts: [80, 90], + enableLogging: false, + environment: { + TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', + TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', }, + logDriver: new ecs.AwsLogDriver({ + streamPrefix: 'TestStream', + }), + family: 'Ec2TaskDef', + executionRole: new Role(stack, 'ExecutionRole', { + path: '/', + assumedBy: new CompositePrincipal( + new ServicePrincipal('ecs.amazonaws.com'), + new ServicePrincipal('ecs-tasks.amazonaws.com'), + ), + }), + taskRole: new Role(stack, 'TaskRole', { + assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'), + }), + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, }, - { - ContainerName: 'hello', - ContainerPort: 90, - TargetGroupArn: { - Ref: 'ServiceLBPublicListenerECSTargetGrouphello90GroupE58E4EAB', + cpu: 256, + assignPublicIp: true, + memoryLimitMiB: 512, + desiredCount: 3, + enableECSManagedTags: true, + enableExecuteCommand: true, + healthCheckGracePeriod: Duration.millis(2000), + propagateTags: ecs.PropagatedTagSource.SERVICE, + serviceName: 'myService', + targetGroups: [ + { + containerPort: 80, }, - }, - ], - NetworkConfiguration: { - AwsvpcConfiguration: { - AssignPublicIp: 'ENABLED', - SecurityGroups: [ - { - 'Fn::GetAtt': [ - 'ServiceSecurityGroupEEA09B68', - 'GroupId', - ], - }, - ], - Subnets: [ - { - Ref: 'VPCPublicSubnet1SubnetB4246D30', + { + containerPort: 90, + }, + ], + }); + + // THEN - stack contains a load balancer and a service + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DesiredCount: 3, + EnableECSManagedTags: true, + HealthCheckGracePeriodSeconds: 2, + LaunchType: 'FARGATE', + LoadBalancers: [ + { + ContainerName: 'hello', + ContainerPort: 80, + TargetGroupArn: { + Ref: 'ServiceLBPublicListenerECSTargetGrouphello80Group233A4D54', }, - { - Ref: 'VPCPublicSubnet2Subnet74179F39', + }, + { + ContainerName: 'hello', + ContainerPort: 90, + TargetGroupArn: { + Ref: 'ServiceLBPublicListenerECSTargetGrouphello90GroupE58E4EAB', }, - ], + }, + ], + NetworkConfiguration: { + AwsvpcConfiguration: { + AssignPublicIp: 'ENABLED', + SecurityGroups: [ + { + 'Fn::GetAtt': [ + 'ServiceSecurityGroupEEA09B68', + 'GroupId', + ], + }, + ], + Subnets: [ + { + Ref: 'VPCPublicSubnet1SubnetB4246D30', + }, + { + Ref: 'VPCPublicSubnet2Subnet74179F39', + }, + ], + }, }, - }, - PropagateTags: 'SERVICE', - ServiceName: 'myService', - }); + PropagateTags: 'SERVICE', + ServiceName: 'myService', + }); - Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { - ContainerDefinitions: [ - { - Environment: [ - { - Name: 'TEST_ENVIRONMENT_VARIABLE1', - Value: 'test environment variable 1 value', - }, - { - Name: 'TEST_ENVIRONMENT_VARIABLE2', - Value: 'test environment variable 2 value', + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Environment: [ + { + Name: 'TEST_ENVIRONMENT_VARIABLE1', + Value: 'test environment variable 1 value', + }, + { + Name: 'TEST_ENVIRONMENT_VARIABLE2', + Value: 'test environment variable 2 value', + }, + ], + Essential: true, + Image: 'test', + LogConfiguration: { + LogDriver: 'awslogs', + Options: { + 'awslogs-group': { + Ref: 'ServiceTaskDefhelloLogGroup44519781', + }, + 'awslogs-stream-prefix': 'TestStream', + 'awslogs-region': { + Ref: 'AWS::Region', + }, + }, }, - ], - Essential: true, - Image: 'test', - LogConfiguration: { - LogDriver: 'awslogs', - Options: { - 'awslogs-group': { - Ref: 'ServiceTaskDefhelloLogGroup44519781', + Name: 'hello', + PortMappings: [ + { + ContainerPort: 80, + Protocol: 'tcp', }, - 'awslogs-stream-prefix': 'TestStream', - 'awslogs-region': { - Ref: 'AWS::Region', + { + ContainerPort: 90, + Protocol: 'tcp', }, + ], + DockerLabels: { + label1: 'labelValue1', + label2: 'labelValue2', }, }, - Name: 'hello', - PortMappings: [ - { - ContainerPort: 80, - Protocol: 'tcp', - }, - { - ContainerPort: 90, - Protocol: 'tcp', - }, + ], + Cpu: '256', + ExecutionRoleArn: { + 'Fn::GetAtt': [ + 'ExecutionRole605A040B', + 'Arn', ], - DockerLabels: { - label1: 'labelValue1', - label2: 'labelValue2', - }, }, - ], - Cpu: '256', - ExecutionRoleArn: { - 'Fn::GetAtt': [ - 'ExecutionRole605A040B', - 'Arn', + Family: 'Ec2TaskDef', + Memory: '512', + NetworkMode: 'awsvpc', + RequiresCompatibilities: [ + 'FARGATE', ], - }, - Family: 'Ec2TaskDef', - Memory: '512', - NetworkMode: 'awsvpc', - RequiresCompatibilities: [ - 'FARGATE', - ], - TaskRoleArn: { - 'Fn::GetAtt': [ - 'TaskRole30FC0FBB', - 'Arn', - ], - }, - }); - }); - - test('EnableExecuteCommand generates correct IAM Permissions', () => { - // GIVEN - const stack = new Stack(); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - containerName: 'hello', - containerPorts: [80, 90], - enableLogging: false, - environment: { - TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', - TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', - }, - logDriver: new ecs.AwsLogDriver({ - streamPrefix: 'TestStream', - }), - family: 'Ec2TaskDef', - executionRole: new Role(stack, 'ExecutionRole', { - path: '/', - assumedBy: new CompositePrincipal( - new ServicePrincipal('ecs.amazonaws.com'), - new ServicePrincipal('ecs-tasks.amazonaws.com'), - ), - }), - taskRole: new Role(stack, 'TaskRole', { - assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'), - }), - dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, - }, - cpu: 256, - assignPublicIp: true, - memoryLimitMiB: 512, - desiredCount: 3, - enableECSManagedTags: true, - enableExecuteCommand: true, - healthCheckGracePeriod: Duration.millis(2000), - propagateTags: ecs.PropagatedTagSource.SERVICE, - serviceName: 'myService', - targetGroups: [ - { - containerPort: 80, - }, - { - containerPort: 90, - }, - ], - }); - - // ECS Exec - Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { - PolicyDocument: enableExecuteCommandPermissions, - PolicyName: 'TaskRoleDefaultPolicy07FC53DE', - Roles: [ - { - Ref: 'TaskRole30FC0FBB', + TaskRoleArn: { + 'Fn::GetAtt': [ + 'TaskRole30FC0FBB', + 'Arn', + ], }, - ], - }); - }); - - test('errors if no essential container in pre-defined task definition', () => { - // GIVEN - const stack = new Stack(); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); - - // THEN - expect(() => { - new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { - cluster, - taskDefinition, }); - }).toThrow(/At least one essential container must be specified/); - }); + }); - test('errors when setting both taskDefinition and taskImageOptions', () => { - // GIVEN - const stack = new Stack(); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - const taskDefinition = new ecs.FargateTaskDefinition(stack, 'Ec2TaskDef'); + test('EnableExecuteCommand generates correct IAM Permissions', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - // THEN - expect(() => { + // WHEN new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { cluster, taskImageOptions: { image: ecs.ContainerImage.fromRegistry('test'), + containerName: 'hello', + containerPorts: [80, 90], + enableLogging: false, + environment: { + TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', + TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', + }, + logDriver: new ecs.AwsLogDriver({ + streamPrefix: 'TestStream', + }), + family: 'Ec2TaskDef', + executionRole: new Role(stack, 'ExecutionRole', { + path: '/', + assumedBy: new CompositePrincipal( + new ServicePrincipal('ecs.amazonaws.com'), + new ServicePrincipal('ecs-tasks.amazonaws.com'), + ), + }), + taskRole: new Role(stack, 'TaskRole', { + assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'), + }), + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, }, - taskDefinition, + cpu: 256, + assignPublicIp: true, + memoryLimitMiB: 512, + desiredCount: 3, + enableECSManagedTags: true, + enableExecuteCommand: true, + healthCheckGracePeriod: Duration.millis(2000), + propagateTags: ecs.PropagatedTagSource.SERVICE, + serviceName: 'myService', + targetGroups: [ + { + containerPort: 80, + }, + { + containerPort: 90, + }, + ], }); - }).toThrow(/You must specify only one of TaskDefinition or TaskImageOptions./); - }); - test('errors when setting neither taskDefinition nor taskImageOptions', () => { - // GIVEN - const stack = new Stack(); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // THEN - expect(() => { - new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { - cluster, + // ECS Exec + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: enableExecuteCommandPermissions, + PolicyName: 'TaskRoleDefaultPolicy07FC53DE', + Roles: [ + { + Ref: 'TaskRole30FC0FBB', + }, + ], }); - }).toThrow(/You must specify one of: taskDefinition or image/); - }); - - test('test Fargate networkloadbalanced construct with custom Port', () => { - // GIVEN - const stack = new Stack(); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - new NetworkLoadBalancedFargateService(stack, 'NLBService', { - cluster: cluster, - memoryLimitMiB: 1024, - cpu: 512, - taskImageOptions: { - image: ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - containerPort: 81, - }, - listenerPort: 8181, }); - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { - Port: 81, - Protocol: 'TCP', - TargetType: 'ip', - VpcId: { - Ref: 'VPCB9E5F0B4', - }, + test('errors if no essential container in pre-defined task definition', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + // THEN + expect(() => { + new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { + cluster, + taskDefinition, + }); + }).toThrow(/At least one essential container must be specified/); }); - }); - test('test Fargate multinetworkloadbalanced construct with custom Port', () => { - // GIVEN - const stack = new Stack(); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + test('errors when setting both taskDefinition and taskImageOptions', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'Ec2TaskDef'); + + // THEN + expect(() => { + new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + }, + taskDefinition, + }); + }).toThrow(/You must specify only one of TaskDefinition or TaskImageOptions./); + }); - new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - }, + test('errors when setting neither taskDefinition nor taskImageOptions', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // THEN + expect(() => { + new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { + cluster, + }); + }).toThrow(/You must specify one of: taskDefinition or image/); }); - new NetworkMultipleTargetGroupsFargateService(stack, 'NLBService', { - cluster: cluster, - memoryLimitMiB: 1024, - cpu: 512, - taskImageOptions: { - image: ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - loadBalancers: [ - { - name: 'lb1', - listeners: [ - { name: 'listener1', port: 8181 }, - ], + test('construct with custom port', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), }, - ], - targetGroups: [{ - containerPort: 81, - }], - }); + }); - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { - Port: 81, - Protocol: 'TCP', - TargetType: 'ip', - VpcId: { - Ref: 'VPCB9E5F0B4', - }, - }); - }); + new NetworkMultipleTargetGroupsFargateService(stack, 'NLBService', { + cluster: cluster, + memoryLimitMiB: 1024, + cpu: 512, + taskImageOptions: { + image: ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, + loadBalancers: [ + { + name: 'lb1', + listeners: [ + { name: 'listener1', port: 8181 }, + ], + }, + ], + targetGroups: [{ + containerPort: 81, + }], + }); - test('Fargate multinetworkloadbalanced construct errors when container port range is set for essential container', () => { - // GIVEN - const stack = new Stack(); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); - - taskDefinition.addContainer('MainContainer', { - image: ecs.ContainerImage.fromRegistry('test'), - portMappings: [{ - containerPort: ContainerDefinition.CONTAINER_PORT_USE_RANGE, - containerPortRange: '8080-8081', - }], + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { + Port: 81, + Protocol: 'TCP', + TargetType: 'ip', + VpcId: { + Ref: 'VPCB9E5F0B4', + }, + }); }); - // THEN - expect(() => { - new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { - cluster, - taskDefinition, + test('construct errors when container port range is set for essential container', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + taskDefinition.addContainer('MainContainer', { + image: ecs.ContainerImage.fromRegistry('test'), + portMappings: [{ + containerPort: ContainerDefinition.CONTAINER_PORT_USE_RANGE, + containerPortRange: '8080-8081', + }], }); - }).toThrow('The first port mapping added to the default container must expose a single port'); + // THEN + expect(() => { + new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { + cluster, + taskDefinition, + }); + }).toThrow('The first port mapping added to the default container must expose a single port'); + }); }); }); diff --git a/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts b/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts index 06512c525d465..c103d75c9a448 100644 --- a/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts @@ -11,1286 +11,1290 @@ import * as route53 from '../../../aws-route53'; import * as cdk from '../../../core'; import * as ecsPatterns from '../../lib'; -test('setting loadBalancerType to Network creates an NLB Public', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { - Type: 'network', - Scheme: 'internet-facing', - }); -}); - -test('setting loadBalancerType to Network and publicLoadBalancer to false creates an NLB Private', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - }, - publicLoadBalancer: false, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { - Type: 'network', - Scheme: 'internal', - }); -}); - -test('setting vpc and cluster throws error', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - expect(() => new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { - cluster, - vpc, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - }, - })).toThrow(); -}); - -test('setting executionRole updated taskDefinition with given execution role', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - const executionRole = new iam.Role(stack, 'ExecutionRole', { - path: '/', - assumedBy: new iam.CompositePrincipal( - new iam.ServicePrincipal('ecs.amazonaws.com'), - new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), - ), - }); - - // WHEN - new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - executionRole, - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { - ExecutionRoleArn: { 'Fn::GetAtt': ['ExecutionRole605A040B', 'Arn'] }, - }); -}); - -test('setting taskRole updated taskDefinition with given task role', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - const taskRole = new iam.Role(stack, 'taskRoleTest', { - path: '/', - assumedBy: new iam.CompositePrincipal( - new iam.ServicePrincipal('ecs.amazonaws.com'), - new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), - ), - }); - - // WHEN - new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - taskRole, - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { - TaskRoleArn: { 'Fn::GetAtt': ['taskRoleTest9DA66B6E', 'Arn'] }, - }); -}); - -test('setting containerName updates container name with given name', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - containerName: 'bob', - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { - ContainerDefinitions: [ - Match.objectLike({ - Name: 'bob', - }), - ], - }); -}); - -test('not setting containerName updates container name with default', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { - ContainerDefinitions: [ - Match.objectLike({ - Name: 'web', - }), - ], - }); -}); - -test('setting servicename updates service name with given name', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - }, - serviceName: 'bob', - }); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - ServiceName: 'bob', - }); -}); - -test('not setting servicename updates service name with default', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - ServiceName: Match.absent(), - }); -}); - -test('setting healthCheckGracePeriod works', () => { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - }, - healthCheckGracePeriod: cdk.Duration.seconds(600), - }); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - HealthCheckGracePeriodSeconds: 600, - }); -}); - -test('setting healthCheck works', () => { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - }, - healthCheck: { - command: ['CMD-SHELL', 'curl -f http://localhost/ || exit 1'], - interval: cdk.Duration.minutes(1), - retries: 5, - startPeriod: cdk.Duration.minutes(1), - timeout: cdk.Duration.minutes(1), - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { - ContainerDefinitions: Match.arrayWith([ - Match.objectLike({ - HealthCheck: { - Command: ['CMD-SHELL', 'curl -f http://localhost/ || exit 1'], - Interval: 60, - Retries: 5, - StartPeriod: 60, - Timeout: 60, +describe('ApplicationLoadBalancedFargateService', () => { + test('setting healthCheckGracePeriod works', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + }, + healthCheckGracePeriod: cdk.Duration.seconds(600), + }); + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + HealthCheckGracePeriodSeconds: 600, + }); + }); + + test('setting healthCheck works', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + }, + healthCheck: { + command: ['CMD-SHELL', 'curl -f http://localhost/ || exit 1'], + interval: cdk.Duration.minutes(1), + retries: 5, + startPeriod: cdk.Duration.minutes(1), + timeout: cdk.Duration.minutes(1), + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: Match.arrayWith([ + Match.objectLike({ + HealthCheck: { + Command: ['CMD-SHELL', 'curl -f http://localhost/ || exit 1'], + Interval: 60, + Retries: 5, + StartPeriod: 60, + Timeout: 60, + }, + }), + ]), + }); + }); + + test('selecting correct vpcSubnets', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { + maxAzs: 2, + subnetConfiguration: [ + { + subnetType: ec2.SubnetType.PUBLIC, + cidrMask: 20, + name: 'Public', }, - }), - ]), - }); -}); - -test('selecting correct vpcSubnets', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc', { - maxAzs: 2, - subnetConfiguration: [ - { - subnetType: ec2.SubnetType.PUBLIC, - cidrMask: 20, - name: 'Public', + { + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, + cidrMask: 20, + name: 'ISOLATED', + }, + ], + }); + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), }, - { + taskSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED, - cidrMask: 20, - name: 'ISOLATED', }, - ], - }); - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - vpc, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - }, - taskSubnets: { - subnetType: ec2.SubnetType.PRIVATE_ISOLATED, - }, - }); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - NetworkConfiguration: { - AwsvpcConfiguration: { - Subnets: [ - { - Ref: 'VpcISOLATEDSubnet1Subnet80F07FA0', - }, - { - Ref: 'VpcISOLATEDSubnet2SubnetB0B548C3', - }, - ], + }); + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + NetworkConfiguration: { + AwsvpcConfiguration: { + Subnets: [ + { + Ref: 'VpcISOLATEDSubnet1Subnet80F07FA0', + }, + { + Ref: 'VpcISOLATEDSubnet2SubnetB0B548C3', + }, + ], + }, }, - }, + }); }); -}); -test('target group uses HTTP/80 as default', () => { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - }, - }); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { - Port: 80, - Protocol: 'HTTP', - }); -}); + test('target group uses HTTP/80 as default', () => { + // GIVEN + const stack = new cdk.Stack(); -test('target group uses HTTPS/443 when configured', () => { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - }, - targetProtocol: ApplicationProtocol.HTTPS, - }); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { - Port: 443, - Protocol: 'HTTPS', - }); -}); - -test('setting platform version', () => { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - }, - platformVersion: ecs.FargatePlatformVersion.VERSION1_4, - }); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - PlatformVersion: ecs.FargatePlatformVersion.VERSION1_4, - }); -}); - -test('test load balanced service with family defined', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { - autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { - vpc, - instanceType: new ec2.InstanceType('t2.micro'), - machineImage: MachineImage.latestAmazonLinux(), - }), - })); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - enableLogging: false, - environment: { - TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', - TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), }, - family: 'fargate-task-family', - }, - desiredCount: 2, - memoryLimitMiB: 512, - serviceName: 'fargate-test-service', - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DesiredCount: 2, - LaunchType: 'FARGATE', - ServiceName: 'fargate-test-service', - }); - - Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { - ContainerDefinitions: [ - Match.objectLike({ - Environment: [ - { - Name: 'TEST_ENVIRONMENT_VARIABLE1', - Value: 'test environment variable 1 value', - }, - { - Name: 'TEST_ENVIRONMENT_VARIABLE2', - Value: 'test environment variable 2 value', - }, - ], - Image: '/aws/aws-example-app', - }), - ], - Family: 'fargate-task-family', - }); -}); - -test('setting ALB deployment controller', () => { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - }, - deploymentController: { - type: ecs.DeploymentControllerType.CODE_DEPLOY, - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DeploymentController: { - Type: 'CODE_DEPLOY', - }, - }); -}); - -test('setting a command for taskImageOptions in an ApplicationLoadBalancedFargateService works', () => { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - command: ['./app/bin/start.sh', '--foo'], - }, - }); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { - ContainerDefinitions: [ - Match.objectLike({ - Image: '/aws/aws-example-app', - Command: ['./app/bin/start.sh', '--foo'], - }), - ], - }); -}); - -test('setting an entryPoint for taskImageOptions in an ApplicationLoadBalancedFargateService works', () => { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - entryPoint: ['echo', 'foo'], - }, - }); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { - ContainerDefinitions: [ - Match.objectLike({ - Image: '/aws/aws-example-app', - EntryPoint: ['echo', 'foo'], + }); + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { + Port: 80, + Protocol: 'HTTP', + }); + }); + + test('target group uses HTTPS/443 when configured', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + }, + targetProtocol: ApplicationProtocol.HTTPS, + }); + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { + Port: 443, + Protocol: 'HTTPS', + }); + }); + + test('setting platform version', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + }, + platformVersion: ecs.FargatePlatformVersion.VERSION1_4, + }); + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + PlatformVersion: ecs.FargatePlatformVersion.VERSION1_4, + }); + }); + + test('load balanced service with family defined', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), }), - ], - }); -}); - -test('setting NLB deployment controller', () => { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - }, - deploymentController: { - type: ecs.DeploymentControllerType.CODE_DEPLOY, - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DeploymentController: { - Type: 'CODE_DEPLOY', - }, - }); -}); - -test('setting ALB circuitBreaker works', () => { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - }, - circuitBreaker: { rollback: true }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DeploymentConfiguration: { - DeploymentCircuitBreaker: { - Enable: true, - Rollback: true, + })); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + enableLogging: false, + environment: { + TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', + TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', + }, + family: 'fargate-task-family', }, - }, - DeploymentController: { - Type: 'ECS', - }, - }); -}); - -test('setting NLB circuitBreaker works', () => { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - }, - circuitBreaker: { rollback: true }, - }); + desiredCount: 2, + memoryLimitMiB: 512, + serviceName: 'fargate-test-service', + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DesiredCount: 2, + LaunchType: 'FARGATE', + ServiceName: 'fargate-test-service', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + Match.objectLike({ + Environment: [ + { + Name: 'TEST_ENVIRONMENT_VARIABLE1', + Value: 'test environment variable 1 value', + }, + { + Name: 'TEST_ENVIRONMENT_VARIABLE2', + Value: 'test environment variable 2 value', + }, + ], + Image: '/aws/aws-example-app', + }), + ], + Family: 'fargate-task-family', + }); + }); + + test('setting ALB deployment controller', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + }, + deploymentController: { + type: ecs.DeploymentControllerType.CODE_DEPLOY, + }, + }); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DeploymentConfiguration: { - DeploymentCircuitBreaker: { - Enable: true, - Rollback: true, + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DeploymentController: { + Type: 'CODE_DEPLOY', }, - }, - DeploymentController: { - Type: 'ECS', - }, + }); }); -}); -test('setting NLB special listener port to create the listener', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'FargateNlbService', { - cluster, - listenerPort: 2015, - taskImageOptions: { - containerPort: 2015, - image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), - }, - }); + test('setting a command for taskImageOption', () => { + // GIVEN + const stack = new cdk.Stack(); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { - DefaultActions: [ - Match.objectLike({ - Type: 'forward', + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + command: ['./app/bin/start.sh', '--foo'], + }, + }); + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + Match.objectLike({ + Image: '/aws/aws-example-app', + Command: ['./app/bin/start.sh', '--foo'], + }), + ], + }); + }); + + test('setting an entryPoint for taskImageOptions', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + entryPoint: ['echo', 'foo'], + }, + }); + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + Match.objectLike({ + Image: '/aws/aws-example-app', + EntryPoint: ['echo', 'foo'], + }), + ], + }); + }); + + test('setting ALB circuitBreaker works', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + }, + circuitBreaker: { rollback: true }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DeploymentConfiguration: { + DeploymentCircuitBreaker: { + Enable: true, + Rollback: true, + }, + }, + DeploymentController: { + Type: 'ECS', + }, + }); + }); + + test('setting ALB special listener port to create the listener', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', { + cluster, + listenerPort: 2015, + taskImageOptions: { + containerPort: 2015, + image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { + DefaultActions: [ + Match.objectLike({ + Type: 'forward', + }), + ], + Port: 2015, + Protocol: 'HTTP', + }); + }); + + test('setting ALB HTTPS protocol to create the listener on 443', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', { + cluster, + protocol: ApplicationProtocol.HTTPS, + domainName: 'domain.com', + domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', { + hostedZoneId: 'fakeId', + zoneName: 'domain.com', }), - ], - Port: 2015, - Protocol: 'TCP', - }); -}); - -test('setting ALB special listener port to create the listener', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', { - cluster, - listenerPort: 2015, - taskImageOptions: { - containerPort: 2015, - image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { - DefaultActions: [ - Match.objectLike({ - Type: 'forward', + taskImageOptions: { + containerPort: 2015, + image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { + DefaultActions: [ + Match.objectLike({ + Type: 'forward', + }), + ], + Port: 443, + Protocol: 'HTTPS', + }); + }); + + test('setting ALB HTTPS correctly sets the recordset name', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', { + cluster, + protocol: ApplicationProtocol.HTTPS, + domainName: 'test.domain.com', + domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', { + hostedZoneId: 'fakeId', + zoneName: 'domain.com.', }), - ], - Port: 2015, - Protocol: 'HTTP', - }); -}); - -test('setting ALB HTTPS protocol to create the listener on 443', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', { - cluster, - protocol: ApplicationProtocol.HTTPS, - domainName: 'domain.com', - domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', { - hostedZoneId: 'fakeId', - zoneName: 'domain.com', - }), - taskImageOptions: { - containerPort: 2015, - image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { - DefaultActions: [ - Match.objectLike({ - Type: 'forward', + taskImageOptions: { + containerPort: 2015, + image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Route53::RecordSet', { + Name: 'test.domain.com.', + }); + }); + + test('setting ALB cname option correctly sets the recordset type', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', { + cluster, + protocol: ApplicationProtocol.HTTPS, + domainName: 'test.domain.com', + domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', { + hostedZoneId: 'fakeId', + zoneName: 'domain.com.', }), - ], - Port: 443, - Protocol: 'HTTPS', - }); -}); - -test('setting ALB HTTPS correctly sets the recordset name', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', { - cluster, - protocol: ApplicationProtocol.HTTPS, - domainName: 'test.domain.com', - domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', { - hostedZoneId: 'fakeId', - zoneName: 'domain.com.', - }), - taskImageOptions: { - containerPort: 2015, - image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Route53::RecordSet', { - Name: 'test.domain.com.', - }); -}); - -test('setting ALB cname option correctly sets the recordset type', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', { - cluster, - protocol: ApplicationProtocol.HTTPS, - domainName: 'test.domain.com', - domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', { - hostedZoneId: 'fakeId', - zoneName: 'domain.com.', - }), - recordType: ecsPatterns.ApplicationLoadBalancedServiceRecordType.CNAME, - taskImageOptions: { - containerPort: 2015, - image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Route53::RecordSet', { - Name: 'test.domain.com.', - Type: 'CNAME', - }); -}); - -test('setting ALB record type to NONE correctly omits the recordset', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', { - cluster, - protocol: ApplicationProtocol.HTTPS, - domainName: 'test.domain.com', - domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', { - hostedZoneId: 'fakeId', - zoneName: 'domain.com.', - }), - recordType: ecsPatterns.ApplicationLoadBalancedServiceRecordType.NONE, - taskImageOptions: { - containerPort: 2015, - image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), - }, - }); - - // THEN - Template.fromStack(stack).resourceCountIs('AWS::Route53::RecordSet', 0); -}); - -test('setting NLB cname option correctly sets the recordset type', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'FargateNlbService', { - cluster, - domainName: 'test.domain.com', - domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', { - hostedZoneId: 'fakeId', - zoneName: 'domain.com.', - }), - recordType: ecsPatterns.NetworkLoadBalancedServiceRecordType.CNAME, - taskImageOptions: { - containerPort: 2015, - image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::Route53::RecordSet', { - Name: 'test.domain.com.', - Type: 'CNAME', - }); -}); - -test('setting NLB record type to NONE correctly omits the recordset', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'FargateNlbService', { - cluster, - domainName: 'test.domain.com', - domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', { - hostedZoneId: 'fakeId', - zoneName: 'domain.com.', - }), - recordType: ecsPatterns.NetworkLoadBalancedServiceRecordType.NONE, - taskImageOptions: { - containerPort: 2015, - image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), - }, - }); - - // THEN - Template.fromStack(stack).resourceCountIs('AWS::Route53::RecordSet', 0); -}); - -test('setting ALB HTTP protocol to create the listener on 80', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', { - cluster, - protocol: ApplicationProtocol.HTTP, - taskImageOptions: { - containerPort: 2015, - image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { - DefaultActions: [ - Match.objectLike({ - Type: 'forward', + recordType: ecsPatterns.ApplicationLoadBalancedServiceRecordType.CNAME, + taskImageOptions: { + containerPort: 2015, + image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Route53::RecordSet', { + Name: 'test.domain.com.', + Type: 'CNAME', + }); + }); + + test('setting ALB record type to NONE correctly omits the recordset', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', { + cluster, + protocol: ApplicationProtocol.HTTPS, + domainName: 'test.domain.com', + domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', { + hostedZoneId: 'fakeId', + zoneName: 'domain.com.', }), - ], - Port: 80, - Protocol: 'HTTP', - }); -}); + recordType: ecsPatterns.ApplicationLoadBalancedServiceRecordType.NONE, + taskImageOptions: { + containerPort: 2015, + image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), + }, + }); -test('setting ALB without any protocol or listenerPort to create the listener on 80', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', { - cluster, - taskImageOptions: { - containerPort: 2015, - image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), - }, + // THEN + Template.fromStack(stack).resourceCountIs('AWS::Route53::RecordSet', 0); }); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { - DefaultActions: [ - Match.objectLike({ - Type: 'forward', - }), - ], - Port: 80, - Protocol: 'HTTP', - }); -}); + test('setting ALB HTTP protocol to create the listener on 80', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); -test('passing in existing network load balancer to NLB Fargate Service', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const nlb = new NetworkLoadBalancer(stack, 'NLB', { vpc }); - - // WHEN - new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { - vpc, - loadBalancer: nlb, - taskImageOptions: { + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', { + cluster, + protocol: ApplicationProtocol.HTTP, + taskImageOptions: { + containerPort: 2015, + image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { + DefaultActions: [ + Match.objectLike({ + Type: 'forward', + }), + ], + Port: 80, + Protocol: 'HTTP', + }); + }); + + test('setting ALB without any protocol or listenerPort to create the listener on 80', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', { + cluster, + taskImageOptions: { + containerPort: 2015, + image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { + DefaultActions: [ + Match.objectLike({ + Type: 'forward', + }), + ], + Port: 80, + Protocol: 'HTTP', + }); + }); + + test('passing in previously created application load balancer', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); + const sg = new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc }); + cluster.connections.addSecurityGroup(sg); + const alb = new ApplicationLoadBalancer(stack, 'ALB', { vpc, securityGroup: sg }); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + loadBalancer: alb, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + LaunchType: 'FARGATE', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { + Type: 'application', + }); + }); + + test('passing in imported application load balancer and resources', () => { + // GIVEN + const stack1 = new cdk.Stack(); + const albArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; + const vpc = new ec2.Vpc(stack1, 'Vpc'); + const cluster = new ecs.Cluster(stack1, 'Cluster', { vpc, clusterName: 'MyClusterName' }); + const sg = new ec2.SecurityGroup(stack1, 'SecurityGroup', { vpc }); + cluster.connections.addSecurityGroup(sg); + const alb = ApplicationLoadBalancer.fromApplicationLoadBalancerAttributes(stack1, 'ALB', { + loadBalancerArn: albArn, + vpc, + securityGroupId: sg.securityGroupId, + loadBalancerDnsName: 'MyDnsName', + }); + + // WHEN + const taskDef = new ecs.FargateTaskDefinition(stack1, 'TaskDef', { + cpu: 1024, + memoryLimitMiB: 1024, + }); + const container = taskDef.addContainer('Container', { image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - LaunchType: 'FARGATE', - }); - - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { - Type: 'network', - }); -}); - -test('passing in imported network load balancer and resources to NLB Fargate service', () => { - // GIVEN - const app = new cdk.App(); - const stack1 = new cdk.Stack(app, 'MyStack'); - const vpc1 = new ec2.Vpc(stack1, 'VPC'); - const cluster1 = new ecs.Cluster(stack1, 'Cluster', { vpc: vpc1 }); - const nlbArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; - const stack2 = new cdk.Stack(stack1, 'Stack2'); - const cluster2 = ecs.Cluster.fromClusterAttributes(stack2, 'ImportedCluster', { - vpc: vpc1, - securityGroups: cluster1.connections.securityGroups, - clusterName: 'cluster-name', - }); + memoryLimitMiB: 1024, + }); + container.addPortMappings({ + containerPort: 80, + }); + + new ecsPatterns.ApplicationLoadBalancedFargateService(stack1, 'FargateALBService', { + cluster, + loadBalancer: alb, + desiredCount: 1, + taskDefinition: taskDef, + }); + + // THEN + Template.fromStack(stack1).hasResourceProperties('AWS::ECS::Service', { + LaunchType: 'FARGATE', + LoadBalancers: [Match.objectLike({ ContainerName: 'Container', ContainerPort: 80 })], + }); + + Template.fromStack(stack1).resourceCountIs('AWS::ElasticLoadBalancingV2::TargetGroup', 1); + + Template.fromStack(stack1).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { + LoadBalancerArn: alb.loadBalancerArn, + Port: 80, + }); + }); + + test('passing in previously created security groups', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); + const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { + allowAllOutbound: false, + description: 'Example', + securityGroupName: 'Rolly', + vpc, + }); - // WHEN - const nlb2 = NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack2, 'ImportedNLB', { - loadBalancerArn: nlbArn, - vpc: vpc1, - }); - const taskDef = new ecs.FargateTaskDefinition(stack2, 'TaskDef', { - cpu: 1024, - memoryLimitMiB: 1024, - }); - const container = taskDef.addContainer('myContainer', { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - memoryLimitMiB: 1024, - }); - container.addPortMappings({ - containerPort: 80, - }); + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, + securityGroups: [securityGroup], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + LaunchType: 'FARGATE', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroup', { + GroupDescription: 'Example', + GroupName: 'Rolly', + SecurityGroupEgress: [ + { + CidrIp: '255.255.255.255/32', + Description: 'Disallow all traffic', + FromPort: 252, + IpProtocol: 'icmp', + ToPort: 86, + }, + ], + VpcId: { + Ref: 'Vpc8378EB38', + }, + }); + }); + + test('domainName and domainZone not required for HTTPS listener with provided cert', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + const exampleDotComZone = new route53.PublicHostedZone(stack, 'ExampleDotCom', { + zoneName: 'example.com', + }); + const certificate = new Certificate(stack, 'Certificate', { + domainName: 'test.example.com', + validation: CertificateValidation.fromDns(exampleDotComZone), + }); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', { + cluster, + protocol: ApplicationProtocol.HTTPS, + + taskImageOptions: { + containerPort: 2015, + image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), + }, + certificate: certificate, + }); - new ecsPatterns.NetworkLoadBalancedFargateService(stack2, 'FargateNLBService', { - cluster: cluster2, - loadBalancer: nlb2, - desiredCount: 1, - taskDefinition: taskDef, + // THEN + Template.fromStack(stack).resourceCountIs('AWS::Route53::RecordSet', 0); }); - // THEN - Template.fromStack(stack2).hasResourceProperties('AWS::ECS::Service', { - LaunchType: 'FARGATE', - LoadBalancers: [Match.objectLike({ ContainerName: 'myContainer', ContainerPort: 80 })], - }); + test('with docker labels defined', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - Template.fromStack(stack2).resourceCountIs('AWS::ElasticLoadBalancingV2::TargetGroup', 1); + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + Match.objectLike({ + Image: '/aws/aws-example-app', + DockerLabels: { + label1: 'labelValue1', + label2: 'labelValue2', + }, + }), + ], + }); + }); + + test('Passing in token for desiredCount will not throw error', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + const param = new cdk.CfnParameter(stack, 'prammm', { + type: 'Number', + default: 1, + }); + + // WHEN + const service = new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, + }, + desiredCount: param.valueAsNumber, + }); - Template.fromStack(stack2).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { - LoadBalancerArn: nlb2.loadBalancerArn, - Port: 80, + // THEN + expect(() => { + service.internalDesiredCount; + }).toBeTruthy; }); -}); -test('passing in previously created application load balancer to ALB Fargate Service', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); - const sg = new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc }); - cluster.connections.addSecurityGroup(sg); - const alb = new ApplicationLoadBalancer(stack, 'ALB', { vpc, securityGroup: sg }); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - cluster, - loadBalancer: alb, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - }); + test('multiple capacity provider strategies are set', () => { + // GIVEN + const stack = new cdk.Stack(); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - LaunchType: 'FARGATE', - }); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.enableFargateCapacityProviders(); - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { - Type: 'application', + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + memoryLimitMiB: 1024, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + }, + capacityProviderStrategies: [ + { + capacityProvider: 'FARGATE', + base: 1, + weight: 1, + }, + { + capacityProvider: 'FARGATE_SPOT', + base: 0, + weight: 2, + }, + ], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + CapacityProviderStrategy: Match.arrayEquals([ + { + Base: 1, + CapacityProvider: 'FARGATE', + Weight: 1, + }, + { + Base: 0, + CapacityProvider: 'FARGATE_SPOT', + Weight: 2, + }, + ]), + }); + }); + + test('should validate minHealthyPercent', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + expect(() => new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, + }, + minHealthyPercent: 0.5, + })).toThrow(/Must be a non-negative integer/); + }); + + test('should validate maxHealthyPercent', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + expect(() => new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, + }, + maxHealthyPercent: 0.5, + })).toThrow(/Must be a non-negative integer/); + }); + + test('minHealthyPercent must be less than maxHealthyPercent', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + expect(() => new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, + }, + minHealthyPercent: 100, + maxHealthyPercent: 70, + })).toThrow(/must be less than/); }); }); -test('passing in imported application load balancer and resources to ALB Fargate Service', () => { - // GIVEN - const stack1 = new cdk.Stack(); - const albArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; - const vpc = new ec2.Vpc(stack1, 'Vpc'); - const cluster = new ecs.Cluster(stack1, 'Cluster', { vpc, clusterName: 'MyClusterName' }); - const sg = new ec2.SecurityGroup(stack1, 'SecurityGroup', { vpc }); - cluster.connections.addSecurityGroup(sg); - const alb = ApplicationLoadBalancer.fromApplicationLoadBalancerAttributes(stack1, 'ALB', { - loadBalancerArn: albArn, - vpc, - securityGroupId: sg.securityGroupId, - loadBalancerDnsName: 'MyDnsName', - }); - - // WHEN - const taskDef = new ecs.FargateTaskDefinition(stack1, 'TaskDef', { - cpu: 1024, - memoryLimitMiB: 1024, - }); - const container = taskDef.addContainer('Container', { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - memoryLimitMiB: 1024, - }); - container.addPortMappings({ - containerPort: 80, - }); - - new ecsPatterns.ApplicationLoadBalancedFargateService(stack1, 'FargateALBService', { - cluster, - loadBalancer: alb, - desiredCount: 1, - taskDefinition: taskDef, - }); +describe('NetworkLoadBalancedFargateService', () => { + test('setting loadBalancerType to Network creates an NLB Public', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { + Type: 'network', + Scheme: 'internet-facing', + }); + }); + + test('setting loadBalancerType to Network and publicLoadBalancer to false creates an NLB Private', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + }, + publicLoadBalancer: false, + }); - // THEN - Template.fromStack(stack1).hasResourceProperties('AWS::ECS::Service', { - LaunchType: 'FARGATE', - LoadBalancers: [Match.objectLike({ ContainerName: 'Container', ContainerPort: 80 })], + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { + Type: 'network', + Scheme: 'internal', + }); }); - Template.fromStack(stack1).resourceCountIs('AWS::ElasticLoadBalancingV2::TargetGroup', 1); + test('setting vpc and cluster throws error', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - Template.fromStack(stack1).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { - LoadBalancerArn: alb.loadBalancerArn, - Port: 80, - }); -}); - -test('passing in previously created security groups to ALB Fargate Service', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'Vpc'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); - const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { - allowAllOutbound: false, - description: 'Example', - securityGroupName: 'Rolly', - vpc, - }); + // WHEN + expect(() => new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { + cluster, + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + }, + })).toThrow(); + }); + + test('setting executionRole updated taskDefinition with given execution role', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + const executionRole = new iam.Role(stack, 'ExecutionRole', { + path: '/', + assumedBy: new iam.CompositePrincipal( + new iam.ServicePrincipal('ecs.amazonaws.com'), + new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + ), + }); + + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + executionRole, + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ExecutionRoleArn: { 'Fn::GetAtt': ['ExecutionRole605A040B', 'Arn'] }, + }); + }); + + test('setting taskRole updated taskDefinition with given task role', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + const taskRole = new iam.Role(stack, 'taskRoleTest', { + path: '/', + assumedBy: new iam.CompositePrincipal( + new iam.ServicePrincipal('ecs.amazonaws.com'), + new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + ), + }); + + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + taskRole, + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + TaskRoleArn: { 'Fn::GetAtt': ['taskRoleTest9DA66B6E', 'Arn'] }, + }); + }); + + test('setting containerName updates container name with given name', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + containerName: 'bob', + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + Match.objectLike({ + Name: 'bob', + }), + ], + }); + }); + + test('not setting containerName updates container name with default', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + Match.objectLike({ + Name: 'web', + }), + ], + }); + }); + + test('setting servicename updates service name with given name', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + }, + serviceName: 'bob', + }); + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + ServiceName: 'bob', + }); + }); + + test('not setting servicename updates service name with default', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + }, + }); - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - securityGroups: [securityGroup], + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + ServiceName: Match.absent(), + }); }); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - LaunchType: 'FARGATE', - }); + test('setting NLB deployment controller', () => { + // GIVEN + const stack = new cdk.Stack(); - Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroup', { - GroupDescription: 'Example', - GroupName: 'Rolly', - SecurityGroupEgress: [ - { - CidrIp: '255.255.255.255/32', - Description: 'Disallow all traffic', - FromPort: 252, - IpProtocol: 'icmp', - ToPort: 86, + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), }, - ], - VpcId: { - Ref: 'Vpc8378EB38', - }, - }); -}); - -test('domainName and domainZone not required for HTTPS listener with provided cert', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - const exampleDotComZone = new route53.PublicHostedZone(stack, 'ExampleDotCom', { - zoneName: 'example.com', - }); - const certificate = new Certificate(stack, 'Certificate', { - domainName: 'test.example.com', - validation: CertificateValidation.fromDns(exampleDotComZone), - }); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', { - cluster, - protocol: ApplicationProtocol.HTTPS, + deploymentController: { + type: ecs.DeploymentControllerType.CODE_DEPLOY, + }, + }); - taskImageOptions: { - containerPort: 2015, - image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), - }, - certificate: certificate, + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DeploymentController: { + Type: 'CODE_DEPLOY', + }, + }); }); - // THEN - Template.fromStack(stack).resourceCountIs('AWS::Route53::RecordSet', 0); -}); + test('setting NLB circuitBreaker works', () => { + // GIVEN + const stack = new cdk.Stack(); -test('test ALB load balanced service with docker labels defined', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { - ContainerDefinitions: [ - Match.objectLike({ - Image: '/aws/aws-example-app', - DockerLabels: { - label1: 'labelValue1', - label2: 'labelValue2', + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + }, + circuitBreaker: { rollback: true }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DeploymentConfiguration: { + DeploymentCircuitBreaker: { + Enable: true, + Rollback: true, }, + }, + DeploymentController: { + Type: 'ECS', + }, + }); + }); + + test('setting NLB special listener port to create the listener', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'FargateNlbService', { + cluster, + listenerPort: 2015, + taskImageOptions: { + containerPort: 2015, + image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { + DefaultActions: [ + Match.objectLike({ + Type: 'forward', + }), + ], + Port: 2015, + Protocol: 'TCP', + }); + }); + + test('setting NLB cname option correctly sets the recordset type', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'FargateNlbService', { + cluster, + domainName: 'test.domain.com', + domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', { + hostedZoneId: 'fakeId', + zoneName: 'domain.com.', }), - ], - }); -}); - -test('test Network load balanced service with docker labels defined', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { - ContainerDefinitions: [ - Match.objectLike({ - Image: '/aws/aws-example-app', - DockerLabels: { - label1: 'labelValue1', - label2: 'labelValue2', - }, + recordType: ecsPatterns.NetworkLoadBalancedServiceRecordType.CNAME, + taskImageOptions: { + containerPort: 2015, + image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Route53::RecordSet', { + Name: 'test.domain.com.', + Type: 'CNAME', + }); + }); + + test('setting NLB record type to NONE correctly omits the recordset', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'FargateNlbService', { + cluster, + domainName: 'test.domain.com', + domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', { + hostedZoneId: 'fakeId', + zoneName: 'domain.com.', }), - ], - }); -}); + recordType: ecsPatterns.NetworkLoadBalancedServiceRecordType.NONE, + taskImageOptions: { + containerPort: 2015, + image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), + }, + }); -test('Passing in token for desiredCount will not throw error', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - const param = new cdk.CfnParameter(stack, 'prammm', { - type: 'Number', - default: 1, + // THEN + Template.fromStack(stack).resourceCountIs('AWS::Route53::RecordSet', 0); }); - // WHEN - const service = new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, - }, - desiredCount: param.valueAsNumber, - }); + test('passing in existing network load balancer to NLB Fargate Service', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const nlb = new NetworkLoadBalancer(stack, 'NLB', { vpc }); - // THEN - expect(() => { - service.internalDesiredCount; - }).toBeTruthy; -}); - -test('ApplicationLoadBalancedFargateService multiple capacity provider strategies are set', () => { - // GIVEN - const stack = new cdk.Stack(); - - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.enableFargateCapacityProviders(); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - cluster, - memoryLimitMiB: 1024, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - }, - capacityProviderStrategies: [ - { - capacityProvider: 'FARGATE', - base: 1, - weight: 1, + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { + vpc, + loadBalancer: nlb, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), }, - { - capacityProvider: 'FARGATE_SPOT', - base: 0, - weight: 2, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + LaunchType: 'FARGATE', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { + Type: 'network', + }); + }); + + test('passing in imported network load balancer and resources to NLB Fargate service', () => { + // GIVEN + const app = new cdk.App(); + const stack1 = new cdk.Stack(app, 'MyStack'); + const vpc1 = new ec2.Vpc(stack1, 'VPC'); + const cluster1 = new ecs.Cluster(stack1, 'Cluster', { vpc: vpc1 }); + const nlbArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; + const stack2 = new cdk.Stack(stack1, 'Stack2'); + const cluster2 = ecs.Cluster.fromClusterAttributes(stack2, 'ImportedCluster', { + vpc: vpc1, + securityGroups: cluster1.connections.securityGroups, + clusterName: 'cluster-name', + }); + + // WHEN + const nlb2 = NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack2, 'ImportedNLB', { + loadBalancerArn: nlbArn, + vpc: vpc1, + }); + const taskDef = new ecs.FargateTaskDefinition(stack2, 'TaskDef', { + cpu: 1024, + memoryLimitMiB: 1024, + }); + const container = taskDef.addContainer('myContainer', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 1024, + }); + container.addPortMappings({ + containerPort: 80, + }); + + new ecsPatterns.NetworkLoadBalancedFargateService(stack2, 'FargateNLBService', { + cluster: cluster2, + loadBalancer: nlb2, + desiredCount: 1, + taskDefinition: taskDef, + }); + + // THEN + Template.fromStack(stack2).hasResourceProperties('AWS::ECS::Service', { + LaunchType: 'FARGATE', + LoadBalancers: [Match.objectLike({ ContainerName: 'myContainer', ContainerPort: 80 })], + }); + + Template.fromStack(stack2).resourceCountIs('AWS::ElasticLoadBalancingV2::TargetGroup', 1); + + Template.fromStack(stack2).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { + LoadBalancerArn: nlb2.loadBalancerArn, + Port: 80, + }); + }); + + test('test Network load balanced service with docker labels defined', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, }, - ], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + Match.objectLike({ + Image: '/aws/aws-example-app', + DockerLabels: { + label1: 'labelValue1', + label2: 'labelValue2', + }, + }), + ], + }); }); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - CapacityProviderStrategy: Match.arrayEquals([ - { - Base: 1, - CapacityProvider: 'FARGATE', - Weight: 1, - }, - { - Base: 0, - CapacityProvider: 'FARGATE_SPOT', - Weight: 2, - }, - ]), - }); -}); + test('NetworkLoadBalancedFargateService multiple capacity provider strategies are set', () => { + // GIVEN + const stack = new cdk.Stack(); -test('NetworkLoadBalancedFargateService multiple capacity provider strategies are set', () => { - // GIVEN - const stack = new cdk.Stack(); - - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.enableFargateCapacityProviders(); - - // WHEN - new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { - cluster, - memoryLimitMiB: 1024, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - }, - capacityProviderStrategies: [ - { - capacityProvider: 'FARGATE', - base: 1, - weight: 1, - }, - { - capacityProvider: 'FARGATE_SPOT', - base: 0, - weight: 2, - }, - ], - }); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.enableFargateCapacityProviders(); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - CapacityProviderStrategy: Match.arrayEquals([ - { - Base: 1, - CapacityProvider: 'FARGATE', - Weight: 1, + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { + cluster, + memoryLimitMiB: 1024, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), }, - { - Base: 0, - CapacityProvider: 'FARGATE_SPOT', - Weight: 2, - }, - ]), + capacityProviderStrategies: [ + { + capacityProvider: 'FARGATE', + base: 1, + weight: 1, + }, + { + capacityProvider: 'FARGATE_SPOT', + base: 0, + weight: 2, + }, + ], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + CapacityProviderStrategy: Match.arrayEquals([ + { + Base: 1, + CapacityProvider: 'FARGATE', + Weight: 1, + }, + { + Base: 0, + CapacityProvider: 'FARGATE_SPOT', + Weight: 2, + }, + ]), + }); }); }); - -test('should validate minHealthyPercent', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - expect(() => new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, - }, - minHealthyPercent: 0.5, - })).toThrow(/Must be a non-negative integer/); -}); - -test('should validate maxHealthyPercent', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - expect(() => new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, - }, - maxHealthyPercent: 0.5, - })).toThrow(/Must be a non-negative integer/); -}); - -test('minHealthyPercent must be less than maxHealthyPercent', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // WHEN - expect(() => new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, - }, - minHealthyPercent: 100, - maxHealthyPercent: 70, - })).toThrow(/must be less than/); -});