1
+ import cxapi = require( '@aws-cdk/cx-api' ) ;
1
2
import colors = require( 'colors/safe' ) ;
2
3
import { format } from 'util' ;
3
4
import { Difference , isPropertyDifference , ResourceDifference , ResourceImpact } from './diff-template' ;
4
- import { TemplateDiff } from './diff/types' ;
5
+ import { DifferenceCollection , TemplateDiff } from './diff/types' ;
5
6
import { deepEqual } from './diff/util' ;
6
7
7
8
/**
8
9
* Renders template differences to the process' console.
9
10
*
10
11
* @param templateDiff TemplateDiff to be rendered to the console.
12
+ * @param logicalToPathMap A map from logical ID to construct path. Useful in
13
+ * case there is no aws:cdk:path metadata in the template.
11
14
*/
12
- export function formatDifferences ( stream : NodeJS . WriteStream , templateDiff : TemplateDiff ) {
15
+ export function formatDifferences ( stream : NodeJS . WriteStream , templateDiff : TemplateDiff , logicalToPathMap : { [ logicalId : string ] : string } = { } ) {
13
16
function print ( fmt : string , ...args : any [ ] ) {
14
17
stream . write ( colors . white ( format ( fmt , ...args ) ) + '\n' ) ;
15
18
}
16
19
17
- const ADDITION = colors . green ( '[+]' ) ;
18
- const UPDATE = colors . yellow ( '[~]' ) ;
20
+ const ADDITION = colors . green ( '[+]' ) ; const UPDATE = colors . yellow ( '[~]' ) ;
19
21
const REMOVAL = colors . red ( '[-]' ) ;
20
22
21
- formatDifference ( 'AWSTemplateFormatVersion' , templateDiff . awsTemplateFormatVersion ) ;
22
- formatDifference ( 'Transform' , templateDiff . transform ) ;
23
- formatDifference ( 'Description' , templateDiff . description ) ;
24
- templateDiff . parameters . forEach ( formatDifference ) ;
25
- templateDiff . metadata . forEach ( formatDifference ) ;
26
- templateDiff . mappings . forEach ( formatDifference ) ;
27
- templateDiff . conditions . forEach ( formatDifference ) ;
28
- templateDiff . resources . forEach ( formatResourceDifference ) ;
29
- templateDiff . outputs . forEach ( formatDifference ) ;
30
- templateDiff . unknown . forEach ( formatDifference ) ;
23
+ if ( templateDiff . awsTemplateFormatVersion || templateDiff . transform || templateDiff . description ) {
24
+ printSectionHeader ( 'Template' ) ;
25
+ formatDifference ( 'AWSTemplateFormatVersion' , 'AWSTemplateFormatVersion' , templateDiff . awsTemplateFormatVersion ) ;
26
+ formatDifference ( 'Transform' , 'Transform' , templateDiff . transform ) ;
27
+ formatDifference ( 'Description' , 'Description' , templateDiff . description ) ;
28
+ printSectionFooter ( ) ;
29
+ }
30
+
31
+ formatSection ( 'Parameters' , 'Parameter' , templateDiff . parameters ) ;
32
+ formatSection ( 'Metadata' , 'Metadata' , templateDiff . metadata ) ;
33
+ formatSection ( 'Mappings' , 'Mapping' , templateDiff . mappings ) ;
34
+ formatSection ( 'Conditions' , 'Condition' , templateDiff . conditions ) ;
35
+ formatSection ( 'Resources' , 'Resource' , templateDiff . resources , formatResourceDifference ) ;
36
+ formatSection ( 'Outputs' , 'Output' , templateDiff . outputs ) ;
37
+ formatSection ( 'Other Changes' , 'Unknown' , templateDiff . unknown ) ;
38
+
39
+ function formatSection < V , T extends Difference < V > > (
40
+ title : string ,
41
+ entryType : string ,
42
+ collection : DifferenceCollection < V , T > ,
43
+ formatter : ( type : string , id : string , diff : T ) => void = formatDifference ) {
44
+
45
+ if ( collection . count === 0 ) {
46
+ return ;
47
+ }
48
+
49
+ printSectionHeader ( title ) ;
50
+ collection . forEach ( ( id , diff ) => formatter ( entryType , id , diff ) ) ;
51
+ printSectionFooter ( ) ;
52
+ }
53
+
54
+ function printSectionHeader ( title : string ) {
55
+ print ( colors . underline ( colors . bold ( title ) ) ) ;
56
+ }
57
+
58
+ function printSectionFooter ( ) {
59
+ print ( '' ) ;
60
+ }
31
61
32
62
/**
33
63
* Print a simple difference for a given named entity.
34
64
*
35
- * @param name the name of the entity that is different.
65
+ * @param logicalId the name of the entity that is different.
36
66
* @param diff the difference to be rendered.
37
67
*/
38
- function formatDifference ( name : string , diff : Difference < any > | undefined ) {
68
+ function formatDifference ( type : string , logicalId : string , diff : Difference < any > | undefined ) {
39
69
if ( ! diff ) { return ; }
70
+
71
+ let value ;
72
+
40
73
const oldValue = formatValue ( diff . oldValue , colors . red ) ;
41
74
const newValue = formatValue ( diff . newValue , colors . green ) ;
42
75
if ( diff . isAddition ) {
43
- print ( '%s Added %s: %s' , ADDITION , colors . blue ( name ) , newValue ) ;
76
+ value = newValue ;
44
77
} else if ( diff . isUpdate ) {
45
- print ( '%s Updated %s: %s to %s' , UPDATE , colors . blue ( name ) , oldValue , newValue ) ;
78
+ value = ` ${ oldValue } to ${ newValue } ` ;
46
79
} else if ( diff . isRemoval ) {
47
- print ( '%s Removed %s: %s' , REMOVAL , colors . blue ( name ) , oldValue ) ;
80
+ value = oldValue ;
48
81
}
82
+
83
+ print ( `${ formatPrefix ( diff ) } ${ colors . cyan ( type ) } ${ formatLogicalId ( logicalId ) } : ${ value } ` ) ;
49
84
}
50
85
51
86
/**
@@ -54,34 +89,28 @@ export function formatDifferences(stream: NodeJS.WriteStream, templateDiff: Temp
54
89
* @param logicalId the logical ID of the resource that changed.
55
90
* @param diff the change to be rendered.
56
91
*/
57
- function formatResourceDifference ( logicalId : string , diff : ResourceDifference ) {
58
- if ( diff . isAddition ) {
59
- print ( '%s %s %s (type: %s)' ,
60
- ADDITION ,
61
- formatImpact ( diff . changeImpact ) ,
62
- colors . blue ( logicalId ) ,
63
- formatValue ( diff . newResourceType , colors . green ) ) ;
64
- } else if ( diff . isUpdate ) {
65
- print ( '%s %s %s (type: %s)' ,
66
- UPDATE ,
67
- formatImpact ( diff . changeImpact ) ,
68
- colors . blue ( logicalId ) ,
69
- formatValue ( diff . newResourceType , colors . green ) ) ;
92
+ function formatResourceDifference ( _type : string , logicalId : string , diff : ResourceDifference ) {
93
+ const resourceType = diff . isRemoval ? diff . oldResourceType : diff . newResourceType ;
94
+
95
+ // tslint:disable-next-line:max-line-length
96
+ print ( `${ formatPrefix ( diff ) } ${ formatValue ( resourceType , colors . cyan ) } ${ formatLogicalId ( logicalId , diff ) } ${ formatImpact ( diff . changeImpact ) } ` ) ;
97
+
98
+ if ( diff . isUpdate ) {
70
99
let processedCount = 0 ;
71
- diff . forEach ( ( type , name , values ) => {
100
+ diff . forEach ( ( _ , name , values ) => {
72
101
processedCount += 1 ;
73
- if ( type === 'Property' ) { name = `.${ name } ` ; }
74
102
formatTreeDiff ( name , values , processedCount === diff . count ) ;
75
103
} ) ;
76
- } else if ( diff . isRemoval ) {
77
- print ( '%s %s %s (type: %s)' ,
78
- REMOVAL ,
79
- formatImpact ( diff . changeImpact ) ,
80
- colors . blue ( logicalId ) ,
81
- formatValue ( diff . oldResourceType , colors . green ) ) ;
82
104
}
83
105
}
84
106
107
+ function formatPrefix < T > ( diff : Difference < T > ) {
108
+ if ( diff . isAddition ) { return ADDITION ; }
109
+ if ( diff . isUpdate ) { return UPDATE ; }
110
+ if ( diff . isRemoval ) { return REMOVAL ; }
111
+ return colors . white ( '[?]' ) ;
112
+ }
113
+
85
114
/**
86
115
* @param value the value to be formatted.
87
116
* @param color the color to be used.
@@ -101,17 +130,16 @@ export function formatDifferences(stream: NodeJS.WriteStream, templateDiff: Temp
101
130
function formatImpact ( impact : ResourceImpact ) {
102
131
switch ( impact ) {
103
132
case ResourceImpact . MAY_REPLACE :
104
- return colors . yellow ( '⚠️ May be replacing' ) ;
133
+ return colors . italic ( colors . yellow ( 'may be replaced' ) ) ;
105
134
case ResourceImpact . WILL_REPLACE :
106
- return colors . bold ( colors . yellow ( '⚠️ Replacing' ) ) ;
135
+ return colors . italic ( colors . bold ( colors . yellow ( 'replace' ) ) ) ;
107
136
case ResourceImpact . WILL_DESTROY :
108
- return colors . bold ( colors . red ( '☢️ Destroying' ) ) ;
137
+ return colors . italic ( colors . bold ( colors . red ( 'destroy' ) ) ) ;
109
138
case ResourceImpact . WILL_ORPHAN :
110
- return colors . red ( '🗑 Orphaning' ) ;
139
+ return colors . italic ( colors . yellow ( 'orphan' ) ) ;
111
140
case ResourceImpact . WILL_UPDATE :
112
- return colors . green ( '🛠 Updating' ) ;
113
141
case ResourceImpact . WILL_CREATE :
114
- return colors . green ( '🆕 Creating' ) ;
142
+ return '' ; // no extra info is gained here
115
143
}
116
144
}
117
145
@@ -130,7 +158,7 @@ export function formatDifferences(stream: NodeJS.WriteStream, templateDiff: Temp
130
158
additionalInfo = ' (requires replacement)' ;
131
159
}
132
160
}
133
- print ( ' %s─ %s %s%s: ' , last ? '└' : '├' , changeTag ( diff . oldValue , diff . newValue ) , colors . blue ( ` ${ name } ` ) , additionalInfo ) ;
161
+ print ( ' %s─ %s %s%s' , last ? '└' : '├' , changeTag ( diff . oldValue , diff . newValue ) , name , additionalInfo ) ;
134
162
return formatObjectDiff ( diff . oldValue , diff . newValue , ` ${ last ? ' ' : '│' } ` ) ;
135
163
}
136
164
@@ -145,12 +173,12 @@ export function formatDifferences(stream: NodeJS.WriteStream, templateDiff: Temp
145
173
function formatObjectDiff ( oldObject : any , newObject : any , linePrefix : string ) {
146
174
if ( ( typeof oldObject !== typeof newObject ) || Array . isArray ( oldObject ) || typeof oldObject === 'string' || typeof oldObject === 'number' ) {
147
175
if ( oldObject !== undefined && newObject !== undefined ) {
148
- print ( '%s ├─ %s Old value: %s' , linePrefix , REMOVAL , formatValue ( oldObject , colors . red ) ) ;
149
- print ( '%s └─ %s New value: %s' , linePrefix , ADDITION , formatValue ( newObject , colors . green ) ) ;
176
+ print ( '%s ├─ %s %s' , linePrefix , REMOVAL , formatValue ( oldObject , colors . red ) ) ;
177
+ print ( '%s └─ %s %s' , linePrefix , ADDITION , formatValue ( newObject , colors . green ) ) ;
150
178
} else if ( oldObject !== undefined /* && newObject === undefined */ ) {
151
- print ( '%s └─ Old value: %s' , linePrefix , formatValue ( oldObject , colors . red ) ) ;
179
+ print ( '%s └─ %s' , linePrefix , formatValue ( oldObject , colors . red ) ) ;
152
180
} else /* if (oldObject === undefined && newObject !== undefined) */ {
153
- print ( '%s └─ New value: %s' , linePrefix , formatValue ( newObject , colors . green ) ) ;
181
+ print ( '%s └─ %s' , linePrefix , formatValue ( newObject , colors . green ) ) ;
154
182
}
155
183
return ;
156
184
}
@@ -189,4 +217,56 @@ export function formatDifferences(stream: NodeJS.WriteStream, templateDiff: Temp
189
217
return ADDITION ;
190
218
}
191
219
}
220
+
221
+ function formatLogicalId ( logicalId : string , diff ?: ResourceDifference ) {
222
+ // if we have a path in the map, return it
223
+ const path = logicalToPathMap [ logicalId ] ;
224
+ if ( path ) {
225
+ // first component of path is the stack name, so let's remove that
226
+ return normalizePath ( path ) ;
227
+ }
228
+
229
+ // if we don't have in our map, it might be a deleted resource, so let's try the
230
+ // template metadata
231
+ const oldPathMetadata = diff && diff . oldValue && diff . oldValue . Metadata && diff . oldValue . Metadata [ cxapi . PATH_METADATA_KEY ] ;
232
+ if ( oldPathMetadata ) {
233
+ return normalizePath ( oldPathMetadata ) ;
234
+ }
235
+
236
+ const newPathMetadata = diff && diff . newValue && diff . newValue . Metadata && diff . newValue . Metadata [ cxapi . PATH_METADATA_KEY ] ;
237
+ if ( newPathMetadata ) {
238
+ return normalizePath ( newPathMetadata ) ;
239
+ }
240
+
241
+ // couldn't figure out the path, just return the logical ID
242
+ return logicalId ;
243
+
244
+ /**
245
+ * Path is supposed to start with "/stack-name". If this is the case (i.e. path has more than
246
+ * two components, we remove the first part. Otherwise, we just use the full path.
247
+ * @param p
248
+ */
249
+ function normalizePath ( p : string ) {
250
+ if ( p . startsWith ( '/' ) ) {
251
+ p = p . substr ( 1 ) ;
252
+ }
253
+
254
+ let parts = p . split ( '/' ) ;
255
+ if ( parts . length > 1 ) {
256
+ parts = parts . slice ( 1 ) ;
257
+
258
+ // remove the last component if it's "Resource" or "Default" (if we have more than a single component)
259
+ if ( parts . length > 1 ) {
260
+ const last = parts [ parts . length - 1 ] ;
261
+ if ( last === 'Resource' || last === 'Default' ) {
262
+ parts = parts . slice ( 0 , parts . length - 1 ) ;
263
+ }
264
+ }
265
+
266
+ p = parts . join ( '/' ) ;
267
+ }
268
+
269
+ return `${ p } ${ colors . gray ( logicalId ) } ` ;
270
+ }
271
+ }
192
272
}
0 commit comments