From 6bbb76413c1649a8c9a9e961afdd9cdc4afcba37 Mon Sep 17 00:00:00 2001 From: wafuwafu13 Date: Mon, 19 Feb 2024 20:51:23 +0000 Subject: [PATCH] refactor(ecs-patterns): organize tests --- .../aws-ecs-patterns/test/ec2/l3s-v2.test.ts | 4 +- .../aws-ecs-patterns/test/ec2/l3s.test.ts | 2396 +++++------------ .../load-balanced-fargate-service-v2.test.ts | 289 ++ .../load-balanced-fargate-service.test.ts | 727 ++++- 4 files changed, 1709 insertions(+), 1707 deletions(-) diff --git a/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/l3s-v2.test.ts b/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/l3s-v2.test.ts index 325101a7ee30b..55954e07c6620 100644 --- a/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/l3s-v2.test.ts +++ b/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/l3s-v2.test.ts @@ -22,7 +22,7 @@ import { NamespaceType } from '../../../aws-servicediscovery'; import { Duration, Stack } from '../../../core'; import { ApplicationMultipleTargetGroupsEc2Service, NetworkMultipleTargetGroupsEc2Service } from '../../lib'; -describe('When Application Load Balancer', () => { +describe('ApplicationMultipleTargetGroupsEc2Service', () => { test('test ECS ALB construct with default settings', () => { // GIVEN const stack = new Stack(); @@ -893,7 +893,7 @@ describe('When Application Load Balancer', () => { }); }); -describe('When Network Load Balancer', () => { +describe('NetworkMultipleTargetGroupsEc2Service', () => { test('test ECS NLB construct with default settings', () => { // GIVEN const stack = new Stack(); diff --git a/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/l3s.test.ts b/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/l3s.test.ts index 0c65e1ae77733..3b454534c1e68 100644 --- a/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/l3s.test.ts +++ b/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/l3s.test.ts @@ -5,1855 +5,843 @@ import * as ec2 from '../../../aws-ec2'; import { MachineImage } from '../../../aws-ec2'; import * as ecs from '../../../aws-ecs'; import { AsgCapacityProvider } from '../../../aws-ecs'; -import { ApplicationLoadBalancer, ApplicationProtocol, ApplicationProtocolVersion, NetworkLoadBalancer, SslPolicy } from '../../../aws-elasticloadbalancingv2'; +import { ApplicationLoadBalancer, ApplicationProtocol, ApplicationProtocolVersion, NetworkLoadBalancer } from '../../../aws-elasticloadbalancingv2'; import { PublicHostedZone } from '../../../aws-route53'; import * as cloudmap from '../../../aws-servicediscovery'; import * as cdk from '../../../core'; -import { Duration } from '../../../core'; import * as ecsPatterns from '../../lib'; -test('test ECS loadbalanced construct', () => { - // 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(), - }), - })); +describe('ApplicationLoadBalancedEc2Service', () => { + test('ECS loadbalanced construct', () => { + // 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.ApplicationLoadBalancedEc2Service(stack, 'Service', { - cluster, - memoryLimitMiB: 1024, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - environment: { - TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', - TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', + // WHEN + new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { + cluster, + memoryLimitMiB: 1024, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + environment: { + TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', + TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', + }, + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, + entryPoint: ['echo', 'ecs-is-awesome'], + command: ['/bin/bash'], }, - dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, - entryPoint: ['echo', 'ecs-is-awesome'], - command: ['/bin/bash'], - }, - desiredCount: 2, - }); + desiredCount: 2, + }); - // THEN - stack contains a load balancer and a service - Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 1); + // THEN - stack contains a load balancer and a service + Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 1); - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DesiredCount: 2, - LaunchType: 'EC2', - }); + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DesiredCount: 2, + LaunchType: 'EC2', + }); - 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', + 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', + }, + ], + Memory: 1024, + DockerLabels: { + label1: 'labelValue1', + label2: 'labelValue2', }, - ], - Memory: 1024, - DockerLabels: { - label1: 'labelValue1', - label2: 'labelValue2', - }, - EntryPoint: ['echo', 'ecs-is-awesome'], - Command: ['/bin/bash'], - }), - ], + EntryPoint: ['echo', 'ecs-is-awesome'], + Command: ['/bin/bash'], + }), + ], + }); }); -}); -test('ApplicationLoadBalancedEc2Service multiple capacity provider strategies are set', () => { - // GIVEN - const stack = new cdk.Stack(); + test('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.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'AutoScalingGroupProvider1', { - autoScalingGroup: new AutoScalingGroup(stack, 'AutoScalingGroup1', { - vpc, - instanceType: new ec2.InstanceType('t2.micro'), - machineImage: MachineImage.latestAmazonLinux(), - }), - })); - cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'AutoScalingGroupProvider2', { - autoScalingGroup: new AutoScalingGroup(stack, 'AutoScalingGroup2', { - vpc, - instanceType: new ec2.InstanceType('t3.micro'), - machineImage: MachineImage.latestAmazonLinux(), - }), - })); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'AutoScalingGroupProvider1', { + autoScalingGroup: new AutoScalingGroup(stack, 'AutoScalingGroup1', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'AutoScalingGroupProvider2', { + autoScalingGroup: new AutoScalingGroup(stack, 'AutoScalingGroup2', { + vpc, + instanceType: new ec2.InstanceType('t3.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); - // WHEN - new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { - cluster, - memoryLimitMiB: 1024, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - }, - capacityProviderStrategies: [ - { - capacityProvider: 'AutoScalingGroupProvider1', - base: 1, - weight: 1, - }, - { - capacityProvider: 'AutoScalingGroupProvider2', - base: 0, - weight: 2, + // WHEN + new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { + cluster, + memoryLimitMiB: 1024, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), }, - ], - }); + capacityProviderStrategies: [ + { + capacityProvider: 'AutoScalingGroupProvider1', + base: 1, + weight: 1, + }, + { + capacityProvider: 'AutoScalingGroupProvider2', + base: 0, + weight: 2, + }, + ], + }); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - CapacityProviderStrategy: Match.arrayEquals([ - { - Base: 1, - CapacityProvider: 'AutoScalingGroupProvider1', - Weight: 1, - }, - { - Base: 0, - CapacityProvider: 'AutoScalingGroupProvider2', - Weight: 2, - }, - ]), + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + CapacityProviderStrategy: Match.arrayEquals([ + { + Base: 1, + CapacityProvider: 'AutoScalingGroupProvider1', + Weight: 1, + }, + { + Base: 0, + CapacityProvider: 'AutoScalingGroupProvider2', + Weight: 2, + }, + ]), + }); }); -}); -test('NetworkLoadBalancedEc2Service 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.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'AutoScalingGroupProvider1', { - autoScalingGroup: new AutoScalingGroup(stack, 'AutoScalingGroup1', { - vpc, - instanceType: new ec2.InstanceType('t2.micro'), - machineImage: MachineImage.latestAmazonLinux(), - }), - })); - cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'AutoScalingGroupProvider2', { - autoScalingGroup: new AutoScalingGroup(stack, 'AutoScalingGroup2', { - vpc, - instanceType: new ec2.InstanceType('t3.micro'), - machineImage: MachineImage.latestAmazonLinux(), - }), - })); - - // WHEN - new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { - cluster, - memoryLimitMiB: 1024, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - }, - capacityProviderStrategies: [ - { - capacityProvider: 'AutoScalingGroupProvider1', - base: 1, - weight: 1, - }, - { - capacityProvider: 'AutoScalingGroupProvider2', - base: 0, - weight: 2, - }, - ], - }); + test('test ECS loadbalanced construct with memoryReservationMiB', () => { + // 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(), + }), + })); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - CapacityProviderStrategy: Match.arrayEquals([ - { - Base: 1, - CapacityProvider: 'AutoScalingGroupProvider1', - Weight: 1, - }, - { - Base: 0, - CapacityProvider: 'AutoScalingGroupProvider2', - Weight: 2, + // WHEN + new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { + cluster, + memoryReservationMiB: 1024, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), }, - ]), - }); -}); - -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.NetworkLoadBalancedEc2Service(stack, 'Service', { - cluster, - vpc, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), - }, - })).toThrow(); -}); + }); -test('test ECS loadbalanced construct with memoryReservationMiB', () => { - // 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(), - }), - })); + // THEN - stack contains a load balancer and a service + Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 1); - // WHEN - new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { - cluster, - memoryReservationMiB: 1024, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - }, + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + Match.objectLike({ + MemoryReservation: 1024, + }), + ], + }); }); - // THEN - stack contains a load balancer and a service - Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 1); - - Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { - ContainerDefinitions: [ - Match.objectLike({ - MemoryReservation: 1024, + test('creates AWS Cloud Map service for Private DNS namespace with application load balanced ec2 service', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), }), - ], - }); -}); - -test('creates AWS Cloud Map service for Private DNS namespace with application load balanced ec2 service', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'MyVpc', {}); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { - autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { - vpc, - instanceType: new ec2.InstanceType('t2.micro'), - machineImage: MachineImage.latestAmazonLinux(), - }), - })); + })); - // WHEN - cluster.addDefaultCloudMapNamespace({ - name: 'foo.com', - type: cloudmap.NamespaceType.DNS_PRIVATE, - }); - - new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { - cluster, - taskImageOptions: { - containerPort: 8000, - image: ecs.ContainerImage.fromRegistry('hello'), - }, - cloudMapOptions: { - name: 'myApp', - }, - memoryLimitMiB: 512, - }); + // WHEN + cluster.addDefaultCloudMapNamespace({ + name: 'foo.com', + type: cloudmap.NamespaceType.DNS_PRIVATE, + }); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - ServiceRegistries: [ - { - ContainerName: 'web', - ContainerPort: 8000, - RegistryArn: { - 'Fn::GetAtt': [ - 'ServiceCloudmapServiceDE76B29D', - 'Arn', - ], - }, + new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { + cluster, + taskImageOptions: { + containerPort: 8000, + image: ecs.ContainerImage.fromRegistry('hello'), }, - ], - }); + cloudMapOptions: { + name: 'myApp', + }, + memoryLimitMiB: 512, + }); - Template.fromStack(stack).hasResourceProperties('AWS::ServiceDiscovery::Service', { - DnsConfig: { - DnsRecords: [ + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + ServiceRegistries: [ { - TTL: 60, - Type: 'SRV', + ContainerName: 'web', + ContainerPort: 8000, + RegistryArn: { + 'Fn::GetAtt': [ + 'ServiceCloudmapServiceDE76B29D', + 'Arn', + ], + }, }, ], - NamespaceId: { - 'Fn::GetAtt': [ - 'EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F', - 'Id', - ], - }, - RoutingPolicy: 'MULTIVALUE', - }, - HealthCheckCustomConfig: { - FailureThreshold: 1, - }, - Name: 'myApp', - NamespaceId: { - 'Fn::GetAtt': [ - 'EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F', - 'Id', - ], - }, - }); -}); - -test('creates AWS Cloud Map service for Private DNS namespace with network load balanced fargate service', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'MyVpc', {}); - const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); - cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { - autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { - vpc, - instanceType: new ec2.InstanceType('t2.micro'), - machineImage: MachineImage.latestAmazonLinux(), - }), - })); - - // WHEN - cluster.addDefaultCloudMapNamespace({ - name: 'foo.com', - type: cloudmap.NamespaceType.DNS_PRIVATE, - }); - - new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - containerPort: 8000, - image: ecs.ContainerImage.fromRegistry('hello'), - }, - cloudMapOptions: { - name: 'myApp', - }, - memoryLimitMiB: 512, - }); + }); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - ServiceRegistries: [ - { - RegistryArn: { + Template.fromStack(stack).hasResourceProperties('AWS::ServiceDiscovery::Service', { + DnsConfig: { + DnsRecords: [ + { + TTL: 60, + Type: 'SRV', + }, + ], + NamespaceId: { 'Fn::GetAtt': [ - 'ServiceCloudmapServiceDE76B29D', - 'Arn', + 'EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F', + 'Id', ], }, + RoutingPolicy: 'MULTIVALUE', }, - ], - }); - - Template.fromStack(stack).hasResourceProperties('AWS::ServiceDiscovery::Service', { - DnsConfig: { - DnsRecords: [ - { - TTL: 60, - Type: 'A', - }, - ], + HealthCheckCustomConfig: { + FailureThreshold: 1, + }, + Name: 'myApp', NamespaceId: { 'Fn::GetAtt': [ 'EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F', 'Id', ], }, - RoutingPolicy: 'MULTIVALUE', - }, - HealthCheckCustomConfig: { - FailureThreshold: 1, - }, - Name: 'myApp', - NamespaceId: { - 'Fn::GetAtt': [ - 'EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F', - 'Id', - ], - }, - }); -}); - -test('test Fargate loadbalanced construct', () => { - // 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('test'), - environment: { - TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', - TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', - }, - dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, - entryPoint: ['echo', 'running-on-fargate'], - command: ['/bin/bash'], - }, - desiredCount: 2, + }); }); - // THEN - stack contains a load balancer and a service - Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 1); - 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', - }, - ], - LogConfiguration: { - LogDriver: 'awslogs', - Options: { - 'awslogs-group': { Ref: 'ServiceTaskDefwebLogGroup2A898F61' }, - 'awslogs-stream-prefix': 'Service', - 'awslogs-region': { Ref: 'AWS::Region' }, - }, - }, - DockerLabels: { - label1: 'labelValue1', - label2: 'labelValue2', + test('throws if desiredTaskCount is 0', () => { + // 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(), + }), + })); + + // THEN + expect(() => + new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { + cluster, + memoryLimitMiB: 1024, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), }, - EntryPoint: ['echo', 'running-on-fargate'], - Command: ['/bin/bash'], + desiredCount: 0, }), - ], - }); - - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DesiredCount: 2, - LaunchType: 'FARGATE', - }); - - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { - Port: 80, - Protocol: 'HTTP', - }); -}); + ).toThrow(/You must specify a desiredCount greater than 0/); + }); + + test('having *HealthyPercent properties', () => { + // 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('test Fargate loadbalanced construct opting out of log driver creation', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + // WHEN + new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { + cluster, + memoryLimitMiB: 1024, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, + desiredCount: 1, + minHealthyPercent: 100, + maxHealthyPercent: 200, + }); - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - enableLogging: false, - environment: { - TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', - TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DeploymentConfiguration: { + MinimumHealthyPercent: 100, + MaximumPercent: 200, }, - }, - desiredCount: 2, + }); }); - // THEN - stack contains a load balancer and a 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', - }, - ], - LogConfiguration: Match.absent(), + test('includes provided protocol version properties', () => { + // 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(), }), - ], - }); -}); + })); + const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); -test('test Fargate loadbalanced construct with TLS', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - }, - domainName: 'api.example.com', - domainZone: zone, - certificate: Certificate.fromCertificateArn(stack, 'Cert', 'helloworld'), - sslPolicy: SslPolicy.TLS12_EXT, - }); - - // THEN - stack contains a load balancer and a service - Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 1); + // WHEN + new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { + cluster, + memoryLimitMiB: 1024, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, + desiredCount: 1, + domainName: 'api.example.com', + domainZone: zone, + protocol: ApplicationProtocol.HTTPS, + protocolVersion: ApplicationProtocolVersion.GRPC, + }); - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { - Port: 443, - Protocol: 'HTTPS', - Certificates: [{ - CertificateArn: 'helloworld', - }], - SslPolicy: SslPolicy.TLS12_EXT, + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { + ProtocolVersion: 'GRPC', + }); }); - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { - Port: 80, - Protocol: 'HTTP', - TargetType: 'ip', - VpcId: { - Ref: 'VPCB9E5F0B4', - }, - }); + test('having deployment controller', () => { + // 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(), + }), + })); - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - LaunchType: 'FARGATE', - }); + // WHEN + new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { + cluster, + memoryLimitMiB: 1024, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, + deploymentController: { + type: ecs.DeploymentControllerType.CODE_DEPLOY, + }, + }); - Template.fromStack(stack).hasResourceProperties('AWS::Route53::RecordSet', { - Name: 'api.example.com.', - HostedZoneId: { - Ref: 'HostedZoneDB99F866', - }, - Type: 'A', - AliasTarget: { - HostedZoneId: { 'Fn::GetAtt': ['ServiceLBE9A1ADBC', 'CanonicalHostedZoneID'] }, - DNSName: { 'Fn::Join': ['', ['dualstack.', { 'Fn::GetAtt': ['ServiceLBE9A1ADBC', 'DNSName'] }]] }, - }, + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DeploymentController: { + Type: 'CODE_DEPLOY', + }, + }); }); -}); -test('test Fargateloadbalanced construct with TLS and default certificate', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); + test('with circuit breaker', () => { + // 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('test'), - }, - domainName: 'api.example.com', - domainZone: zone, - protocol: ApplicationProtocol.HTTPS, - }); + // WHEN + new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { + cluster, + memoryLimitMiB: 1024, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, + circuitBreaker: { rollback: true }, + }); - // THEN - stack contains a load balancer, a service, and a certificate - Template.fromStack(stack).hasResourceProperties('AWS::CertificateManager::Certificate', { - DomainName: 'api.example.com', - DomainValidationOptions: [ - { - DomainName: 'api.example.com', - HostedZoneId: { - Ref: 'HostedZoneDB99F866', + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DeploymentConfiguration: { + DeploymentCircuitBreaker: { + Enable: true, + Rollback: true, }, }, - ], - ValidationMethod: 'DNS', + DeploymentController: { + Type: 'ECS', + }, + }); }); - Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 1); + test('accepts previously created 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' }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); + const sg = new ec2.SecurityGroup(stack, 'SG', { vpc }); + const alb = new ApplicationLoadBalancer(stack, 'NLB', { + vpc, + securityGroup: sg, + }); + const taskDef = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + const container = taskDef.addContainer('Container', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 1024, + }); + container.addPortMappings({ containerPort: 80 }); - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { - Port: 443, - Protocol: 'HTTPS', - Certificates: [{ - CertificateArn: { - Ref: 'ServiceCertificateA7C65FE6', - }, - }], - }); + // WHEN + new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { + cluster, + loadBalancer: alb, + taskDefinition: taskDef, + }); - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - LaunchType: 'FARGATE', + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + LaunchType: 'EC2', + }); + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { + Type: 'application', + }); }); - Template.fromStack(stack).hasResourceProperties('AWS::Route53::RecordSet', { - Name: 'api.example.com.', - HostedZoneId: { - Ref: 'HostedZoneDB99F866', - }, - Type: 'A', - AliasTarget: { - HostedZoneId: { 'Fn::GetAtt': ['ServiceLBE9A1ADBC', 'CanonicalHostedZoneID'] }, - DNSName: { 'Fn::Join': ['', ['dualstack.', { 'Fn::GetAtt': ['ServiceLBE9A1ADBC', 'DNSName'] }]] }, - }, + test('accepts imported load balancer', () => { + // GIVEN + const stack = new cdk.Stack(); + const albArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; + const vpc = new ec2.Vpc(stack, 'Vpc'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); + const sg = new ec2.SecurityGroup(stack, 'SG', { vpc }); + const alb = ApplicationLoadBalancer.fromApplicationLoadBalancerAttributes(stack, 'ALB', { + loadBalancerArn: albArn, + vpc, + securityGroupId: sg.securityGroupId, + loadBalancerDnsName: 'MyName', + }); + const taskDef = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + const container = taskDef.addContainer('Container', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 1024, + }); + container.addPortMappings({ + containerPort: 80, + }); + // WHEN + new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { + cluster, + loadBalancer: alb, + taskDefinition: taskDef, + }); + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + LaunchType: 'EC2', + LoadBalancers: [Match.objectLike({ ContainerName: 'Container', ContainerPort: 80 })], + }); + Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::TargetGroup', 1); + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { + LoadBalancerArn: alb.loadBalancerArn, + Port: 80, + }); }); -}); - -test('errors when setting domainName but not domainZone', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // THEN - expect(() => { - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + test('test ECS loadbalanced construct default/open security group', () => { + // 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.ApplicationLoadBalancedEc2Service(stack, 'Service', { cluster, + memoryReservationMiB: 1024, taskImageOptions: { image: ecs.ContainerImage.fromRegistry('test'), }, - domainName: 'api.example.com', }); - }).toThrow(); -}); - -test('errors when setting both HTTP protocol and certificate', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + // THEN - Stack contains no ingress security group rules + Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroup', { + SecurityGroupIngress: [Match.objectLike({ + CidrIp: '0.0.0.0/0', + FromPort: 80, + IpProtocol: 'tcp', + ToPort: 80, + })], + }); + }); - // THEN - expect(() => { - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + test('test ECS loadbalanced construct closed security group', () => { + // 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(), + }), + })); + const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); + // WHEN + new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { cluster, + memoryReservationMiB: 1024, taskImageOptions: { image: ecs.ContainerImage.fromRegistry('test'), }, - protocol: ApplicationProtocol.HTTP, + domainName: 'api.example.com', + domainZone: zone, certificate: Certificate.fromCertificateArn(stack, 'Cert', 'helloworld'), + openListener: false, + redirectHTTP: true, + }); + // THEN - Stack contains no ingress security group rules + Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroup', { + SecurityGroupIngress: Match.absent(), }); - }).toThrow(); + }); }); -test('errors when setting both HTTP protocol and redirectHTTP', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); +describe('NetworkLoadBalancedEc2Service', () => { + 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 }); - // THEN - expect(() => { - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + // WHEN + expect(() => new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { cluster, + vpc, taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), }, - protocol: ApplicationProtocol.HTTP, - redirectHTTP: true, - }); - }).toThrow(); -}); - -test('does not throw errors when not setting HTTPS protocol but certificate for redirectHTTP', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); - - // THEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - }, - domainName: 'api.example.com', - domainZone: zone, - redirectHTTP: true, - certificate: Certificate.fromCertificateArn(stack, 'Cert', 'helloworld'), + })).toThrow(); }); -}); -test('errors when setting HTTPS protocol but not domain name', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + test('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.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'AutoScalingGroupProvider1', { + autoScalingGroup: new AutoScalingGroup(stack, 'AutoScalingGroup1', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'AutoScalingGroupProvider2', { + autoScalingGroup: new AutoScalingGroup(stack, 'AutoScalingGroup2', { + vpc, + instanceType: new ec2.InstanceType('t3.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); - // THEN - expect(() => { - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + // WHEN + new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { cluster, + memoryLimitMiB: 1024, taskImageOptions: { image: ecs.ContainerImage.fromRegistry('test'), }, - protocol: ApplicationProtocol.HTTPS, - }); - }).toThrow(); -}); - -test('errors when idleTimeout is over 4000 seconds', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // THEN - expect(() => { - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - 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', - }), - }, - idleTimeout: Duration.seconds(5000), - desiredCount: 2, - }); - }).toThrowError(); -}); - -test('errors when idleTimeout is under 1 seconds', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // THEN - expect(() => { - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - 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', - }), - }, - idleTimeout: Duration.seconds(0), - desiredCount: 2, - }); - }).toThrowError(); -}); - -test('passes when idleTimeout is between 1 and 4000 seconds', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - // THEN - expect(() => { - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - 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', - }), - }, - idleTimeout: Duration.seconds(4000), - desiredCount: 2, - }); - }).toBeTruthy(); -}); - -test('idletime is undefined when not set', () => { - // 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('test'), - 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', - }), - }, - desiredCount: 2, - }); - - // THEN - stack contains default LoadBalancer Attributes - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { - LoadBalancerAttributes: [ - { - Key: 'deletion_protection.enabled', - Value: 'false', - }, - ], - }); -}); - -test('errors when idleTimeout is over 4000 seconds for multiAlbService', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - - // THEN - expect(() => { - new ecsPatterns.ApplicationMultipleTargetGroupsFargateService(stack, 'myService', { - cluster: new ecs.Cluster(stack, 'EcsCluster', { vpc }), - memoryLimitMiB: 256, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - enableExecuteCommand: true, - loadBalancers: [ - { - name: 'lb', - idleTimeout: Duration.seconds(400), - domainName: 'api.example.com', - domainZone: new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }), - listeners: [ - { - name: 'listener', - protocol: ApplicationProtocol.HTTPS, - certificate: Certificate.fromCertificateArn(stack, 'Cert', 'helloworld'), - sslPolicy: SslPolicy.TLS12_EXT, - }, - ], - }, - { - name: 'lb2', - idleTimeout: Duration.seconds(5000), - domainName: 'frontend.com', - domainZone: new PublicHostedZone(stack, 'HostedZone2', { zoneName: 'frontend.com' }), - listeners: [ - { - name: 'listener2', - protocol: ApplicationProtocol.HTTPS, - certificate: Certificate.fromCertificateArn(stack, 'Cert2', 'helloworld'), - sslPolicy: SslPolicy.TLS12_EXT, - }, - ], - }, - ], - targetGroups: [ - { - containerPort: 80, - listener: 'listener', - }, - { - containerPort: 90, - pathPattern: 'a/b/c', - priority: 10, - listener: 'listener', - }, + capacityProviderStrategies: [ { - containerPort: 443, - listener: 'listener2', + capacityProvider: 'AutoScalingGroupProvider1', + base: 1, + weight: 1, }, { - containerPort: 80, - pathPattern: 'a/b/c', - priority: 10, - listener: 'listener2', + capacityProvider: 'AutoScalingGroupProvider2', + base: 0, + weight: 2, }, ], }); - }).toThrowError(); -}); - -test('errors when idleTimeout is under 1 seconds for multiAlbService', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - // THEN - expect(() => { - new ecsPatterns.ApplicationMultipleTargetGroupsFargateService(stack, 'myService', { - cluster: new ecs.Cluster(stack, 'EcsCluster', { vpc }), - memoryLimitMiB: 256, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - enableExecuteCommand: true, - loadBalancers: [ + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + CapacityProviderStrategy: Match.arrayEquals([ { - name: 'lb', - idleTimeout: Duration.seconds(400), - domainName: 'api.example.com', - domainZone: new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }), - listeners: [ - { - name: 'listener', - protocol: ApplicationProtocol.HTTPS, - certificate: Certificate.fromCertificateArn(stack, 'Cert', 'helloworld'), - sslPolicy: SslPolicy.TLS12_EXT, - }, - ], - }, - { - name: 'lb2', - idleTimeout: Duration.seconds(0), - domainName: 'frontend.com', - domainZone: new PublicHostedZone(stack, 'HostedZone2', { zoneName: 'frontend.com' }), - listeners: [ - { - name: 'listener2', - protocol: ApplicationProtocol.HTTPS, - certificate: Certificate.fromCertificateArn(stack, 'Cert2', 'helloworld'), - sslPolicy: SslPolicy.TLS12_EXT, - }, - ], + Base: 1, + CapacityProvider: 'AutoScalingGroupProvider1', + Weight: 1, }, - ], - targetGroups: [ { - containerPort: 80, - listener: 'listener', + Base: 0, + CapacityProvider: 'AutoScalingGroupProvider2', + Weight: 2, }, - { - containerPort: 90, - pathPattern: 'a/b/c', - priority: 10, - listener: 'listener', - }, - { - containerPort: 443, - listener: 'listener2', - }, - { - containerPort: 80, - pathPattern: 'a/b/c', - priority: 10, - listener: 'listener2', - }, - ], - }); - }).toThrowError(); -}); - -test('passes when idleTimeout is between 1 and 4000 seconds for multiAlbService', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - - // THEN - expect(() => { - new ecsPatterns.ApplicationMultipleTargetGroupsFargateService(stack, 'myService', { - cluster: new ecs.Cluster(stack, 'EcsCluster', { vpc }), - memoryLimitMiB: 256, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - enableExecuteCommand: true, - loadBalancers: [ - { - name: 'lb', - idleTimeout: Duration.seconds(5), - domainName: 'api.example.com', - domainZone: new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }), - listeners: [ - { - name: 'listener', - protocol: ApplicationProtocol.HTTPS, - certificate: Certificate.fromCertificateArn(stack, 'Cert', 'helloworld'), - sslPolicy: SslPolicy.TLS12_EXT, - }, - ], - }, - { - name: 'lb2', - idleTimeout: Duration.seconds(500), - domainName: 'frontend.com', - domainZone: new PublicHostedZone(stack, 'HostedZone2', { zoneName: 'frontend.com' }), - listeners: [ - { - name: 'listener2', - protocol: ApplicationProtocol.HTTPS, - certificate: Certificate.fromCertificateArn(stack, 'Cert2', 'helloworld'), - sslPolicy: SslPolicy.TLS12_EXT, - }, - ], - }, - ], - targetGroups: [ - { - containerPort: 80, - listener: 'listener', - }, - { - containerPort: 90, - pathPattern: 'a/b/c', - priority: 10, - listener: 'listener', - }, - { - containerPort: 443, - listener: 'listener2', - }, - { - containerPort: 80, - pathPattern: 'a/b/c', - priority: 10, - listener: 'listener2', - }, - ], + ]), }); - }).toBeTruthy(); -}); - -test('idletime is undefined when not set for multiAlbService', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - - // WHEN - new ecsPatterns.ApplicationMultipleTargetGroupsFargateService(stack, 'myService', { - cluster: new ecs.Cluster(stack, 'EcsCluster', { vpc }), - memoryLimitMiB: 256, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - enableExecuteCommand: true, - loadBalancers: [ - { - name: 'lb', - domainName: 'api.example.com', - domainZone: new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }), - listeners: [ - { - name: 'listener', - protocol: ApplicationProtocol.HTTPS, - certificate: Certificate.fromCertificateArn(stack, 'Cert', 'helloworld'), - sslPolicy: SslPolicy.TLS12_EXT, - }, - ], - }, - { - name: 'lb2', - domainName: 'frontend.com', - domainZone: new PublicHostedZone(stack, 'HostedZone2', { zoneName: 'frontend.com' }), - listeners: [ - { - name: 'listener2', - protocol: ApplicationProtocol.HTTPS, - certificate: Certificate.fromCertificateArn(stack, 'Cert2', 'helloworld'), - sslPolicy: SslPolicy.TLS12_EXT, - }, - ], - }, - ], - targetGroups: [ - { - containerPort: 80, - listener: 'listener', - }, - { - containerPort: 90, - pathPattern: 'a/b/c', - priority: 10, - listener: 'listener', - }, - { - containerPort: 443, - listener: 'listener2', - }, - { - containerPort: 80, - pathPattern: 'a/b/c', - priority: 10, - listener: 'listener2', - }, - ], }); - // THEN - stack contains default LoadBalancer Attributes - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { - LoadBalancerAttributes: [ - { - Key: 'deletion_protection.enabled', - Value: 'false', - }, - ], - }); -}); - -test('test Fargate loadbalanced construct with optional log driver input', () => { - // 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('test'), - 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', + test('NLB - throws if desiredTaskCount is 0', () => { + // 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(), }), - }, - desiredCount: 2, - }); - - // THEN - stack contains a load balancer and a 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', - }, - ], - LogConfiguration: { - LogDriver: 'awslogs', - Options: { - 'awslogs-group': { Ref: 'ServiceTaskDefwebLogGroup2A898F61' }, - 'awslogs-stream-prefix': 'TestStream', - 'awslogs-region': { Ref: 'AWS::Region' }, - }, + })); + + // THEN + expect(() => + new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { + cluster, + memoryLimitMiB: 1024, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), }, + desiredCount: 0, }), - ], - }); -}); - -test('test Fargate loadbalanced construct with logging enabled', () => { - // 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('test'), - enableLogging: true, - environment: { - TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', - TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', - }, - }, - desiredCount: 2, - }); - - // THEN - stack contains a load balancer and a 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', - }, - ], - LogConfiguration: { - LogDriver: 'awslogs', - Options: { - 'awslogs-group': { Ref: 'ServiceTaskDefwebLogGroup2A898F61' }, - 'awslogs-stream-prefix': 'Service', - 'awslogs-region': { Ref: 'AWS::Region' }, - }, - }, + ).toThrow(/You must specify a desiredCount greater than 0/); + }); + + test('having *HealthyPercent properties', () => { + // 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('test Fargate loadbalanced construct with both image and taskDefinition provided', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); - taskDefinition.addContainer('web', { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - memoryLimitMiB: 512, - }); - - // WHEN - expect(() => new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - enableLogging: true, - environment: { - TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', - TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', + // WHEN + new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { + cluster, + memoryLimitMiB: 1024, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), }, - }, - desiredCount: 2, - taskDefinition, - })).toThrow(); -}); - -test('test Fargate application loadbalanced construct with taskDefinition provided', () => { - // GIVEN - const stack = new cdk.Stack(); - const vpc = new ec2.Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); - const container = taskDefinition.addContainer('passedTaskDef', { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - memoryLimitMiB: 512, - }); - container.addPortMappings({ - containerPort: 80, - }); + desiredCount: 1, + minHealthyPercent: 100, + maxHealthyPercent: 200, + }); - // WHEN - new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { - cluster, - taskDefinition, - desiredCount: 2, - memoryLimitMiB: 1024, + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DeploymentConfiguration: { + MinimumHealthyPercent: 100, + MaximumPercent: 200, + }, + }); }); - Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { - ContainerDefinitions: [ - Match.objectLike({ - Image: 'amazon/amazon-ecs-sample', - Memory: 512, - Name: 'passedTaskDef', - PortMappings: [ - { - ContainerPort: 80, - Protocol: 'tcp', - }, - ], + test('having deployment controller', () => { + // 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('ALB - throws if desiredTaskCount is 0', () => { - // 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(), - }), - })); - - // THEN - expect(() => - new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { + // WHEN + new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { cluster, memoryLimitMiB: 1024, taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), }, - desiredCount: 0, - }), - ).toThrow(/You must specify a desiredCount greater than 0/); -}); + deploymentController: { + type: ecs.DeploymentControllerType.CODE_DEPLOY, + }, + }); -test('NLB - throws if desiredTaskCount is 0', () => { - // 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(), - }), - })); + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DeploymentController: { + Type: 'CODE_DEPLOY', + }, + }); + }); - // THEN - expect(() => + test('with circuit breaker', () => { + // 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.NetworkLoadBalancedEc2Service(stack, 'Service', { cluster, memoryLimitMiB: 1024, taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), }, - desiredCount: 0, - }), - ).toThrow(/You must specify a desiredCount greater than 0/); -}); - -test('ALBFargate - having *HealthyPercent properties', () => { - // 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, 'ALB123Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - minHealthyPercent: 100, - maxHealthyPercent: 200, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DeploymentConfiguration: { - MinimumHealthyPercent: 100, - MaximumPercent: 200, - }, - }); -}); - -test('NLBFargate - having *HealthyPercent properties', () => { - // 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, - memoryLimitMiB: 1024, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - desiredCount: 1, - minHealthyPercent: 100, - maxHealthyPercent: 200, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DeploymentConfiguration: { - MinimumHealthyPercent: 100, - MaximumPercent: 200, - }, - }); -}); - -test('ALB - having *HealthyPercent properties', () => { - // 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.ApplicationLoadBalancedEc2Service(stack, 'Service', { - cluster, - memoryLimitMiB: 1024, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - desiredCount: 1, - minHealthyPercent: 100, - maxHealthyPercent: 200, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DeploymentConfiguration: { - MinimumHealthyPercent: 100, - MaximumPercent: 200, - }, - }); -}); - -test('ALB - includes provided protocol version properties', () => { - // 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(), - }), - })); - const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { - cluster, - memoryLimitMiB: 1024, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - desiredCount: 1, - domainName: 'api.example.com', - domainZone: zone, - protocol: ApplicationProtocol.HTTPS, - protocolVersion: ApplicationProtocolVersion.GRPC, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { - ProtocolVersion: 'GRPC', - }); -}); - -test('NLB - having *HealthyPercent properties', () => { - // 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.NetworkLoadBalancedEc2Service(stack, 'Service', { - cluster, - memoryLimitMiB: 1024, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - desiredCount: 1, - minHealthyPercent: 100, - maxHealthyPercent: 200, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DeploymentConfiguration: { - MinimumHealthyPercent: 100, - MaximumPercent: 200, - }, - }); -}); - -test('ALB - having deployment controller', () => { - // 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.ApplicationLoadBalancedEc2Service(stack, 'Service', { - cluster, - memoryLimitMiB: 1024, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - deploymentController: { - type: ecs.DeploymentControllerType.CODE_DEPLOY, - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DeploymentController: { - Type: 'CODE_DEPLOY', - }, - }); -}); - -test('NLB - having deployment controller', () => { - // 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.NetworkLoadBalancedEc2Service(stack, 'Service', { - cluster, - memoryLimitMiB: 1024, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - deploymentController: { - type: ecs.DeploymentControllerType.CODE_DEPLOY, - }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DeploymentController: { - Type: 'CODE_DEPLOY', - }, - }); -}); - -test('ALB with circuit breaker', () => { - // 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.ApplicationLoadBalancedEc2Service(stack, 'Service', { - cluster, - memoryLimitMiB: 1024, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - circuitBreaker: { rollback: true }, - }); + circuitBreaker: { rollback: true }, + }); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DeploymentConfiguration: { - DeploymentCircuitBreaker: { - Enable: true, - Rollback: true, + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DeploymentConfiguration: { + DeploymentCircuitBreaker: { + Enable: true, + Rollback: true, + }, }, - }, - DeploymentController: { - Type: 'ECS', - }, - }); -}); - -test('NLB with circuit breaker', () => { - // 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.NetworkLoadBalancedEc2Service(stack, 'Service', { - cluster, - memoryLimitMiB: 1024, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - circuitBreaker: { rollback: true }, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - DeploymentConfiguration: { - DeploymentCircuitBreaker: { - Enable: true, - Rollback: true, + DeploymentController: { + Type: 'ECS', }, - }, - DeploymentController: { - Type: 'ECS', - }, - }); -}); - -test('NetworkLoadbalancedEC2Service accepts previously created 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' }); - cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { - autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { - vpc, - instanceType: new ec2.InstanceType('t2.micro'), - machineImage: MachineImage.latestAmazonLinux(), - }), - })); - const nlb = new NetworkLoadBalancer(stack, 'NLB', { vpc }); - const taskDef = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); - const container = taskDef.addContainer('Container', { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - memoryLimitMiB: 1024, - }); - container.addPortMappings({ containerPort: 80 }); - - // WHEN - new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { - cluster, - loadBalancer: nlb, - taskDefinition: taskDef, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - LaunchType: 'EC2', - }); - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { - Type: 'network', - }); -}); - -test('NetworkLoadBalancedEC2Service accepts imported load balancer', () => { - // GIVEN - const stack = new cdk.Stack(); - const nlbArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; - const vpc = new ec2.Vpc(stack, 'Vpc'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); - cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { - autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { - vpc, - instanceType: new ec2.InstanceType('t2.micro'), - machineImage: MachineImage.latestAmazonLinux(), - }), - })); - const nlb = NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack, 'NLB', { - loadBalancerArn: nlbArn, - vpc, - }); - const taskDef = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); - const container = taskDef.addContainer('Container', { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - memoryLimitMiB: 1024, - }); - container.addPortMappings({ - containerPort: 80, - }); - - // WHEN - new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { - cluster, - loadBalancer: nlb, - desiredCount: 1, - taskDefinition: taskDef, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - LaunchType: 'EC2', - LoadBalancers: [Match.objectLike({ ContainerName: 'Container', ContainerPort: 80 })], - }); - Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::TargetGroup', 1); - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { - LoadBalancerArn: nlb.loadBalancerArn, - Port: 80, - }); -}); - -test('ApplicationLoadBalancedEC2Service accepts previously created 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' }); - cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { - autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { - vpc, - instanceType: new ec2.InstanceType('t2.micro'), - machineImage: MachineImage.latestAmazonLinux(), - }), - })); - const sg = new ec2.SecurityGroup(stack, 'SG', { vpc }); - const alb = new ApplicationLoadBalancer(stack, 'NLB', { - vpc, - securityGroup: sg, - }); - const taskDef = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); - const container = taskDef.addContainer('Container', { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - memoryLimitMiB: 1024, - }); - container.addPortMappings({ containerPort: 80 }); - - // WHEN - new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { - cluster, - loadBalancer: alb, - taskDefinition: taskDef, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - LaunchType: 'EC2', - }); - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { - Type: 'application', - }); -}); - -test('ApplicationLoadBalancedEC2Service accepts imported load balancer', () => { - // GIVEN - const stack = new cdk.Stack(); - const albArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; - const vpc = new ec2.Vpc(stack, 'Vpc'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); - cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { - autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { - vpc, - instanceType: new ec2.InstanceType('t2.micro'), - machineImage: MachineImage.latestAmazonLinux(), - }), - })); - const sg = new ec2.SecurityGroup(stack, 'SG', { vpc }); - const alb = ApplicationLoadBalancer.fromApplicationLoadBalancerAttributes(stack, 'ALB', { - loadBalancerArn: albArn, - vpc, - securityGroupId: sg.securityGroupId, - loadBalancerDnsName: 'MyName', - }); - const taskDef = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); - const container = taskDef.addContainer('Container', { - image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - memoryLimitMiB: 1024, - }); - container.addPortMappings({ - containerPort: 80, - }); - // WHEN - new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { - cluster, - loadBalancer: alb, - taskDefinition: taskDef, - }); - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { - LaunchType: 'EC2', - LoadBalancers: [Match.objectLike({ ContainerName: 'Container', ContainerPort: 80 })], - }); - Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::TargetGroup', 1); - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { - LoadBalancerArn: alb.loadBalancerArn, - Port: 80, + }); }); -}); -test('test ECS loadbalanced construct default/open security group', () => { - // 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('accepts previously created 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' }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); + const nlb = new NetworkLoadBalancer(stack, 'NLB', { vpc }); + const taskDef = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + const container = taskDef.addContainer('Container', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 1024, + }); + container.addPortMappings({ containerPort: 80 }); - // WHEN - new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { - cluster, - memoryReservationMiB: 1024, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - }, - }); + // WHEN + new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { + cluster, + loadBalancer: nlb, + taskDefinition: taskDef, + }); - // THEN - Stack contains no ingress security group rules - Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroup', { - SecurityGroupIngress: [Match.objectLike({ - CidrIp: '0.0.0.0/0', - FromPort: 80, - IpProtocol: 'tcp', - ToPort: 80, - })], + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + LaunchType: 'EC2', + }); + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { + Type: 'network', + }); }); -}); -test('test ECS loadbalanced construct closed security group', () => { - // 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', { + test('accepts imported load balancer', () => { + // GIVEN + const stack = new cdk.Stack(); + const nlbArn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188'; + const vpc = new ec2.Vpc(stack, 'Vpc'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, clusterName: 'MyCluster' }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); + const nlb = NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack, 'NLB', { + loadBalancerArn: nlbArn, vpc, - instanceType: new ec2.InstanceType('t2.micro'), - machineImage: MachineImage.latestAmazonLinux(), - }), - })); - const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); + }); + const taskDef = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + const container = taskDef.addContainer('Container', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 1024, + }); + container.addPortMappings({ + containerPort: 80, + }); - // WHEN - new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { - cluster, - memoryReservationMiB: 1024, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - }, - domainName: 'api.example.com', - domainZone: zone, - certificate: Certificate.fromCertificateArn(stack, 'Cert', 'helloworld'), - openListener: false, - redirectHTTP: true, - }); + // WHEN + new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', { + cluster, + loadBalancer: nlb, + desiredCount: 1, + taskDefinition: taskDef, + }); - // THEN - Stack contains no ingress security group rules - Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroup', { - SecurityGroupIngress: Match.absent(), + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + LaunchType: 'EC2', + LoadBalancers: [Match.objectLike({ ContainerName: 'Container', ContainerPort: 80 })], + }); + Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::TargetGroup', 1); + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { + LoadBalancerArn: nlb.loadBalancerArn, + Port: 80, + }); }); }); 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 b0cc895c7c4a1..53c34c69b5dde 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 @@ -1,8 +1,11 @@ import { Match, Template } from '../../../assertions'; +import { Certificate } from '../../../aws-certificatemanager'; import { Vpc } from '../../../aws-ec2'; import * as ecs from '../../../aws-ecs'; import { ContainerDefinition, ContainerImage } from '../../../aws-ecs'; +import { ApplicationProtocol, SslPolicy } from '../../../aws-elasticloadbalancingv2'; import { CompositePrincipal, Role, ServicePrincipal } from '../../../aws-iam'; +import { PublicHostedZone } from '../../../aws-route53'; import { Duration, Stack } from '../../../core'; import { ApplicationLoadBalancedFargateService, ApplicationMultipleTargetGroupsFargateService, NetworkLoadBalancedFargateService, NetworkMultipleTargetGroupsFargateService } from '../../lib'; @@ -416,6 +419,292 @@ describe('Application Load Balancer', () => { }); }).toThrow(/You must specify one of: taskDefinition or image/); }); + + test('errors when idleTimeout is over 4000 seconds for multiAlbService', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + + // THEN + expect(() => { + new ApplicationMultipleTargetGroupsFargateService(stack, 'myService', { + cluster: new ecs.Cluster(stack, 'EcsCluster', { vpc }), + memoryLimitMiB: 256, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, + enableExecuteCommand: true, + loadBalancers: [ + { + name: 'lb', + idleTimeout: Duration.seconds(400), + domainName: 'api.example.com', + domainZone: new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }), + listeners: [ + { + name: 'listener', + protocol: ApplicationProtocol.HTTPS, + certificate: Certificate.fromCertificateArn(stack, 'Cert', 'helloworld'), + sslPolicy: SslPolicy.TLS12_EXT, + }, + ], + }, + { + name: 'lb2', + idleTimeout: Duration.seconds(5000), + domainName: 'frontend.com', + domainZone: new PublicHostedZone(stack, 'HostedZone2', { zoneName: 'frontend.com' }), + listeners: [ + { + name: 'listener2', + protocol: ApplicationProtocol.HTTPS, + certificate: Certificate.fromCertificateArn(stack, 'Cert2', 'helloworld'), + sslPolicy: SslPolicy.TLS12_EXT, + }, + ], + }, + ], + targetGroups: [ + { + containerPort: 80, + listener: 'listener', + }, + { + containerPort: 90, + pathPattern: 'a/b/c', + priority: 10, + listener: 'listener', + }, + { + containerPort: 443, + listener: 'listener2', + }, + { + containerPort: 80, + pathPattern: 'a/b/c', + priority: 10, + listener: 'listener2', + }, + ], + }); + }).toThrowError(); + }); + + test('errors when idleTimeout is under 1 seconds for multiAlbService', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + + // THEN + expect(() => { + new ApplicationMultipleTargetGroupsFargateService(stack, 'myService', { + cluster: new ecs.Cluster(stack, 'EcsCluster', { vpc }), + memoryLimitMiB: 256, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, + enableExecuteCommand: true, + loadBalancers: [ + { + name: 'lb', + idleTimeout: Duration.seconds(400), + domainName: 'api.example.com', + domainZone: new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }), + listeners: [ + { + name: 'listener', + protocol: ApplicationProtocol.HTTPS, + certificate: Certificate.fromCertificateArn(stack, 'Cert', 'helloworld'), + sslPolicy: SslPolicy.TLS12_EXT, + }, + ], + }, + { + name: 'lb2', + idleTimeout: Duration.seconds(0), + domainName: 'frontend.com', + domainZone: new PublicHostedZone(stack, 'HostedZone2', { zoneName: 'frontend.com' }), + listeners: [ + { + name: 'listener2', + protocol: ApplicationProtocol.HTTPS, + certificate: Certificate.fromCertificateArn(stack, 'Cert2', 'helloworld'), + sslPolicy: SslPolicy.TLS12_EXT, + }, + ], + }, + ], + targetGroups: [ + { + containerPort: 80, + listener: 'listener', + }, + { + containerPort: 90, + pathPattern: 'a/b/c', + priority: 10, + listener: 'listener', + }, + { + containerPort: 443, + listener: 'listener2', + }, + { + containerPort: 80, + pathPattern: 'a/b/c', + priority: 10, + listener: 'listener2', + }, + ], + }); + }).toThrowError(); + }); + + test('passes when idleTimeout is between 1 and 4000 seconds for multiAlbService', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + + // THEN + expect(() => { + new ApplicationMultipleTargetGroupsFargateService(stack, 'myService', { + cluster: new ecs.Cluster(stack, 'EcsCluster', { vpc }), + memoryLimitMiB: 256, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, + enableExecuteCommand: true, + loadBalancers: [ + { + name: 'lb', + idleTimeout: Duration.seconds(5), + domainName: 'api.example.com', + domainZone: new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }), + listeners: [ + { + name: 'listener', + protocol: ApplicationProtocol.HTTPS, + certificate: Certificate.fromCertificateArn(stack, 'Cert', 'helloworld'), + sslPolicy: SslPolicy.TLS12_EXT, + }, + ], + }, + { + name: 'lb2', + idleTimeout: Duration.seconds(500), + domainName: 'frontend.com', + domainZone: new PublicHostedZone(stack, 'HostedZone2', { zoneName: 'frontend.com' }), + listeners: [ + { + name: 'listener2', + protocol: ApplicationProtocol.HTTPS, + certificate: Certificate.fromCertificateArn(stack, 'Cert2', 'helloworld'), + sslPolicy: SslPolicy.TLS12_EXT, + }, + ], + }, + ], + targetGroups: [ + { + containerPort: 80, + listener: 'listener', + }, + { + containerPort: 90, + pathPattern: 'a/b/c', + priority: 10, + listener: 'listener', + }, + { + containerPort: 443, + listener: 'listener2', + }, + { + containerPort: 80, + pathPattern: 'a/b/c', + priority: 10, + listener: 'listener2', + }, + ], + }); + }).toBeTruthy(); + }); + + test('idletime is undefined when not set for multiAlbService', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + + // WHEN + new ApplicationMultipleTargetGroupsFargateService(stack, 'myService', { + cluster: new ecs.Cluster(stack, 'EcsCluster', { vpc }), + memoryLimitMiB: 256, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, + enableExecuteCommand: true, + loadBalancers: [ + { + name: 'lb', + domainName: 'api.example.com', + domainZone: new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }), + listeners: [ + { + name: 'listener', + protocol: ApplicationProtocol.HTTPS, + certificate: Certificate.fromCertificateArn(stack, 'Cert', 'helloworld'), + sslPolicy: SslPolicy.TLS12_EXT, + }, + ], + }, + { + name: 'lb2', + domainName: 'frontend.com', + domainZone: new PublicHostedZone(stack, 'HostedZone2', { zoneName: 'frontend.com' }), + listeners: [ + { + name: 'listener2', + protocol: ApplicationProtocol.HTTPS, + certificate: Certificate.fromCertificateArn(stack, 'Cert2', 'helloworld'), + sslPolicy: SslPolicy.TLS12_EXT, + }, + ], + }, + ], + targetGroups: [ + { + containerPort: 80, + listener: 'listener', + }, + { + containerPort: 90, + pathPattern: 'a/b/c', + priority: 10, + listener: 'listener', + }, + { + containerPort: 443, + listener: 'listener2', + }, + { + containerPort: 80, + pathPattern: 'a/b/c', + priority: 10, + listener: 'listener2', + }, + ], + }); + + // THEN - stack contains default LoadBalancer Attributes + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { + LoadBalancerAttributes: [ + { + Key: 'deletion_protection.enabled', + Value: 'false', + }, + ], + }); + }); }); }); 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 c103d75c9a448..5092f257a96fb 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 @@ -5,9 +5,10 @@ import * as ec2 from '../../../aws-ec2'; import { MachineImage } from '../../../aws-ec2'; import * as ecs from '../../../aws-ecs'; import { AsgCapacityProvider } from '../../../aws-ecs'; -import { ApplicationLoadBalancer, ApplicationProtocol, NetworkLoadBalancer } from '../../../aws-elasticloadbalancingv2'; +import { ApplicationLoadBalancer, ApplicationProtocol, NetworkLoadBalancer, SslPolicy } from '../../../aws-elasticloadbalancingv2'; import * as iam from '../../../aws-iam'; import * as route53 from '../../../aws-route53'; +import * as cloudmap from '../../../aws-servicediscovery'; import * as cdk from '../../../core'; import * as ecsPatterns from '../../lib'; @@ -809,6 +810,629 @@ describe('ApplicationLoadBalancedFargateService', () => { maxHealthyPercent: 70, })).toThrow(/must be less than/); }); + + test('test Fargate loadbalanced construct', () => { + // 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('test'), + environment: { + TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', + TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', + }, + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, + entryPoint: ['echo', 'running-on-fargate'], + command: ['/bin/bash'], + }, + desiredCount: 2, + }); + + // THEN - stack contains a load balancer and a service + Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 1); + 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', + }, + ], + LogConfiguration: { + LogDriver: 'awslogs', + Options: { + 'awslogs-group': { Ref: 'ServiceTaskDefwebLogGroup2A898F61' }, + 'awslogs-stream-prefix': 'Service', + 'awslogs-region': { Ref: 'AWS::Region' }, + }, + }, + DockerLabels: { + label1: 'labelValue1', + label2: 'labelValue2', + }, + EntryPoint: ['echo', 'running-on-fargate'], + Command: ['/bin/bash'], + }), + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DesiredCount: 2, + LaunchType: 'FARGATE', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { + Port: 80, + Protocol: 'HTTP', + }); + }); + + test('test Fargate loadbalanced construct opting out of log driver creation', () => { + // 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('test'), + enableLogging: false, + environment: { + TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', + TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', + }, + }, + desiredCount: 2, + }); + + // THEN - stack contains a load balancer and a 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', + }, + ], + LogConfiguration: Match.absent(), + }), + ], + }); + }); + + test('test Fargate loadbalanced construct with TLS', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + const zone = new route53.PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + }, + domainName: 'api.example.com', + domainZone: zone, + certificate: Certificate.fromCertificateArn(stack, 'Cert', 'helloworld'), + sslPolicy: SslPolicy.TLS12_EXT, + }); + + // THEN - stack contains a load balancer and a service + Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 1); + + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { + Port: 443, + Protocol: 'HTTPS', + Certificates: [{ + CertificateArn: 'helloworld', + }], + SslPolicy: SslPolicy.TLS12_EXT, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { + Port: 80, + Protocol: 'HTTP', + TargetType: 'ip', + VpcId: { + Ref: 'VPCB9E5F0B4', + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + LaunchType: 'FARGATE', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Route53::RecordSet', { + Name: 'api.example.com.', + HostedZoneId: { + Ref: 'HostedZoneDB99F866', + }, + Type: 'A', + AliasTarget: { + HostedZoneId: { 'Fn::GetAtt': ['ServiceLBE9A1ADBC', 'CanonicalHostedZoneID'] }, + DNSName: { 'Fn::Join': ['', ['dualstack.', { 'Fn::GetAtt': ['ServiceLBE9A1ADBC', 'DNSName'] }]] }, + }, + }); + }); + + test('test Fargateloadbalanced construct with TLS and default certificate', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + const zone = new route53.PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + }, + domainName: 'api.example.com', + domainZone: zone, + protocol: ApplicationProtocol.HTTPS, + }); + + // THEN - stack contains a load balancer, a service, and a certificate + Template.fromStack(stack).hasResourceProperties('AWS::CertificateManager::Certificate', { + DomainName: 'api.example.com', + DomainValidationOptions: [ + { + DomainName: 'api.example.com', + HostedZoneId: { + Ref: 'HostedZoneDB99F866', + }, + }, + ], + ValidationMethod: 'DNS', + }); + + Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 1); + + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::Listener', { + Port: 443, + Protocol: 'HTTPS', + Certificates: [{ + CertificateArn: { + Ref: 'ServiceCertificateA7C65FE6', + }, + }], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + LaunchType: 'FARGATE', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Route53::RecordSet', { + Name: 'api.example.com.', + HostedZoneId: { + Ref: 'HostedZoneDB99F866', + }, + Type: 'A', + AliasTarget: { + HostedZoneId: { 'Fn::GetAtt': ['ServiceLBE9A1ADBC', 'CanonicalHostedZoneID'] }, + DNSName: { 'Fn::Join': ['', ['dualstack.', { 'Fn::GetAtt': ['ServiceLBE9A1ADBC', 'DNSName'] }]] }, + }, + }); + }); + + test('errors when setting domainName but not domainZone', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // THEN + expect(() => { + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + }, + domainName: 'api.example.com', + }); + }).toThrow(); + }); + + test('errors when setting both HTTP protocol and certificate', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // THEN + expect(() => { + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + }, + protocol: ApplicationProtocol.HTTP, + certificate: Certificate.fromCertificateArn(stack, 'Cert', 'helloworld'), + }); + }).toThrow(); + }); + + test('errors when setting both HTTP protocol and redirectHTTP', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // THEN + expect(() => { + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + }, + protocol: ApplicationProtocol.HTTP, + redirectHTTP: true, + }); + }).toThrow(); + }); + + test('does not throw errors when not setting HTTPS protocol but certificate for redirectHTTP', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + const zone = new route53.PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); + + // THEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + }, + domainName: 'api.example.com', + domainZone: zone, + redirectHTTP: true, + certificate: Certificate.fromCertificateArn(stack, 'Cert', 'helloworld'), + }); + }); + + test('errors when setting HTTPS protocol but not domain name', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // THEN + expect(() => { + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + }, + protocol: ApplicationProtocol.HTTPS, + }); + }).toThrow(); + }); + + test('errors when idleTimeout is over 4000 seconds', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // THEN + expect(() => { + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + 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', + }), + }, + idleTimeout: cdk.Duration.seconds(5000), + desiredCount: 2, + }); + }).toThrowError(); + }); + + test('errors when idleTimeout is under 1 seconds', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // THEN + expect(() => { + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + 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', + }), + }, + idleTimeout: cdk.Duration.seconds(0), + desiredCount: 2, + }); + }).toThrowError(); + }); + + test('passes when idleTimeout is between 1 and 4000 seconds', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // THEN + expect(() => { + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + 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', + }), + }, + idleTimeout: cdk.Duration.seconds(4000), + desiredCount: 2, + }); + }).toBeTruthy(); + }); + + test('idletime is undefined when not set', () => { + // 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('test'), + 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', + }), + }, + desiredCount: 2, + }); + + // THEN - stack contains default LoadBalancer Attributes + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { + LoadBalancerAttributes: [ + { + Key: 'deletion_protection.enabled', + Value: 'false', + }, + ], + }); + }); + + test('test Fargate loadbalanced construct with optional log driver input', () => { + // 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('test'), + 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', + }), + }, + desiredCount: 2, + }); + + // THEN - stack contains a load balancer and a 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', + }, + ], + LogConfiguration: { + LogDriver: 'awslogs', + Options: { + 'awslogs-group': { Ref: 'ServiceTaskDefwebLogGroup2A898F61' }, + 'awslogs-stream-prefix': 'TestStream', + 'awslogs-region': { Ref: 'AWS::Region' }, + }, + }, + }), + ], + }); + }); + + test('test Fargate loadbalanced construct with logging enabled', () => { + // 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('test'), + enableLogging: true, + environment: { + TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', + TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', + }, + }, + desiredCount: 2, + }); + + // THEN - stack contains a load balancer and a 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', + }, + ], + LogConfiguration: { + LogDriver: 'awslogs', + Options: { + 'awslogs-group': { Ref: 'ServiceTaskDefwebLogGroup2A898F61' }, + 'awslogs-stream-prefix': 'Service', + 'awslogs-region': { Ref: 'AWS::Region' }, + }, + }, + }), + ], + }); + }); + + test('test Fargate loadbalanced construct with both image and taskDefinition provided', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + // WHEN + expect(() => new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + enableLogging: true, + environment: { + TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', + TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', + }, + }, + desiredCount: 2, + taskDefinition, + })).toThrow(); + }); + + test('test Fargate application loadbalanced construct with taskDefinition provided', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + const container = taskDefinition.addContainer('passedTaskDef', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + container.addPortMappings({ + containerPort: 80, + }); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskDefinition, + desiredCount: 2, + memoryLimitMiB: 1024, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + Match.objectLike({ + Image: 'amazon/amazon-ecs-sample', + Memory: 512, + Name: 'passedTaskDef', + PortMappings: [ + { + ContainerPort: 80, + Protocol: 'tcp', + }, + ], + }), + ], + }); + }); + + test('ALBFargate - having *HealthyPercent properties', () => { + // 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, 'ALB123Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, + minHealthyPercent: 100, + maxHealthyPercent: 200, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DeploymentConfiguration: { + MinimumHealthyPercent: 100, + MaximumPercent: 200, + }, + }); + }); }); describe('NetworkLoadBalancedFargateService', () => { @@ -1297,4 +1921,105 @@ describe('NetworkLoadBalancedFargateService', () => { ]), }); }); + + test('creates AWS Cloud Map service for Private DNS namespace with network load balanced fargate service', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); + + // WHEN + cluster.addDefaultCloudMapNamespace({ + name: 'foo.com', + type: cloudmap.NamespaceType.DNS_PRIVATE, + }); + + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + containerPort: 8000, + image: ecs.ContainerImage.fromRegistry('hello'), + }, + cloudMapOptions: { + name: 'myApp', + }, + memoryLimitMiB: 512, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + ServiceRegistries: [ + { + RegistryArn: { + 'Fn::GetAtt': [ + 'ServiceCloudmapServiceDE76B29D', + 'Arn', + ], + }, + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ServiceDiscovery::Service', { + DnsConfig: { + DnsRecords: [ + { + TTL: 60, + Type: 'A', + }, + ], + NamespaceId: { + 'Fn::GetAtt': [ + 'EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F', + 'Id', + ], + }, + RoutingPolicy: 'MULTIVALUE', + }, + HealthCheckCustomConfig: { + FailureThreshold: 1, + }, + Name: 'myApp', + NamespaceId: { + 'Fn::GetAtt': [ + 'EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F', + 'Id', + ], + }, + }); + }); + + test('having *HealthyPercent properties', () => { + // 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, + memoryLimitMiB: 1024, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }, + desiredCount: 1, + minHealthyPercent: 100, + maxHealthyPercent: 200, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DeploymentConfiguration: { + MinimumHealthyPercent: 100, + MaximumPercent: 200, + }, + }); + }); });