Skip to content

Add enableExecuteCommand for ECS service in blue-green-container-deployment #203

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

Open
wants to merge 50 commits into
base: cdk2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
cad0182
Add support for ECS Exec command
adewolff Jul 20, 2023
7d6ebcc
Publish package to Jfrog
adewolff Jul 20, 2023
fd6ed90
Publish package to Jfrog
adewolff Jul 20, 2023
f6d34a0
fix untracked files
adewolff Jul 20, 2023
6b7ce1e
update version
adewolff Jul 20, 2023
832ca32
Publish
adewolff Jul 20, 2023
e14e941
update version
adewolff Jul 20, 2023
dd68aef
Publish
adewolff Jul 20, 2023
139aab6
update version
adewolff Jul 20, 2023
9c94f91
Publish
adewolff Jul 20, 2023
dfdf949
update version
adewolff Jul 20, 2023
57f5ce4
Publish
adewolff Jul 20, 2023
e0b3b05
update version
adewolff Jul 20, 2023
98ecf8a
Publish
adewolff Jul 20, 2023
a7c0ea8
update version
adewolff Jul 20, 2023
c05e7d8
Publish
adewolff Jul 20, 2023
16204d0
fix enableexecute
adewolff Jul 27, 2023
092268e
update version
adewolff Jul 27, 2023
f7c8b4a
Publish
adewolff Jul 27, 2023
84fdd23
force update
adewolff Jul 27, 2023
ccca16a
Publish
adewolff Jul 27, 2023
75d973e
default enableexecute to false
adewolff Jul 27, 2023
4008219
Publish
adewolff Jul 27, 2023
472bc93
set default value in const
adewolff Jul 27, 2023
e560a35
Publish
adewolff Jul 27, 2023
66f9960
string bug fix
adewolff Jul 27, 2023
99a2661
Publish
adewolff Jul 27, 2023
818a48d
string bug fix v2
adewolff Jul 28, 2023
b892269
Publish
adewolff Jul 28, 2023
efefebe
Add TaskRole to dummy-task-definition.ts
adewolff Jul 31, 2023
ca99106
Publish
adewolff Jul 31, 2023
b9e5d28
Remove managedpolicies
adewolff Jul 31, 2023
c30d1dc
Publish
adewolff Jul 31, 2023
2b076fa
Add compositeprincipal
adewolff Jul 31, 2023
4f935bd
Publish
adewolff Jul 31, 2023
18bd9f8
fix composite principal
adewolff Aug 1, 2023
04974a6
Publish
adewolff Aug 1, 2023
61ea27d
fix composite principal
adewolff Aug 1, 2023
de491f2
Publish
adewolff Aug 1, 2023
4602f79
white space change to force publish
adewolff Aug 2, 2023
7708806
Publish
adewolff Aug 2, 2023
774166e
force publish
adewolff Aug 2, 2023
ebe8a6a
force publish
adewolff Aug 2, 2023
9546b0a
Publish
adewolff Aug 2, 2023
abb3d0a
force publish
adewolff Aug 2, 2023
24b8f88
Publish
adewolff Aug 2, 2023
702be26
force publish
adewolff Aug 2, 2023
51cadc9
Merge published changes
rogperez Aug 2, 2023
b4b7d68
Fix yarn.lock
rogperez Aug 2, 2023
43e478a
Undo package.json and yarn.lock changes
rogperez Aug 2, 2023
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,4 @@ cdk.context.json
*.tsbuildinfo
tsconfig.json
tsconfig.eslint.json
.idea
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
"**/__fixtures__/**",
"**/__tests__/**"
]
}
}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
[{"timestamp":1643739695237,"files":[{"filename":"ecs-deployment-group/index.js","previous":148006,"size":147762,"diff":-244},{"filename":"ecs-service/index.js","previous":147514,"size":147469,"diff":-45}]},{"timestamp":1643739156615,"files":[{"filename":"ecs-deployment-group/index.js","previous":147762,"size":148006,"diff":244},{"filename":"ecs-service/index.js","previous":147469,"size":147514,"diff":45}]},{"timestamp":1643738715672,"files":[{"filename":"ecs-deployment-group/index.js","previous":148006,"size":147762,"diff":-244},{"filename":"ecs-service/index.js","previous":147514,"size":147469,"diff":-45}]},{"timestamp":1643738518282,"files":[{"filename":"ecs-deployment-group/index.js","previous":0,"size":148006,"diff":148006},{"filename":"ecs-service/index.js","previous":0,"size":147514,"diff":147514}]},{"timestamp":1643732386377,"files":[{"filename":"ecs-deployment-group/index.js","previous":147762,"size":0,"diff":-147762},{"filename":"ecs-service/index.js","previous":147469,"size":0,"diff":-147469}]},{"timestamp":1642109009944,"files":[{"filename":"ecs-deployment-group/index.js","previous":145598,"size":147762,"diff":2164},{"filename":"ecs-service/index.js","previous":145304,"size":147469,"diff":2165}]},{"timestamp":1639256487176,"files":[{"filename":"ecs-deployment-group/index.js","previous":145308,"size":145598,"diff":290},{"filename":"ecs-service/index.js","previous":145014,"size":145304,"diff":290}]},{"timestamp":1637794842477,"files":[{"filename":"ecs-deployment-group/index.js","previous":145307,"size":145308,"diff":1},{"filename":"ecs-service/index.js","previous":145016,"size":145014,"diff":-2}]},{"timestamp":1637781788109,"files":[{"filename":"ecs-deployment-group/index.js","previous":144989,"size":145307,"diff":318},{"filename":"ecs-service/index.js","previous":144998,"size":145016,"diff":18}]},{"timestamp":1637708323927,"files":[{"filename":"ecs-deployment-group/index.js","previous":0,"size":144989,"diff":144989},{"filename":"ecs-service/index.js","previous":0,"size":144998,"diff":144998}]}]
[{"timestamp":1690558790308,"files":[{"filename":"ecs-deployment-group/index.js","previous":147762,"size":147762,"diff":0},{"filename":"ecs-service/index.js","previous":147510,"size":147638,"diff":128}]},{"timestamp":1690499998459,"files":[{"filename":"ecs-deployment-group/index.js","previous":147762,"size":147762,"diff":0},{"filename":"ecs-service/index.js","previous":147497,"size":147510,"diff":13}]},{"timestamp":1690491995429,"files":[{"filename":"ecs-deployment-group/index.js","previous":147762,"size":147762,"diff":0},{"filename":"ecs-service/index.js","previous":147512,"size":147497,"diff":-15}]},{"timestamp":1690479814306,"files":[{"filename":"ecs-deployment-group/index.js","previous":147762,"size":147762,"diff":0},{"filename":"ecs-service/index.js","previous":147497,"size":147512,"diff":15}]},{"timestamp":1690475921041,"files":[{"filename":"ecs-deployment-group/index.js","previous":147762,"size":147762,"diff":0},{"filename":"ecs-service/index.js","previous":147496,"size":147497,"diff":1}]},{"timestamp":1689884928007,"files":[{"filename":"ecs-deployment-group/index.js","previous":147762,"size":147762,"diff":0},{"filename":"ecs-service/index.js","previous":147469,"size":147496,"diff":27}]},{"timestamp":1643739695237,"files":[{"filename":"ecs-deployment-group/index.js","previous":148006,"size":147762,"diff":-244},{"filename":"ecs-service/index.js","previous":147514,"size":147469,"diff":-45}]},{"timestamp":1643739156615,"files":[{"filename":"ecs-deployment-group/index.js","previous":147762,"size":148006,"diff":244},{"filename":"ecs-service/index.js","previous":147469,"size":147514,"diff":45}]},{"timestamp":1643738715672,"files":[{"filename":"ecs-deployment-group/index.js","previous":148006,"size":147762,"diff":-244},{"filename":"ecs-service/index.js","previous":147514,"size":147469,"diff":-45}]},{"timestamp":1643738518282,"files":[{"filename":"ecs-deployment-group/index.js","previous":0,"size":148006,"diff":148006},{"filename":"ecs-service/index.js","previous":0,"size":147514,"diff":147514}]},{"timestamp":1643732386377,"files":[{"filename":"ecs-deployment-group/index.js","previous":147762,"size":0,"diff":-147762},{"filename":"ecs-service/index.js","previous":147469,"size":0,"diff":-147469}]},{"timestamp":1642109009944,"files":[{"filename":"ecs-deployment-group/index.js","previous":145598,"size":147762,"diff":2164},{"filename":"ecs-service/index.js","previous":145304,"size":147469,"diff":2165}]},{"timestamp":1639256487176,"files":[{"filename":"ecs-deployment-group/index.js","previous":145308,"size":145598,"diff":290},{"filename":"ecs-service/index.js","previous":145014,"size":145304,"diff":290}]},{"timestamp":1637794842477,"files":[{"filename":"ecs-deployment-group/index.js","previous":145307,"size":145308,"diff":1},{"filename":"ecs-service/index.js","previous":145016,"size":145014,"diff":-2}]},{"timestamp":1637781788109,"files":[{"filename":"ecs-deployment-group/index.js","previous":144989,"size":145307,"diff":318},{"filename":"ecs-service/index.js","previous":144998,"size":145016,"diff":18}]},{"timestamp":1637708323927,"files":[{"filename":"ecs-deployment-group/index.js","previous":0,"size":144989,"diff":144989},{"filename":"ecs-service/index.js","previous":0,"size":144998,"diff":144998}]}]
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ Object {
"Arn",
],
},
"\\",\\"taskRoleArn\\":\\"",
Object {
"Fn::GetAtt": Array [
"DummyTaskDefinitionExecutionRole715DBD43",
"Arn",
],
},
"\\",\\"networkMode\\":\\"awsvpc\\",\\"cpu\\":\\"256\\",\\"memory\\":\\"512\\",\\"containerDefinitions\\":[{\\"name\\":\\"sample-website\\",\\"image\\":\\"image\\",\\"portMappings\\":[{\\"hostPort\\":80,\\"protocol\\":\\"tcp\\",\\"containerPort\\":80}]}]},\\"physicalResourceId\\":{\\"responsePath\\":\\"taskDefinition.taskDefinitionArn\\"}}",
],
],
Expand All @@ -126,6 +133,13 @@ Object {
"Arn",
],
},
"\\",\\"taskRoleArn\\":\\"",
Object {
"Fn::GetAtt": Array [
"DummyTaskDefinitionExecutionRole715DBD43",
"Arn",
],
},
"\\",\\"networkMode\\":\\"awsvpc\\",\\"cpu\\":\\"256\\",\\"memory\\":\\"512\\",\\"containerDefinitions\\":[{\\"name\\":\\"sample-website\\",\\"image\\":\\"image\\",\\"portMappings\\":[{\\"hostPort\\":80,\\"protocol\\":\\"tcp\\",\\"containerPort\\":80}]}]},\\"physicalResourceId\\":{\\"responsePath\\":\\"taskDefinition.taskDefinitionArn\\"}}",
],
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,54 @@ describe('EcsService', () => {
);
});
});

