Skip to content

Commit 8a74121

Browse files
authored
Merge pull request #141 from Verimatrix/tag-cdk-resources
feat: Add tagging support
2 parents 48d8732 + 63dc038 commit 8a74121

14 files changed

Lines changed: 696 additions & 22 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
[{"timestamp":1637517357940,"files":[{"filename":"ecs-deployment-group/index.js","previous":144825,"size":144825,"diff":0},{"filename":"ecs-service/index.js","previous":144797,"size":144818,"diff":21}]},{"timestamp":1634467601943,"files":[{"filename":"ecs-deployment-group/index.js","previous":144792,"size":144825,"diff":33},{"filename":"ecs-service/index.js","previous":2133,"size":144797,"diff":142664}]},{"timestamp":1634411334117,"files":[{"filename":"dummy-task-definition/index.js","previous":1728,"size":0,"diff":-1728},{"filename":"ecs-deployment-group/index.js","previous":2182,"size":144792,"diff":142610},{"filename":"ecs-service/index.js","previous":2138,"size":2133,"diff":-5}]},{"timestamp":1631683651094,"files":[{"filename":"dummy-task-definition/index.js","previous":1728,"size":1728,"diff":0},{"filename":"ecs-deployment-group/index.js","previous":2182,"size":2182,"diff":0},{"filename":"ecs-service/index.js","previous":2152,"size":2138,"diff":-14}]},{"timestamp":1629825530770,"files":[{"filename":"dummy-task-definition/index.js","previous":1728,"size":1728,"diff":0},{"filename":"ecs-deployment-group/index.js","previous":2171,"size":2182,"diff":11},{"filename":"ecs-service/index.js","previous":2152,"size":2152,"diff":0}]},{"timestamp":1629823812165,"files":[{"filename":"dummy-task-definition/index.js","previous":1728,"size":1728,"diff":0},{"filename":"ecs-deployment-group/index.js","previous":2123,"size":2171,"diff":48},{"filename":"ecs-service/index.js","previous":2152,"size":2152,"diff":0}]},{"timestamp":1627652613714,"files":[{"filename":"dummy-task-definition/index.js","previous":1726,"size":1728,"diff":2},{"filename":"ecs-deployment-group/index.js","previous":2123,"size":2123,"diff":0},{"filename":"ecs-service/index.js","previous":2081,"size":2152,"diff":71}]},{"timestamp":1609276390870,"files":[{"filename":"dummy-task-definition/index.js","previous":1712,"size":1726,"diff":14},{"filename":"ecs-deployment-group/index.js","previous":2123,"size":2123,"diff":0},{"filename":"ecs-service/index.js","previous":2081,"size":2081,"diff":0}]},{"timestamp":1606329521054,"files":[{"filename":"dummy-task-definition/index.js","previous":1706,"size":1712,"diff":6},{"filename":"ecs-deployment-group/index.js","previous":2116,"size":2123,"diff":7},{"filename":"ecs-service/index.js","previous":2073,"size":2081,"diff":8}]},{"timestamp":1596457247342,"files":[{"filename":"dummy-task-definition/index.js","previous":1756,"size":1706,"diff":-50},{"filename":"ecs-deployment-group/index.js","previous":2116,"size":2116,"diff":0},{"filename":"ecs-service/index.js","previous":2073,"size":2073,"diff":0}]},{"timestamp":1596454924871,"files":[{"filename":"dummy-task-definition/index.js","previous":4964,"size":1756,"diff":-3208},{"filename":"ecs-deployment-group/index.js","previous":6103,"size":2116,"diff":-3987},{"filename":"ecs-service/index.js","previous":6141,"size":2073,"diff":-4068}]},{"timestamp":1596407637937,"files":[{"filename":"blue-green-service/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"blue-green-service/index.js","previous":3368,"size":0,"diff":-3368},{"filename":"dummy-task-definition/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"dummy-task-definition/index.js","previous":1963,"size":4964,"diff":3001},{"filename":"ecs-deployment-group/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"ecs-deployment-group/index.js","previous":2292,"size":6103,"diff":3811},{"filename":"ecs-service/__entrypoint__.js","previous":6760,"size":0,"diff":-6760},{"filename":"ecs-service/index.js","previous":2312,"size":6141,"diff":3829}]}]
1+
[{"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}]}]

packages/cdk-blue-green-container-deployment/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@
8989
"@aws-cdk/assert": "^1.134.0",
9090
"aws-sdk": "^2.1035.0",
9191
"custom-resource-helper": "^1.0.15",
92-
"jest-cdk-snapshot": "^1.4.2"
92+
"jest-cdk-snapshot": "^1.4.2",
93+
"winston": "^3.3.3"
9394
},
9495
"externals": [
9596
"aws-sdk"
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { expect as expectCDK, haveResource } from '@aws-cdk/assert';
2+
import * as ecs from '@aws-cdk/aws-ecs';
3+
import * as elb from '@aws-cdk/aws-elasticloadbalancingv2';
4+
import * as cdk from '@aws-cdk/core';
5+
6+
import { DummyTaskDefinition } from '../dummy-task-definition';
7+
import { EcsService, PropagateTags } from '../ecs-service';
8+
9+
describe('EcsService', () => {
10+
const app = new cdk.App();
11+
12+
describe('with default props', () => {
13+
const stack = new cdk.Stack(app, 'MyStackWithDefaults');
14+
const cluster = new ecs.Cluster(stack, 'Cluster');
15+
const prodTargetGroup = new elb.ApplicationTargetGroup(stack, 'ProdTargetGroup', { vpc: cluster.vpc });
16+
const testTargetGroup = new elb.ApplicationTargetGroup(stack, 'TestTargetGroup', { vpc: cluster.vpc });
17+
const taskDefinition = new DummyTaskDefinition(stack, 'DummyTaskDefinition', { image: 'nginx' });
18+
19+
new EcsService(stack, 'Service', {
20+
cluster,
21+
serviceName: 'My Service',
22+
prodTargetGroup,
23+
testTargetGroup,
24+
taskDefinition,
25+
});
26+
27+
test('Creates a BlueGreenService custom resource', () => {
28+
expectCDK(stack).to(
29+
haveResource('Custom::BlueGreenService', {
30+
ServiceName: 'My Service',
31+
LaunchType: 'FARGATE',
32+
}),
33+
);
34+
});
35+
});
36+
37+
describe('with tag propagation', () => {
38+
const stack = new cdk.Stack(app, 'MyStackWithTagPropagation');
39+
const cluster = new ecs.Cluster(stack, 'Cluster');
40+
const prodTargetGroup = new elb.ApplicationTargetGroup(stack, 'ProdTargetGroup', { vpc: cluster.vpc });
41+
const testTargetGroup = new elb.ApplicationTargetGroup(stack, 'TestTargetGroup', { vpc: cluster.vpc });
42+
const taskDefinition = new DummyTaskDefinition(stack, 'DummyTaskDefinition', { image: 'nginx' });
43+
44+
new EcsService(stack, 'Service', {
45+
cluster,
46+
serviceName: 'My Service',
47+
prodTargetGroup,
48+
testTargetGroup,
49+
taskDefinition,
50+
propagateTags: PropagateTags.SERVICE,
51+
});
52+
53+
test('enables tag propagation', () => {
54+
expectCDK(stack).to(
55+
haveResource('Custom::BlueGreenService', {
56+
ServiceName: 'My Service',
57+
LaunchType: 'FARGATE',
58+
PropagateTags: 'SERVICE',
59+
}),
60+
);
61+
});
62+
});
63+
64+
describe('with tags', () => {
65+
const stack = new cdk.Stack(app, 'MyStackWithTags');
66+
const cluster = new ecs.Cluster(stack, 'Cluster');
67+
const prodTargetGroup = new elb.ApplicationTargetGroup(stack, 'ProdTargetGroup', { vpc: cluster.vpc });
68+
const testTargetGroup = new elb.ApplicationTargetGroup(stack, 'TestTargetGroup', { vpc: cluster.vpc });
69+
const taskDefinition = new DummyTaskDefinition(stack, 'DummyTaskDefinition', { image: 'nginx' });
70+
71+
cdk.Tags.of(stack).add('Foo', 'Bar');
72+
73+
new EcsService(stack, 'Service', {
74+
cluster,
75+
serviceName: 'My Service',
76+
prodTargetGroup,
77+
testTargetGroup,
78+
taskDefinition,
79+
});
80+
81+
test('adds Tags to the BlueGreenService', () => {
82+
expectCDK(stack).to(
83+
haveResource('Custom::BlueGreenService', {
84+
Tags: [
85+
{
86+
Key: 'Foo',
87+
Value: 'Bar',
88+
},
89+
],
90+
}),
91+
);
92+
});
93+
});
94+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export const defaultContext = {
2+
callbackWaitsForEmptyEventLoop: true,
3+
functionName: 'foo',
4+
functionVersion: 'foo',
5+
invokedFunctionArn: 'arn:aws:lambda:eu-west-1:012345678910:function:MyCustomResourceHandler',
6+
memoryLimitInMB: 'foo',
7+
awsRequestId: 'foo',
8+
logGroupName: 'foo',
9+
logStreamName: 'foo',
10+
getRemainingTimeInMillis: (): number => 5000,
11+
done: (): void => {
12+
return;
13+
},
14+
fail: (): void => {
15+
return;
16+
},
17+
succeed: (): void => {
18+
return;
19+
},
20+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export const defaultEcsServiceResourceProperties = {
2+
ServiceToken: 'foo',
3+
Cluster: 'foo',
4+
ServiceName: 'foo',
5+
ContainerName: 'foo',
6+
TaskDefinition: 'foo',
7+
LaunghType: 'foo',
8+
PlatformVersion: '1.1.0',
9+
DesiredCount: 1,
10+
Subnets: ['foo'],
11+
SecurityGroups: ['foo'],
12+
TargetGroupArn: 'foo',
13+
ContainerPort: 8080,
14+
SchedulingStrategy: 'foo',
15+
HealthCheckGracePeriodSeconds: 3,
16+
DeploymentConfiguration: {},
17+
PropogateTags: 'SERVICE',
18+
Tags: [],
19+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export const defaultEvent = {
2+
ServiceToken: 'foo',
3+
ResponseURL: 'foo',
4+
StackId: 'foo',
5+
RequestId: 'foo',
6+
LogicalResourceId: 'foo',
7+
ResourceType: 'foo',
8+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import * as winston from 'winston';
2+
3+
export const defaultLogger = winston.createLogger();
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
const mockCreateRequest = jest.fn();
2+
const mockUpdateRequest = jest.fn();
3+
const mockTagResourceRequest = jest.fn();
4+
const mockUntagResourceRequest = jest.fn();
5+
6+
jest.mock('aws-sdk', () => {
7+
return {
8+
CodeDeploy: jest.fn().mockImplementation(() => {
9+
return {
10+
createDeploymentGroup: mockCreateRequest.mockReturnValue({
11+
promise: jest.fn().mockResolvedValue({
12+
service: {
13+
serviceArn: 'arn:aws:ecs:us-east-1:012345678910:service/MyCluster/MyService',
14+
serviceName: 'MyService',
15+
},
16+
}),
17+
}),
18+
updateDeploymentGroup: mockUpdateRequest.mockReturnValue({
19+
promise: jest.fn().mockResolvedValue({
20+
service: {
21+
serviceArn: 'arn:aws:ecs:us-east-1:012345678910:service/MyCluster/MyService',
22+
serviceName: 'MyService',
23+
},
24+
}),
25+
}),
26+
tagResource: mockTagResourceRequest.mockReturnValue({
27+
promise: jest.fn().mockResolvedValue({}),
28+
}),
29+
untagResource: mockUntagResourceRequest.mockReturnValue({
30+
promise: jest.fn().mockResolvedValue({}),
31+
}),
32+
};
33+
}),
34+
};
35+
});
36+
37+
import { handleCreate, handleUpdate } from '../../../lambdas/ecs-deployment-group';
38+
import { defaultContext } from '../__fixtures__/default-context';
39+
import { defaultEvent } from '../__fixtures__/default-event';
40+
import { defaultLogger } from '../__fixtures__/default-logger';
41+
42+
const defaultEcsDeploymentGroupProperties = {
43+
ApplicationName: 'TestApplicationName',
44+
DeploymentGroupName: 'TestDeploymentGroupName',
45+
ServiceRoleArn: 'arn:aws:iam::012345678910:role/MyRole',
46+
EcsServices: [
47+
{
48+
ServiceName: 'Foo',
49+
ClusterName: 'Foo',
50+
},
51+
],
52+
TargetGroupNames: ['Foo'],
53+
ProdTrafficListenerArn: 'arn:aws:elasticloadbalancing::012345678910:listener/app/MyApp/foo/prod',
54+
TestTrafficListenerArn: 'arn:aws:elasticloadbalancing::012345678910:listener/app/MyApp/foo/test',
55+
TerminationWaitTimeInMinutes: 5,
56+
};
57+
58+
describe('createHandler', () => {
59+
test('sends tags with create request', async () => {
60+
await handleCreate(
61+
{
62+
...defaultEvent,
63+
RequestType: 'Create',
64+
ResourceProperties: {
65+
ServiceToken: 'foo',
66+
...defaultEcsDeploymentGroupProperties,
67+
Tags: [
68+
{ Key: 'foo', Value: 'bar' },
69+
{ Key: 'k', Value: 'west' },
70+
],
71+
},
72+
},
73+
defaultContext,
74+
defaultLogger,
75+
);
76+
77+
expect(mockCreateRequest).toHaveBeenCalledWith(
78+
expect.objectContaining({
79+
tags: [
80+
{ Key: 'foo', Value: 'bar' },
81+
{ Key: 'k', Value: 'west' },
82+
],
83+
}),
84+
);
85+
});
86+
87+
test('returns the physical id and arn of the deployment group', async () => {
88+
const response = await handleCreate(
89+
{
90+
...defaultEvent,
91+
RequestType: 'Create',
92+
ResourceProperties: {
93+
ServiceToken: 'foo',
94+
...defaultEcsDeploymentGroupProperties,
95+
Tags: [
96+
{ Key: 'foo', Value: 'bar' },
97+
{ Key: 'k', Value: 'west' },
98+
],
99+
},
100+
},
101+
{
102+
...defaultContext,
103+
invokedFunctionArn: 'arn:aws:lambda:eu-west-1:012345678910:function:MyCustomResourceHandler',
104+
},
105+
defaultLogger,
106+
);
107+
108+
expect(response).toEqual(
109+
expect.objectContaining({
110+
physicalResourceId: 'TestDeploymentGroupName',
111+
responseData: {
112+
Arn: 'arn:aws:codedeploy:eu-west-1:012345678910:deploymentgroup:TestApplicationName/TestDeploymentGroupName',
113+
},
114+
}),
115+
);
116+
});
117+
});
118+
119+
describe('updateHandler', () => {
120+
test('sends data update requests', async () => {
121+
await handleUpdate(
122+
{
123+
...defaultEvent,
124+
RequestType: 'Update',
125+
PhysicalResourceId: 'foo',
126+
ResourceProperties: {
127+
ServiceToken: 'foo',
128+
...defaultEcsDeploymentGroupProperties,
129+
Tags: [
130+
{ Key: 'dis', Value: 'dat' },
131+
{ Key: 'k', Value: 'west' },
132+
{ Key: 'ye', Value: 'west' },
133+
],
134+
},
135+
OldResourceProperties: {
136+
ServiceToken: 'foo',
137+
...defaultEcsDeploymentGroupProperties,
138+
Tags: [
139+
{ Key: 'foo', Value: 'bar' },
140+
{ Key: 'k', Value: 'WEST' },
141+
{ Key: 'ye', Value: 'west' },
142+
],
143+
},
144+
},
145+
defaultContext,
146+
defaultLogger,
147+
);
148+
149+
expect(mockUpdateRequest).toHaveBeenCalled();
150+
151+
expect(mockUntagResourceRequest).toHaveBeenCalledWith({
152+
ResourceArn: 'arn:aws:codedeploy:eu-west-1:012345678910:deploymentgroup:TestApplicationName/TestDeploymentGroupName',
153+
TagKeys: ['foo'],
154+
});
155+
156+
expect(mockTagResourceRequest).toHaveBeenCalledWith(
157+
expect.objectContaining({
158+
ResourceArn: 'arn:aws:codedeploy:eu-west-1:012345678910:deploymentgroup:TestApplicationName/TestDeploymentGroupName',
159+
Tags: [
160+
{ Key: 'dis', Value: 'dat' },
161+
{ Key: 'k', Value: 'west' },
162+
{ Key: 'ye', Value: 'west' },
163+
],
164+
}),
165+
);
166+
});
167+
168+
test('returns the physical id and arn of the deployment group', async () => {
169+
const response = await handleUpdate(
170+
{
171+
...defaultEvent,
172+
RequestType: 'Update',
173+
PhysicalResourceId: 'foo',
174+
ResourceProperties: {
175+
...defaultEcsDeploymentGroupProperties,
176+
ServiceToken: 'foo',
177+
},
178+
OldResourceProperties: {
179+
...defaultEcsDeploymentGroupProperties,
180+
},
181+
},
182+
{
183+
...defaultContext,
184+
invokedFunctionArn: 'arn:aws:lambda:us-east-1:012345678910:function:MyCustomResourceHandler',
185+
},
186+
defaultLogger,
187+
);
188+
189+
expect(response).toEqual(
190+
expect.objectContaining({
191+
physicalResourceId: 'TestDeploymentGroupName',
192+
responseData: {
193+
Arn: 'arn:aws:codedeploy:us-east-1:012345678910:deploymentgroup:TestApplicationName/TestDeploymentGroupName',
194+
},
195+
}),
196+
);
197+
});
198+
});

0 commit comments

Comments
 (0)