Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ecs): add a service extension interface to ECS services #13235

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions packages/@aws-cdk/aws-ecs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -788,3 +788,32 @@ new ecs.FargateService(stack, 'FargateService', {

app.synth();
```

## Add Service Extensions

You may create and apply packaged sets of modifications to ECS services by
implementing one or both of `IFargateServiceExtension` or `IEc2ServiceExtension`
in your code and by adding the extension to your ECS service.

```ts
class MyStandardScaling implements ecs.IFargateServiceExtension, ecs.IEc2ServiceExtension {
// Compatible with both FargateService and Ec2Service
extend(service: ecs.FargateService | ecs.Ec2Service): void {
service.autoScaleTaskCount({
maxCapacity: 100,
minCapacity: 2,
}).scaleOnCpuUtilization('Target40', {
targetUtilizationPercent: 40,
});
}
}

// Create a fargate service
const fargateService = new ecs.FargateService(...);
// Apply your neatly packaged modification to the service.
fargateService.addExtension(new MyStandardScaling());

// Or create an EC2 service and apply it there.
const ec2Service = new ecs.Ec2Service(...);
ec2Service.addExtension(new MyStandardScaling());
```
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ export class TaskDefinition extends TaskDefinitionBase {
}

/**
* Adds the specified extention to the task definition.
* Adds the specified extension to the task definition.
*
* Extension can be used to apply a packaged modification to
* a task definition.
Expand Down
28 changes: 28 additions & 0 deletions packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,16 @@ export class Ec2Service extends BaseService implements IEc2Service {
}
}

/**
* Adds the specified extension to the service.
*
* Extension can be used to apply a packaged modification to
* a service.
*/
public addExtension(extension: IEc2ServiceExtension) {
extension.extend(this);
}

/**
* Validates this Ec2Service.
*/
Expand Down Expand Up @@ -335,3 +345,21 @@ export class BuiltInAttributes {
*/
public static readonly OS_TYPE = 'attribute:ecs.os-type';
}

/**
* An extension for `FargateService`
*
* Classes that want to make changes to a FargateService (such as applying a
* standard autoscaling pattern) can implement this interface, and can then
* be "added" to a service like so:
*
* service.addExtension(new MyExtension("some_parameter"));
*/
export interface IEc2ServiceExtension {
/**
* Apply the extension to the given service
*
* @param service [disable-awslint:ref-via-interface]
*/
extend(service: Ec2Service): void;
}
28 changes: 28 additions & 0 deletions packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,16 @@ export class FargateService extends BaseService implements IFargateService {
validate: () => !this.taskDefinition.defaultContainer ? ['A TaskDefinition must have at least one essential container'] : [],
});
}

/**
* Adds the specified extention to the service.
*
* Extension can be used to apply a packaged modification to
* a service.
*/
public addExtension(extension: IFargateServiceExtension) {
extension.extend(this);
}
}

/**
Expand Down Expand Up @@ -233,3 +243,21 @@ const SECRET_JSON_FIELD_UNSUPPORTED_PLATFORM_VERSIONS = [
FargatePlatformVersion.VERSION1_2,
FargatePlatformVersion.VERSION1_3,
];

/**
* An extension for `FargateService`
*
* Classes that want to make changes to a FargateService (such as applying a
* standard autoscaling pattern) can implement this interface, and can then
* be "added" to a service like so:
*
* service.addExtension(new MyExtension("some_parameter"));
*/
export interface IFargateServiceExtension {
/**
* Apply the extension to the given service
*
* @param service [disable-awslint:ref-via-interface]
*/
extend(service: FargateService): void;
}
84 changes: 84 additions & 0 deletions packages/@aws-cdk/aws-ecs/test/service-extensions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import * as cdk from '@aws-cdk/core';
import * as ecs from '../lib';
import { expect as expectCDK, haveResource } from '@aws-cdk/assert';

describe('service extensions', () => {
test('allows the user to create an extension that works for both ec2 and fargate services', () => {
// GIVEN
const stack = new cdk.Stack();
const cluster = new ecs.Cluster(stack, 'Cluster');

// A very compatible task definition
const taskDefinition = new ecs.TaskDefinition(stack, 'TaskDefinition', {
compatibility: ecs.Compatibility.EC2_AND_FARGATE,
cpu: '256',
memoryMiB: '512',
});
const mainContainer = taskDefinition.addContainer('main', {
image: ecs.ContainerImage.fromRegistry('nginx'),
memoryLimitMiB: 512,
});
mainContainer.addPortMappings({ containerPort: 80 });

// A very compatible service extension
class MyStandardScaling implements ecs.IFargateServiceExtension, ecs.IEc2ServiceExtension {
extend(s: ecs.FargateService | ecs.Ec2Service): void {
s.autoScaleTaskCount({
maxCapacity: 100,
minCapacity: 2,
}).scaleOnCpuUtilization('Target40', {
targetUtilizationPercent: 40,
});
}
}

const myStandardScalingExtension = new MyStandardScaling();
const extendSpy = jest.spyOn(myStandardScalingExtension, 'extend');

const fargateService = new ecs.FargateService(stack, 'FargateService', {
cluster,
taskDefinition,
});

const ec2Service = new ecs.FargateService(stack, 'Ec2Service', {
cluster,
taskDefinition,
});

// WHEN
fargateService.addExtension(myStandardScalingExtension);
ec2Service.addExtension(myStandardScalingExtension);

// THEN
expect(extendSpy).toBeCalledWith(fargateService);
expect(extendSpy).toBeCalledWith(ec2Service);

expectCDK(stack).to(haveResource('AWS::ApplicationAutoScaling::ScalableTarget', {
ResourceId: {
'Fn::Join': [
'',
[
'service/',
{ Ref: 'ClusterEB0386A7' },
'/',
{ 'Fn::GetAtt': ['FargateServiceAC2B3B85', 'Name'] },
],
],
},
}));

expectCDK(stack).to(haveResource('AWS::ApplicationAutoScaling::ScalableTarget', {
ResourceId: {
'Fn::Join': [
'',
[
'service/',
{ Ref: 'ClusterEB0386A7' },
'/',
{ 'Fn::GetAtt': ['Ec2Service04A33183', 'Name'] },
],
],
},
}));
});
});