Skip to content

Commit e15d391

Browse files
iamhopaul123Elad Ben-Israel
authored andcommitted
feat(core): improved API for tags (#3465)
Improved tagging API.
1 parent f7fbbe0 commit e15d391

File tree

3 files changed

+115
-1
lines changed

3 files changed

+115
-1
lines changed

design/tagging-API-change.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Tagging API Change
2+
3+
CDK support tagging and can cascade tags to all its taggable children (see [here](https://docs.aws.amazon.com/cdk/latest/guide/tagging.html)). The current CDK tagging API is shown below:
4+
5+
``` ts
6+
myConstruct.node.applyAspect(new Tag('key', 'value'));
7+
8+
myConstruct.node.applyAspect(new RemoveTag('key', 'value'));
9+
```
10+
11+
As we can see, the current tagging API is not nice and grammatically verbose for using, since there is no reason to expose `node` to users and `applyAspect` does not indicate anything towards tags, which leaves room for improvement. Also, users need to create two objects to add tag and remove tag which causes confusion to some degree.
12+
13+
## General approach
14+
15+
For the tagging behavior part, we propose using just one entry point `Tag` for the new tagging API:
16+
17+
``` ts
18+
Tag.add(myConstruct, 'key', 'value');
19+
20+
Tag.remove(myConstruct, 'key');
21+
```
22+
23+
## Code changes
24+
25+
Given the above, we should make the following changes:
26+
1. Add two methods `add` and `remove` to `Tag` class, which calls `applyAspect`to add tags or remove tags.
27+
28+
# Part1: Change CDK Tagging API
29+
30+
Implementation for the new tagging API is shown below:
31+
32+
``` ts
33+
/**
34+
* The Tag Aspect will handle adding a tag to this node and cascading tags to children
35+
*/
36+
export class Tag extends TagBase {
37+
38+
/**
39+
* add tags to the node of a construct and all its the taggable children
40+
*/
41+
public static add(scope: Construct, key: string, value: string, props: TagProps = {}) {
42+
scope.node.applyAspect(new Tag(key, value, props));
43+
}
44+
45+
/**
46+
* remove tags to the node of a construct and all its the taggable children
47+
*/
48+
public static remove(scope: Construct, key: string, props: TagProps = {}) {
49+
scope.node.applyAspect(new RemoveTag(key, props));
50+
}
51+
52+
...
53+
}
54+
```
55+
56+
And below is an example use case demonstrating how the adjusted tagging API works:
57+
58+
``` ts
59+
// Create Task Definition
60+
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef');
61+
62+
// Create Service
63+
const service = new ecs.Ec2Service(stack, "Service", {
64+
cluster,
65+
taskDefinition,
66+
});
67+
68+
Tag.add(taskDefinition, 'tfoo', 'tbar');
69+
Tag.remove(taskDefinition, 'foo', 'bar');
70+
71+
Tag.add(service, 'sfoo', 'sbar');
72+
Tag.remove(service, 'foo', 'bar');
73+
```

packages/@aws-cdk/core/lib/tag-aspect.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// import cxapi = require('@aws-cdk/cx-api');
22
import { IAspect } from './aspect';
3-
import { IConstruct } from './construct';
3+
import { Construct, IConstruct } from './construct';
44
import { ITaggable, TagManager } from './tag-manager';
55

66
/**
@@ -85,6 +85,20 @@ abstract class TagBase implements IAspect {
8585
*/
8686
export class Tag extends TagBase {
8787

88+
/**
89+
* add tags to the node of a construct and all its the taggable children
90+
*/
91+
public static add(scope: Construct, key: string, value: string, props: TagProps = {}) {
92+
scope.node.applyAspect(new Tag(key, value, props));
93+
}
94+
95+
/**
96+
* remove tags to the node of a construct and all its the taggable children
97+
*/
98+
public static remove(scope: Construct, key: string, props: TagProps = {}) {
99+
scope.node.applyAspect(new RemoveTag(key, props));
100+
}
101+
88102
/**
89103
* The string value of the tag
90104
*/

packages/@aws-cdk/core/test/test.tag-aspect.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,33 @@ export = {
107107
test.deepEqual(res2.tags.renderTags(), [{key: 'first', value: 'there is only 1'}]);
108108
test.done();
109109
},
110+
'add will add a tag and remove will remove a tag if it exists'(test: Test) {
111+
const root = new Stack();
112+
const res = new TaggableResource(root, 'FakeResource', {
113+
type: 'AWS::Fake::Thing',
114+
});
115+
const res2 = new TaggableResource(res, 'FakeResource', {
116+
type: 'AWS::Fake::Thing',
117+
});
118+
const asg = new AsgTaggableResource(res, 'AsgFakeResource', {
119+
type: 'AWS::Fake::Thing',
120+
});
121+
122+
const map = new MapTaggableResource(res, 'MapFakeResource', {
123+
type: 'AWS::Fake::Thing',
124+
});
125+
Tag.add(root, 'root', 'was here');
126+
Tag.add(res, 'first', 'there is only 1');
127+
Tag.remove(res, 'root');
128+
Tag.remove(res, 'doesnotexist');
129+
ConstructNode.prepare(root.node);
130+
131+
test.deepEqual(res.tags.renderTags(), [{key: 'first', value: 'there is only 1'}]);
132+
test.deepEqual(map.tags.renderTags(), {first: 'there is only 1'});
133+
test.deepEqual(asg.tags.renderTags(), [{key: 'first', value: 'there is only 1', propagateAtLaunch: true}]);
134+
test.deepEqual(res2.tags.renderTags(), [{key: 'first', value: 'there is only 1'}]);
135+
test.done();
136+
},
110137
'the #visit function is idempotent'(test: Test) {
111138
const root = new Stack();
112139
const res = new TaggableResource(root, 'FakeResource', {

0 commit comments

Comments
 (0)