Skip to content

Commit

Permalink
feat(ecs): allow users to provide a CloudMap service to associate wit…
Browse files Browse the repository at this point in the history
…h an ECS service (#13192)

This PR introduces `BaseService.associateCloudMapService()` which allows the user to associate the ECS service with a CloudMap service that they provide.

**API sample**
```ts
const cloudMapService = new cloudmap.Service(...);
const ecsService = new ecs.FargateService(...);

ecsService.associateCloudMapService({
  service: cloudMapService,
});
```

Closes #10057

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
misterjoshua committed Mar 9, 2021
1 parent c4dc3bc commit a7d314c
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 0 deletions.
14 changes: 14 additions & 0 deletions packages/@aws-cdk/aws-ecs/README.md
Expand Up @@ -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.
Expand Down
47 changes: 47 additions & 0 deletions packages/@aws-cdk/aws-ecs/lib/base/base-service.ts
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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
*/
Expand Down
95 changes: 95 additions & 0 deletions packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts
Expand Up @@ -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();
Expand Down

0 comments on commit a7d314c

Please sign in to comment.