Skip to content

Commit e6e8c51

Browse files
authored
feat(aws-codedeploy): add instance tag filter support for server Deployment Groups. (#824)
1 parent 2af8309 commit e6e8c51

File tree

3 files changed

+248
-0
lines changed

3 files changed

+248
-0
lines changed

packages/@aws-cdk/aws-codedeploy/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,27 @@ const deploymentGroup = new codedeploy.ServerDeploymentGroup(this, 'CodeDeployDe
3232
// adds User Data that installs the CodeDeploy agent on your auto-scaling groups hosts
3333
// default: true
3434
installAgent: true,
35+
// adds EC2 instances matching tags
36+
ec2InstanceTags: new codedeploy.InstanceTagSet(
37+
{
38+
// any instance with tags satisfying
39+
// key1=v1 or key1=v2 or key2 (any value) or value v3 (any key)
40+
// will match this group
41+
'key1': ['v1', 'v2'],
42+
'key2': [],
43+
'': ['v3'],
44+
},
45+
),
46+
// adds on-premise instances matching tags
47+
onPremiseInstanceTags: new codedeploy.InstanceTagSet(
48+
// only instances with tags (key1=v1 or key1=v2) AND key2=v3 will match this set
49+
{
50+
'key1': ['v1', 'v2'],
51+
},
52+
{
53+
'key2': ['v3'],
54+
},
55+
),
3556
});
3657
```
3758

packages/@aws-cdk/aws-codedeploy/lib/deployment-group.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,41 @@ class ImportedServerDeploymentGroupRef extends ServerDeploymentGroupRef {
9999
}
100100
}
101101

102+
/**
103+
* Represents a group of instance tags.
104+
* An instance will match a group if it has a tag matching
105+
* any of the group's tags by key and any of the provided values -
106+
* in other words, tag groups follow 'or' semantics.
107+
* If the value for a given key is an empty array,
108+
* an instance will match when it has a tag with the given key,
109+
* regardless of the value.
110+
* If the key is an empty string, any tag,
111+
* regardless of its key, with any of the given values, will match.
112+
*/
113+
export type InstanceTagGroup = {[key: string]: string[]};
114+
115+
/**
116+
* Represents a set of instance tag groups.
117+
* An instance will match a set if it matches all of the groups in the set -
118+
* in other words, sets follow 'and' semantics.
119+
* You can have a maximum of 3 tag groups inside a set.
120+
*/
121+
export class InstanceTagSet {
122+
private readonly _instanceTagGroups: InstanceTagGroup[];
123+
124+
constructor(...instanceTagGroups: InstanceTagGroup[]) {
125+
if (instanceTagGroups.length > 3) {
126+
throw new Error('An instance tag set can have a maximum of 3 instance tag groups, ' +
127+
`but ${instanceTagGroups.length} were provided`);
128+
}
129+
this._instanceTagGroups = instanceTagGroups;
130+
}
131+
132+
public get instanceTagGroups(): InstanceTagGroup[] {
133+
return this._instanceTagGroups.slice();
134+
}
135+
}
136+
102137
/**
103138
* Construction properties for {@link ServerDeploymentGroup}.
104139
*/
@@ -153,6 +188,20 @@ export interface ServerDeploymentGroupProps {
153188
* @default the Deployment Group will not have a load balancer defined
154189
*/
155190
loadBalancer?: codedeploylb.ILoadBalancer;
191+
192+
/*
193+
* All EC2 instances matching the given set of tags when a deployment occurs will be added to this Deployment Group.
194+
*
195+
* @default no additional EC2 instances will be added to the Deployment Group
196+
*/
197+
ec2InstanceTags?: InstanceTagSet;
198+
199+
/**
200+
* All on-premise instances matching the given set of tags when a deployment occurs will be added to this Deployment Group.
201+
*
202+
* @default no additional on-premise instances will be added to the Deployment Group
203+
*/
204+
onPremiseInstanceTags?: InstanceTagSet;
156205
}
157206

158207
/**
@@ -204,6 +253,8 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupRef {
204253
: {
205254
deploymentOption: 'WITH_TRAFFIC_CONTROL',
206255
},
256+
ec2TagSet: this.ec2TagSet(props.ec2InstanceTags),
257+
onPremisesTagSet: this.onPremiseTagSet(props.onPremiseInstanceTags),
207258
});
208259

209260
this.deploymentGroupName = resource.deploymentGroupName;
@@ -284,6 +335,75 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupRef {
284335
};
285336
}
286337
}
338+
339+
private ec2TagSet(tagSet?: InstanceTagSet):
340+
cloudformation.DeploymentGroupResource.EC2TagSetProperty | undefined {
341+
if (!tagSet || tagSet.instanceTagGroups.length === 0) {
342+
return undefined;
343+
}
344+
345+
return {
346+
ec2TagSetList: tagSet.instanceTagGroups.map(tagGroup => {
347+
return {
348+
ec2TagGroup: this.tagGroup2TagsArray(tagGroup) as
349+
cloudformation.DeploymentGroupResource.EC2TagFilterProperty[],
350+
};
351+
}),
352+
};
353+
}
354+
355+
private onPremiseTagSet(tagSet?: InstanceTagSet):
356+
cloudformation.DeploymentGroupResource.OnPremisesTagSetProperty | undefined {
357+
if (!tagSet || tagSet.instanceTagGroups.length === 0) {
358+
return undefined;
359+
}
360+
361+
return {
362+
onPremisesTagSetList: tagSet.instanceTagGroups.map(tagGroup => {
363+
return {
364+
onPremisesTagGroup: this.tagGroup2TagsArray(tagGroup) as
365+
cloudformation.DeploymentGroupResource.TagFilterProperty[],
366+
};
367+
}),
368+
};
369+
}
370+
371+
private tagGroup2TagsArray(tagGroup: InstanceTagGroup): any[] {
372+
const tagsInGroup = [];
373+
for (const tagKey in tagGroup) {
374+
if (tagGroup.hasOwnProperty(tagKey)) {
375+
const tagValues = tagGroup[tagKey];
376+
if (tagKey.length > 0) {
377+
if (tagValues.length > 0) {
378+
for (const tagValue of tagValues) {
379+
tagsInGroup.push({
380+
key: tagKey,
381+
value: tagValue,
382+
type: 'KEY_AND_VALUE',
383+
});
384+
}
385+
} else {
386+
tagsInGroup.push({
387+
key: tagKey,
388+
type: 'KEY_ONLY',
389+
});
390+
}
391+
} else {
392+
if (tagValues.length > 0) {
393+
for (const tagValue of tagValues) {
394+
tagsInGroup.push({
395+
value: tagValue,
396+
type: 'VALUE_ONLY',
397+
});
398+
}
399+
} else {
400+
throw new Error('Cannot specify both an empty key and no values for an instance tag filter');
401+
}
402+
}
403+
}
404+
}
405+
return tagsInGroup;
406+
}
287407
}
288408

289409
function deploymentGroupName2Arn(applicationName: string, deploymentGroupName: string): string {

packages/@aws-cdk/aws-codedeploy/test/test.deployment-group.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,5 +157,112 @@ export = {
157157

158158
test.done();
159159
},
160+
161+
'can be created with a single EC2 instance tag set with a single or no value'(test: Test) {
162+
const stack = new cdk.Stack();
163+
164+
new codedeploy.ServerDeploymentGroup(stack, 'DeploymentGroup', {
165+
ec2InstanceTags: new codedeploy.InstanceTagSet(
166+
{
167+
'some-key': ['some-value'],
168+
'other-key': [],
169+
},
170+
),
171+
});
172+
173+
expect(stack).to(haveResource('AWS::CodeDeploy::DeploymentGroup', {
174+
"Ec2TagSet": {
175+
"Ec2TagSetList": [
176+
{
177+
"Ec2TagGroup": [
178+
{
179+
"Key": "some-key",
180+
"Value": "some-value",
181+
"Type": "KEY_AND_VALUE",
182+
},
183+
{
184+
"Key": "other-key",
185+
"Type": "KEY_ONLY",
186+
},
187+
],
188+
},
189+
],
190+
},
191+
}));
192+
193+
test.done();
194+
},
195+
196+
'can be created with two on-premise instance tag sets with multiple values or without a key'(test: Test) {
197+
const stack = new cdk.Stack();
198+
199+
new codedeploy.ServerDeploymentGroup(stack, 'DeploymentGroup', {
200+
onPremiseInstanceTags: new codedeploy.InstanceTagSet(
201+
{
202+
'some-key': ['some-value', 'another-value'],
203+
},
204+
{
205+
'': ['keyless-value'],
206+
},
207+
),
208+
});
209+
210+
expect(stack).to(haveResource('AWS::CodeDeploy::DeploymentGroup', {
211+
"OnPremisesTagSet": {
212+
"OnPremisesTagSetList": [
213+
{
214+
"OnPremisesTagGroup": [
215+
{
216+
"Key": "some-key",
217+
"Value": "some-value",
218+
"Type": "KEY_AND_VALUE",
219+
},
220+
{
221+
"Key": "some-key",
222+
"Value": "another-value",
223+
"Type": "KEY_AND_VALUE",
224+
},
225+
],
226+
},
227+
{
228+
"OnPremisesTagGroup": [
229+
{
230+
"Value": "keyless-value",
231+
"Type": "VALUE_ONLY",
232+
},
233+
],
234+
},
235+
],
236+
},
237+
}));
238+
239+
test.done();
240+
},
241+
242+
'cannot be created with an instance tag set containing a keyless, valueless filter'(test: Test) {
243+
const stack = new cdk.Stack();
244+
245+
test.throws(() => {
246+
new codedeploy.ServerDeploymentGroup(stack, 'DeploymentGroup', {
247+
onPremiseInstanceTags: new codedeploy.InstanceTagSet({
248+
'': [],
249+
}),
250+
});
251+
});
252+
253+
test.done();
254+
},
255+
256+
'cannot be created with an instance tag set containing 4 instance tag groups'(test: Test) {
257+
const stack = new cdk.Stack();
258+
259+
test.throws(() => {
260+
new codedeploy.ServerDeploymentGroup(stack, 'DeploymentGroup', {
261+
onPremiseInstanceTags: new codedeploy.InstanceTagSet({}, {}, {}, {}),
262+
});
263+
}, /3/);
264+
265+
test.done();
266+
},
160267
},
161268
};

0 commit comments

Comments
 (0)