diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index 8ac684943e244..cc4bd432e7dba 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -728,6 +728,20 @@ new ecs.Ec2Service(stack, 'Service', { }); ``` +### Associate With a Specific CloudMap Service + +You may associate an ECS service with a specific CloudMap service. To do +this, use the service's `associateCloudMapService` method: + +```ts +const cloudMapService = new cloudmap.Service(...); +const ecsService = new ecs.FargateService(...); + +ecsService.associateCloudMapService({ + service: cloudMapService, +}); +``` + ## Capacity Providers Currently, only `FARGATE` and `FARGATE_SPOT` capacity providers are supported. diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 1e1fdde585f1e..3ff3f20fd8acd 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -601,6 +601,27 @@ export abstract class BaseService extends Resource return cloudmapService; } + /** + * Associates this service with a CloudMap service + */ + public associateCloudMapService(options: AssociateCloudMapServiceOptions): void { + const service = options.service; + + const { containerName, containerPort } = determineContainerNameAndPort({ + taskDefinition: this.taskDefinition, + dnsRecordType: service.dnsRecordType, + container: options.container, + containerPort: options.containerPort, + }); + + // add Cloudmap service to the ECS Service's serviceRegistry + this.addServiceRegistry({ + arn: service.serviceArn, + containerName, + containerPort, + }); + } + /** * This method returns the specified CloudWatch metric name for this service. */ @@ -748,6 +769,10 @@ export abstract class BaseService extends Resource * Associate Service Discovery (Cloud Map) service */ private addServiceRegistry(registry: ServiceRegistry) { + if (this.serviceRegistries.length >= 1) { + throw new Error('Cannot associate with the given service discovery registry. ECS supports at most one service registry per service.'); + } + const sr = this.renderServiceRegistry(registry); this.serviceRegistries.push(sr); } @@ -816,6 +841,28 @@ export interface CloudMapOptions { readonly containerPort?: number; } +/** + * The options for using a cloudmap service. + */ +export interface AssociateCloudMapServiceOptions { + /** + * The cloudmap service to register with. + */ + readonly service: cloudmap.IService; + + /** + * The container to point to for a SRV record. + * @default - the task definition's default container + */ + readonly container?: ContainerDefinition; + + /** + * The port to point to for a SRV record. + * @default - the default port of the task definition's default container + */ + readonly containerPort?: number; +} + /** * Service Registry for ECS service */ diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts index c982920225214..5531b53413f46 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts @@ -262,6 +262,101 @@ nodeunitShim({ test.done(); }, + 'with user-provided cloudmap service'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + const container = taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + container.addPortMappings({ containerPort: 8000 }); + + const cloudMapNamespace = new cloudmap.PrivateDnsNamespace(stack, 'TestCloudMapNamespace', { + name: 'scorekeep.com', + vpc, + }); + + const cloudMapService = new cloudmap.Service(stack, 'Service', { + name: 'service-name', + namespace: cloudMapNamespace, + dnsRecordType: cloudmap.DnsRecordType.SRV, + }); + + const ecsService = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + }); + + // WHEN + ecsService.associateCloudMapService({ + service: cloudMapService, + container: container, + containerPort: 8000, + }); + + // THEN + expect(stack).to(haveResource('AWS::ECS::Service', { + ServiceRegistries: [ + { + ContainerName: 'web', + ContainerPort: 8000, + RegistryArn: { 'Fn::GetAtt': ['ServiceDBC79909', 'Arn'] }, + }, + ], + })); + + test.done(); + }, + + 'errors when more than one service registry used'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + const container = taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + container.addPortMappings({ containerPort: 8000 }); + + const cloudMapNamespace = new cloudmap.PrivateDnsNamespace(stack, 'TestCloudMapNamespace', { + name: 'scorekeep.com', + vpc, + }); + + const ecsService = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + }); + + ecsService.enableCloudMap({ + cloudMapNamespace, + }); + + const cloudMapService = new cloudmap.Service(stack, 'Service', { + name: 'service-name', + namespace: cloudMapNamespace, + dnsRecordType: cloudmap.DnsRecordType.SRV, + }); + + // WHEN / THEN + test.throws(() => { + ecsService.associateCloudMapService({ + service: cloudMapService, + container: container, + containerPort: 8000, + }); + }, /at most one service registry/i); + + test.done(); + }, + 'with all properties set'(test: Test) { // GIVEN const stack = new cdk.Stack();