describe('with execute command', () => {
const stack = new cdk.Stack(app, 'MyStackWithExecute');
const cluster = new ecs.Cluster(stack, 'Cluster');
const prodTargetGroup = new elb.ApplicationTargetGroup(stack, 'ProdTargetGroup', { vpc: cluster.vpc });
const testTargetGroup = new elb.ApplicationTargetGroup(stack, 'TestTargetGroup', { vpc: cluster.vpc });
const taskDefinition = new DummyTaskDefinition(stack, 'DummyTaskDefinition', { image: 'nginx' });
const enableExecuteCommand = true

new EcsService(stack, 'Service', {
cluster,
serviceName: 'My Service',
prodTargetGroup,
testTargetGroup,
taskDefinition,
enableExecuteCommand
});

test('Creates a BlueGreenService custom resource', () => {
expectCDK(stack).to(
haveResource('Custom::BlueGreenService', {
EnableExecuteCommand: true,
}),
);
});
});

describe('execute command defaults to false', () => {
const stack = new cdk.Stack(app, 'MyStackWithout');
const cluster = new ecs.Cluster(stack, 'Cluster');
const prodTargetGroup = new elb.ApplicationTargetGroup(stack, 'ProdTargetGroup', { vpc: cluster.vpc });
const testTargetGroup = new elb.ApplicationTargetGroup(stack, 'TestTargetGroup', { vpc: cluster.vpc });
const taskDefinition = new DummyTaskDefinition(stack, 'DummyTaskDefinition', { image: 'nginx' });

new EcsService(stack, 'Service', {
cluster,
serviceName: 'My Service',
prodTargetGroup,
testTargetGroup,
taskDefinition
});

test('Creates a BlueGreenService custom resource', () => {
expectCDK(stack).to(
haveResource('Custom::BlueGreenService', {
EnableExecuteCommand: false,
}),
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export const defaultEcsServiceResourceProperties = {
DeploymentConfiguration: {},
PropogateTags: 'SERVICE',
Tags: [],
EnableExecuteCommand: false
};
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,48 @@ describe('createHandler', () => {
},
});
});

test('sends enable execute true command with create request', async () => {
const response = await handleCreate(
{
...defaultEvent,
RequestType: 'Create',
ResourceProperties: {
...defaultEcsServiceResourceProperties,
EnableExecuteCommand: true,
},
},
defaultContext,
defaultLogger,
);

expect(mockCreateRequest).toHaveBeenCalledWith(
expect.objectContaining({
enableExecuteCommand: true,
}),
);
});

test('sends enable execute false command with create request', async () => {
const response = await handleCreate(
{
...defaultEvent,
RequestType: 'Create',
ResourceProperties: {
...defaultEcsServiceResourceProperties,
EnableExecuteCommand: false,
},
},
defaultContext,
defaultLogger,
);

expect(mockCreateRequest).toHaveBeenCalledWith(
expect.objectContaining({
enableExecuteCommand: false,
}),
);
});
});

