@@ -24,6 +24,24 @@ interface StackTag {
24
24
Key : string ;
25
25
Value : string ;
26
26
}
27
+
28
+ /**
29
+ * The results of parsing Tags.
30
+ */
31
+ interface ParseTagsResult {
32
+ /**
33
+ * The "simple" (meaning, not including complex CloudFormation functions)
34
+ * tags that were found.
35
+ */
36
+ readonly tags : Tag [ ] ;
37
+
38
+ /**
39
+ * The collection of "dynamic" (meaning, including complex CloudFormation functions)
40
+ * tags that were found.
41
+ */
42
+ readonly dynamicTags : any ;
43
+ }
44
+
27
45
/**
28
46
* Interface for converter between CloudFormation and internal tag representations
29
47
*/
@@ -38,31 +56,33 @@ interface ITagFormatter {
38
56
*
39
57
* Use the given priority.
40
58
*/
41
- parseTags ( cfnPropertyTags : any , priority : number ) : Tag [ ] ;
59
+ parseTags ( cfnPropertyTags : any , priority : number ) : ParseTagsResult ;
42
60
}
43
61
44
62
/**
45
63
* Standard tags are a list of { key, value } objects
46
64
*/
47
65
class StandardFormatter implements ITagFormatter {
48
- public parseTags ( cfnPropertyTags : any , priority : number ) : Tag [ ] {
66
+ public parseTags ( cfnPropertyTags : any , priority : number ) : ParseTagsResult {
49
67
if ( ! Array . isArray ( cfnPropertyTags ) ) {
50
68
throw new Error ( `Invalid tag input expected array of {key, value} have ${ JSON . stringify ( cfnPropertyTags ) } ` ) ;
51
69
}
52
70
53
71
const tags : Tag [ ] = [ ] ;
72
+ const dynamicTags : any = [ ] ;
54
73
for ( const tag of cfnPropertyTags ) {
55
74
if ( tag . key === undefined || tag . value === undefined ) {
56
- throw new Error ( `Invalid tag input expected {key, value} have ${ JSON . stringify ( tag ) } ` ) ;
75
+ dynamicTags . push ( tag ) ;
76
+ } else {
77
+ // using interp to ensure Token is now string
78
+ tags . push ( {
79
+ key : `${ tag . key } ` ,
80
+ value : `${ tag . value } ` ,
81
+ priority,
82
+ } ) ;
57
83
}
58
- // using interp to ensure Token is now string
59
- tags . push ( {
60
- key : `${ tag . key } ` ,
61
- value : `${ tag . value } ` ,
62
- priority,
63
- } ) ;
64
84
}
65
- return tags ;
85
+ return { tags, dynamicTags } ;
66
86
}
67
87
68
88
public formatTags ( tags : Tag [ ] ) : any {
@@ -73,36 +93,38 @@ class StandardFormatter implements ITagFormatter {
73
93
value : tag . value ,
74
94
} ) ;
75
95
}
76
- return cfnTags . length === 0 ? undefined : cfnTags ;
96
+ return cfnTags ;
77
97
}
78
98
}
79
99
80
100
/**
81
101
* ASG tags are a list of { key, value, propagateAtLaunch } objects
82
102
*/
83
103
class AsgFormatter implements ITagFormatter {
84
- public parseTags ( cfnPropertyTags : any , priority : number ) : Tag [ ] {
85
- const tags : Tag [ ] = [ ] ;
104
+ public parseTags ( cfnPropertyTags : any , priority : number ) : ParseTagsResult {
86
105
if ( ! Array . isArray ( cfnPropertyTags ) ) {
87
106
throw new Error ( `Invalid tag input expected array of {key, value, propagateAtLaunch} have ${ JSON . stringify ( cfnPropertyTags ) } ` ) ;
88
107
}
89
108
109
+ const tags : Tag [ ] = [ ] ;
110
+ const dynamicTags : any = [ ] ;
90
111
for ( const tag of cfnPropertyTags ) {
91
112
if ( tag . key === undefined ||
92
- tag . value === undefined ||
93
- tag . propagateAtLaunch === undefined ) {
94
- throw new Error ( `Invalid tag input expected {key, value, propagateAtLaunch} have ${ JSON . stringify ( tag ) } ` ) ;
113
+ tag . value === undefined ||
114
+ tag . propagateAtLaunch === undefined ) {
115
+ dynamicTags . push ( tag ) ;
116
+ } else {
117
+ // using interp to ensure Token is now string
118
+ tags . push ( {
119
+ key : `${ tag . key } ` ,
120
+ value : `${ tag . value } ` ,
121
+ priority,
122
+ applyToLaunchedInstances : ! ! tag . propagateAtLaunch ,
123
+ } ) ;
95
124
}
96
- // using interp to ensure Token is now string
97
- tags . push ( {
98
- key : `${ tag . key } ` ,
99
- value : `${ tag . value } ` ,
100
- priority,
101
- applyToLaunchedInstances : ! ! tag . propagateAtLaunch ,
102
- } ) ;
103
125
}
104
126
105
- return tags ;
127
+ return { tags, dynamicTags } ;
106
128
}
107
129
108
130
public formatTags ( tags : Tag [ ] ) : any {
@@ -114,20 +136,20 @@ class AsgFormatter implements ITagFormatter {
114
136
propagateAtLaunch : tag . applyToLaunchedInstances !== false ,
115
137
} ) ;
116
138
}
117
- return cfnTags . length === 0 ? undefined : cfnTags ;
139
+ return cfnTags ;
118
140
}
119
141
}
120
142
121
143
/**
122
144
* Some CloudFormation constructs use a { key: value } map for tags
123
145
*/
124
146
class MapFormatter implements ITagFormatter {
125
- public parseTags ( cfnPropertyTags : any , priority : number ) : Tag [ ] {
126
- const tags : Tag [ ] = [ ] ;
147
+ public parseTags ( cfnPropertyTags : any , priority : number ) : ParseTagsResult {
127
148
if ( Array . isArray ( cfnPropertyTags ) || typeof ( cfnPropertyTags ) !== 'object' ) {
128
149
throw new Error ( `Invalid tag input expected map of {key: value} have ${ JSON . stringify ( cfnPropertyTags ) } ` ) ;
129
150
}
130
151
152
+ const tags : Tag [ ] = [ ] ;
131
153
for ( const [ key , value ] of Object . entries ( cfnPropertyTags ) ) {
132
154
tags . push ( {
133
155
key,
@@ -136,23 +158,23 @@ class MapFormatter implements ITagFormatter {
136
158
} ) ;
137
159
}
138
160
139
- return tags ;
161
+ return { tags, dynamicTags : undefined } ;
140
162
}
141
163
142
164
public formatTags ( tags : Tag [ ] ) : any {
143
- const cfnTags : { [ key : string ] : string } = { } ;
165
+ const cfnTags : { [ key : string ] : string } = { } ;
144
166
for ( const tag of tags ) {
145
167
cfnTags [ `${ tag . key } ` ] = `${ tag . value } ` ;
146
168
}
147
- return Object . keys ( cfnTags ) . length === 0 ? undefined : cfnTags ;
169
+ return cfnTags ;
148
170
}
149
171
}
150
172
151
173
/**
152
174
* StackTags are of the format { Key: key, Value: value }
153
175
*/
154
176
class KeyValueFormatter implements ITagFormatter {
155
- public parseTags ( keyValueTags : any , priority : number ) : Tag [ ] {
177
+ public parseTags ( keyValueTags : any , priority : number ) : ParseTagsResult {
156
178
const tags : Tag [ ] = [ ] ;
157
179
for ( const key in keyValueTags ) {
158
180
if ( keyValueTags . hasOwnProperty ( key ) ) {
@@ -164,8 +186,9 @@ class KeyValueFormatter implements ITagFormatter {
164
186
} ) ;
165
187
}
166
188
}
167
- return tags ;
189
+ return { tags, dynamicTags : undefined } ;
168
190
}
191
+
169
192
public formatTags ( unformattedTags : Tag [ ] ) : any {
170
193
const tags : StackTag [ ] = [ ] ;
171
194
unformattedTags . forEach ( tag => {
@@ -174,20 +197,20 @@ class KeyValueFormatter implements ITagFormatter {
174
197
Value : tag . value ,
175
198
} ) ;
176
199
} ) ;
177
- return tags . length > 0 ? tags : undefined ;
200
+ return tags ;
178
201
}
179
202
}
180
203
181
204
class NoFormat implements ITagFormatter {
182
- public parseTags ( _cfnPropertyTags : any ) : Tag [ ] {
183
- return [ ] ;
205
+ public parseTags ( _cfnPropertyTags : any ) : ParseTagsResult {
206
+ return { tags : [ ] , dynamicTags : undefined } ;
184
207
}
208
+
185
209
public formatTags ( _tags : Tag [ ] ) : any {
186
210
return undefined ;
187
211
}
188
212
}
189
213
190
-
191
214
let _tagFormattersCache : { [ key : string ] : ITagFormatter } | undefined ;
192
215
193
216
/**
@@ -203,7 +226,7 @@ function TAG_FORMATTERS(): {[key: string]: ITagFormatter} {
203
226
[ TagType . KEY_VALUE ] : new KeyValueFormatter ( ) ,
204
227
[ TagType . NOT_TAGGABLE ] : new NoFormat ( ) ,
205
228
} ) ;
206
- } ;
229
+ }
207
230
208
231
/**
209
232
* Interface to implement tags.
@@ -258,7 +281,6 @@ export interface TagManagerOptions {
258
281
*
259
282
*/
260
283
export class TagManager {
261
-
262
284
/**
263
285
* Check whether the given construct is Taggable
264
286
*/
@@ -283,6 +305,7 @@ export class TagManager {
283
305
public readonly renderedTags : IResolvable ;
284
306
285
307
private readonly tags = new Map < string , Tag > ( ) ;
308
+ private readonly dynamicTags : any ;
286
309
private readonly priorities = new Map < string , number > ( ) ;
287
310
private readonly tagFormatter : ITagFormatter ;
288
311
private readonly resourceTypeName : string ;
@@ -292,7 +315,9 @@ export class TagManager {
292
315
this . resourceTypeName = resourceTypeName ;
293
316
this . tagFormatter = TAG_FORMATTERS ( ) [ tagType ] ;
294
317
if ( tagStructure !== undefined ) {
295
- this . _setTag ( ...this . tagFormatter . parseTags ( tagStructure , this . initialTagPriority ) ) ;
318
+ const parseTagsResult = this . tagFormatter . parseTags ( tagStructure , this . initialTagPriority ) ;
319
+ this . dynamicTags = parseTagsResult . dynamicTags ;
320
+ this . _setTag ( ...parseTagsResult . tags ) ;
296
321
}
297
322
this . tagPropertyName = options . tagPropertyName || 'tags' ;
298
323
@@ -331,7 +356,14 @@ export class TagManager {
331
356
* tags at synthesis time.
332
357
*/
333
358
public renderTags ( ) : any {
334
- return this . tagFormatter . formatTags ( this . sortedTags ) ;
359
+ const formattedTags = this . tagFormatter . formatTags ( this . sortedTags ) ;
360
+ if ( Array . isArray ( formattedTags ) || Array . isArray ( this . dynamicTags ) ) {
361
+ const ret = [ ...formattedTags ?? [ ] , ...this . dynamicTags ?? [ ] ] ;
362
+ return ret . length > 0 ? ret : undefined ;
363
+ } else {
364
+ const ret = { ...formattedTags ?? { } , ...this . dynamicTags ?? { } } ;
365
+ return Object . keys ( ret ) . length > 0 ? ret : undefined ;
366
+ }
335
367
}
336
368
337
369
/**
@@ -378,7 +410,8 @@ export class TagManager {
378
410
}
379
411
}
380
412
381
- private get sortedTags ( ) {
382
- return Array . from ( this . tags . values ( ) ) . sort ( ( a , b ) => a . key . localeCompare ( b . key ) ) ;
413
+ private get sortedTags ( ) : Tag [ ] {
414
+ return Array . from ( this . tags . values ( ) )
415
+ . sort ( ( a , b ) => a . key . localeCompare ( b . key ) ) ;
383
416
}
384
417
}
0 commit comments