describe('updateHandler', () => {
Expand Down Expand Up @@ -138,6 +180,31 @@ describe('updateHandler', () => {
);
});

test('update enable execute command', async () => {
await handleUpdate(
{
...defaultEvent,
RequestType: 'Update',
PhysicalResourceId: 'foo',
ResourceProperties: {
...defaultEcsServiceResourceProperties,
EnableExecuteCommand: true,
},
OldResourceProperties: {
...defaultEcsServiceResourceProperties,
},
},
defaultContext,
defaultLogger,
);

expect(mockUpdateRequest).toHaveBeenCalledWith(
expect.objectContaining({
enableExecuteCommand: true
}),
);
});

test('does not delete keys if no old keys are deleted', async () => {
await handleUpdate(
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { ITaggable, TagManager, TagType, Lazy } from 'aws-cdk-lib';
import { NetworkMode } from 'aws-cdk-lib/aws-ecs';
import { Role, ServicePrincipal, ManagedPolicy, PolicyStatement, Effect, IRole } from 'aws-cdk-lib/aws-iam';
import {
Role,
ServicePrincipal,
ManagedPolicy,
PolicyStatement,
Effect,
IRole,
} from 'aws-cdk-lib/aws-iam';
import {
AwsCustomResource,
AwsCustomResourcePolicy,
Expand Down Expand Up @@ -81,6 +88,7 @@ export class DummyTaskDefinition extends Construct implements IDummyTaskDefiniti
requiresCompatibilities: ['FARGATE'],
family: this.family,
executionRoleArn: this.executionRole.roleArn,
taskRoleArn: this.executionRole.roleArn,
networkMode: NetworkMode.AWS_VPC,
cpu: '256',
memory: '512',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface EcsServiceProps {
readonly prodTargetGroup: ITargetGroup;
readonly testTargetGroup: ITargetGroup;
readonly taskDefinition: DummyTaskDefinition;
readonly enableExecuteCommand?: boolean;

/**
* The period of time, in seconds, that the Amazon ECS service scheduler ignores unhealthy
Expand Down Expand Up @@ -85,6 +86,7 @@ export class EcsService extends Construct implements IConnectable, IEcsService,
testTargetGroup,
taskDefinition,
healthCheckGracePeriod = Duration.seconds(60),
enableExecuteCommand = false,
} = props;

this.tags = new TagManager(TagType.KEY_VALUE, 'TagManager');
Expand Down Expand Up @@ -142,6 +144,7 @@ export class EcsService extends Construct implements IConnectable, IEcsService,
ContainerPort: containerPort,
SchedulingStrategy: SchedulingStrategy.REPLICA,
HealthCheckGracePeriodSeconds: healthCheckGracePeriod.toSeconds(),
EnableExecuteCommand: enableExecuteCommand,
PropagateTags: props.propagateTags,
DeploymentConfiguration: {
maximumPercent: props.maxHealthyPercent ?? 200,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,20 @@ export interface BlueGreenServiceProps {
deploymentConfiguration: ECS.DeploymentConfiguration;
propagateTags: 'SERVICE' | 'TASK_DEFINITION' | string;
tags: Tag[];
enableExecuteCommand: boolean;
}

const ecs = new ECS();

// this method is required to fix a Cloudformation bug which causes properties to be converted to strings.
// Can be removed once resolved: https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/1037
function booleanize(candidate: string | boolean) : boolean {
if(typeof candidate === "boolean") {
return candidate;
}
return candidate === "true";
}

const getProperties = (props: CloudFormationCustomResourceEvent['ResourceProperties']): BlueGreenServiceProps => ({
cluster: props.Cluster,
serviceName: props.ServiceName,
Expand All @@ -52,6 +62,7 @@ const getProperties = (props: CloudFormationCustomResourceEvent['ResourcePropert
deploymentConfiguration: props.DeploymentConfiguration,
propagateTags: props.PropagateTags,
tags: props.Tags ?? [],
enableExecuteCommand: booleanize(props.EnableExecuteCommand),
});

export const handleCreate: OnCreateHandler = async (event): Promise<ResourceHandlerReturn> => {
Expand All @@ -72,6 +83,7 @@ export const handleCreate: OnCreateHandler = async (event): Promise<ResourceHand
deploymentConfiguration,
propagateTags,
tags,
enableExecuteCommand,
} = getProperties(event.ResourceProperties);

const { service } = await ecs
Expand Down Expand Up @@ -105,6 +117,7 @@ export const handleCreate: OnCreateHandler = async (event): Promise<ResourceHand
tags: tags.map((t) => {
return { key: t.Key, value: t.Value };
}),
enableExecuteCommand
})
.promise();

Expand All @@ -127,7 +140,7 @@ export const handleCreate: OnCreateHandler = async (event): Promise<ResourceHand
* For more information, see CreateDeployment in the AWS CodeDeploy API Reference.
*/
export const handleUpdate: OnUpdateHandler = async (event): Promise<ResourceHandlerReturn> => {
const { cluster, serviceName, desiredCount, deploymentConfiguration, healthCheckGracePeriodSeconds, tags } = getProperties(
const { cluster, serviceName, desiredCount, deploymentConfiguration, healthCheckGracePeriodSeconds, tags, enableExecuteCommand } = getProperties(
event.ResourceProperties,
);

Expand All @@ -138,6 +151,7 @@ export const handleUpdate: OnUpdateHandler = async (event): Promise<ResourceHand
desiredCount,
deploymentConfiguration,
healthCheckGracePeriodSeconds,
enableExecuteCommand
})
.promise();

Expand Down