@@ -3,7 +3,7 @@ import * as cfn_diff from '@aws-cdk/cloudformation-diff';
3
3
import type * as cxapi from '@aws-cdk/cx-api' ;
4
4
import type { WaiterResult } from '@smithy/util-waiter' ;
5
5
import * as chalk from 'chalk' ;
6
- import type { AffectedResource , ResourceChange } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads' ;
6
+ import type { AffectedResource , HotswapResult , ResourceSubject , ResourceChange , NonHotswappableChange } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads' ;
7
7
import { NonHotswappableReason } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads' ;
8
8
import type { IMessageSpan , IoHelper } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private' ;
9
9
import { IO , SPAN } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private' ;
@@ -21,7 +21,6 @@ import type {
21
21
HotswapOperation ,
22
22
RejectedChange ,
23
23
HotswapPropertyOverrides ,
24
- HotswapResult ,
25
24
} from '../hotswap/common' ;
26
25
import {
27
26
ICON ,
@@ -156,22 +155,24 @@ async function hotswapDeployment(
156
155
} ) ;
157
156
158
157
const stackChanges = cfn_diff . fullDiff ( currentTemplate . deployedRootTemplate , stack . template ) ;
159
- const { hotswappable : hotswapOperations , nonHotswappable : nonHotswappableChanges } = await classifyResourceChanges (
158
+ const { hotswappable, nonHotswappable } = await classifyResourceChanges (
160
159
stackChanges ,
161
160
evaluateCfnTemplate ,
162
161
sdk ,
163
162
currentTemplate . nestedStacks , hotswapPropertyOverrides ,
164
163
) ;
165
164
166
- await logNonHotswappableChanges ( ioSpan , nonHotswappableChanges , hotswapMode ) ;
165
+ await logNonHotswappableChanges ( ioSpan , nonHotswappable , hotswapMode ) ;
167
166
168
- const hotswappableChanges = hotswapOperations . map ( o => o . change ) ;
167
+ const hotswappableChanges = hotswappable . map ( o => o . change ) ;
168
+ const nonHotswappableChanges = nonHotswappable . map ( n => n . change ) ;
169
169
170
170
// preserve classic hotswap behavior
171
171
if ( hotswapMode === 'fall-back' ) {
172
172
if ( nonHotswappableChanges . length > 0 ) {
173
173
return {
174
174
stack,
175
+ mode : hotswapMode ,
175
176
hotswapped : false ,
176
177
hotswappableChanges,
177
178
nonHotswappableChanges,
@@ -180,10 +181,11 @@ async function hotswapDeployment(
180
181
}
181
182
182
183
// apply the short-circuitable changes
183
- await applyAllHotswappableChanges ( sdk , ioSpan , hotswapOperations ) ;
184
+ await applyAllHotswappableChanges ( sdk , ioSpan , hotswappable ) ;
184
185
185
186
return {
186
187
stack,
188
+ mode : hotswapMode ,
187
189
hotswapped : true ,
188
190
hotswappableChanges,
189
191
nonHotswappableChanges,
@@ -214,10 +216,15 @@ async function classifyResourceChanges(
214
216
for ( const logicalId of Object . keys ( stackChanges . outputs . changes ) ) {
215
217
nonHotswappableResources . push ( {
216
218
hotswappable : false ,
217
- reason : NonHotswappableReason . OUTPUT ,
218
- description : 'output was changed' ,
219
- logicalId,
220
- resourceType : 'Stack Output' ,
219
+ change : {
220
+ reason : NonHotswappableReason . OUTPUT ,
221
+ description : 'output was changed' ,
222
+ subject : {
223
+ type : 'Output' ,
224
+ logicalId,
225
+ metadata : evaluateCfnTemplate . metadataFor ( logicalId ) ,
226
+ } ,
227
+ } ,
221
228
} ) ;
222
229
}
223
230
// gather the results of the detector functions
@@ -237,7 +244,7 @@ async function classifyResourceChanges(
237
244
continue ;
238
245
}
239
246
240
- const hotswappableChangeCandidate = isCandidateForHotswapping ( change , logicalId ) ;
247
+ const hotswappableChangeCandidate = isCandidateForHotswapping ( logicalId , change , evaluateCfnTemplate ) ;
241
248
// we don't need to run this through the detector functions, we can already judge this
242
249
if ( 'hotswappable' in hotswappableChangeCandidate ) {
243
250
if ( ! hotswappableChangeCandidate . hotswappable ) {
@@ -348,10 +355,16 @@ async function findNestedHotswappableChanges(
348
355
nonHotswappable : [
349
356
{
350
357
hotswappable : false ,
351
- logicalId,
352
- reason : NonHotswappableReason . NESTED_STACK_CREATION ,
353
- description : `physical name for AWS::CloudFormation::Stack '${ logicalId } ' could not be found in CloudFormation, so this is a newly created nested stack and cannot be hotswapped` ,
354
- resourceType : 'AWS::CloudFormation::Stack' ,
358
+ change : {
359
+ reason : NonHotswappableReason . NESTED_STACK_CREATION ,
360
+ description : 'newly created nested stacks cannot be hotswapped' ,
361
+ subject : {
362
+ type : 'Resource' ,
363
+ logicalId,
364
+ resourceType : 'AWS::CloudFormation::Stack' ,
365
+ metadata : evaluateCfnTemplate . metadataFor ( logicalId ) ,
366
+ } ,
367
+ } ,
355
368
} ,
356
369
] ,
357
370
} ;
@@ -414,36 +427,56 @@ function makeRenameDifference(
414
427
* Returns a `NonHotswappableChange` if the change is not hotswappable
415
428
*/
416
429
function isCandidateForHotswapping (
417
- change : cfn_diff . ResourceDifference ,
418
430
logicalId : string ,
431
+ change : cfn_diff . ResourceDifference ,
432
+ evaluateCfnTemplate : EvaluateCloudFormationTemplate ,
419
433
) : RejectedChange | ResourceChange {
420
434
// a resource has been removed OR a resource has been added; we can't short-circuit that change
421
435
if ( ! change . oldValue ) {
422
436
return {
423
437
hotswappable : false ,
424
- resourceType : change . newValue ! . Type ,
425
- logicalId,
426
- reason : NonHotswappableReason . RESOURCE_CREATION ,
427
- description : `resource '${ logicalId } ' was created by this deployment` ,
438
+ change : {
439
+ reason : NonHotswappableReason . RESOURCE_CREATION ,
440
+ description : `resource '${ logicalId } ' was created by this deployment` ,
441
+ subject : {
442
+ type : 'Resource' ,
443
+ logicalId,
444
+ resourceType : change . newValue ! . Type ,
445
+ metadata : evaluateCfnTemplate . metadataFor ( logicalId ) ,
446
+ } ,
447
+ } ,
428
448
} ;
429
449
} else if ( ! change . newValue ) {
430
450
return {
431
451
hotswappable : false ,
432
- resourceType : change . oldValue ! . Type ,
433
452
logicalId,
434
- reason : NonHotswappableReason . RESOURCE_DELETION ,
435
- description : `resource '${ logicalId } ' was destroyed by this deployment` ,
453
+ change : {
454
+ reason : NonHotswappableReason . RESOURCE_DELETION ,
455
+ description : `resource '${ logicalId } ' was destroyed by this deployment` ,
456
+ subject : {
457
+ type : 'Resource' ,
458
+ logicalId,
459
+ resourceType : change . oldValue . Type ,
460
+ metadata : evaluateCfnTemplate . metadataFor ( logicalId ) ,
461
+ } ,
462
+ } ,
436
463
} ;
437
464
}
438
465
439
466
// a resource has had its type changed
440
- if ( change . newValue ? .Type !== change . oldValue ? .Type ) {
467
+ if ( change . newValue . Type !== change . oldValue . Type ) {
441
468
return {
442
469
hotswappable : false ,
443
- resourceType : change . newValue ?. Type ,
444
- logicalId,
445
- reason : NonHotswappableReason . RESOURCE_TYPE_CHANGED ,
446
- description : `resource '${ logicalId } ' had its type changed from '${ change . oldValue ?. Type } ' to '${ change . newValue ?. Type } '` ,
470
+ change : {
471
+ reason : NonHotswappableReason . RESOURCE_TYPE_CHANGED ,
472
+ description : `resource '${ logicalId } ' had its type changed from '${ change . oldValue ?. Type } ' to '${ change . newValue ?. Type } '` ,
473
+ subject : {
474
+ type : 'Resource' ,
475
+ logicalId,
476
+ resourceType : change . newValue . Type ,
477
+ metadata : evaluateCfnTemplate . metadataFor ( logicalId ) ,
478
+ } ,
479
+ } ,
447
480
} ;
448
481
}
449
482
@@ -452,6 +485,7 @@ function isCandidateForHotswapping(
452
485
oldValue : change . oldValue ,
453
486
newValue : change . newValue ,
454
487
propertyUpdates : change . propertyUpdates ,
488
+ metadata : evaluateCfnTemplate . metadataFor ( logicalId ) ,
455
489
} ;
456
490
}
457
491
@@ -547,25 +581,51 @@ async function logNonHotswappableChanges(
547
581
messages . push ( format ( '%s %s' , chalk . red ( '⚠️' ) , chalk . red ( 'The following non-hotswappable changes were found:' ) ) ) ;
548
582
}
549
583
550
- for ( const change of nonHotswappableChanges ) {
551
- if ( change . rejectedProperties ?. length ) {
552
- messages . push ( format (
553
- ' logicalID: %s, type: %s, rejected changes: %s, reason: %s' ,
554
- chalk . bold ( change . logicalId ) ,
555
- chalk . bold ( change . resourceType ) ,
556
- chalk . bold ( change . rejectedProperties ) ,
557
- chalk . red ( change . description ) ,
558
- ) ) ;
559
- } else {
560
- messages . push ( format (
561
- ' logicalID: %s, type: %s, reason: %s' ,
562
- chalk . bold ( change . logicalId ) ,
563
- chalk . bold ( change . resourceType ) ,
564
- chalk . red ( change . description ) ,
565
- ) ) ;
566
- }
584
+ for ( const rejection of nonHotswappableChanges ) {
585
+ messages . push ( ' ' + nonHotswappableChangeMessage ( rejection . change ) ) ;
567
586
}
568
587
messages . push ( '' ) ; // newline
569
588
570
589
await ioSpan . notify ( IO . DEFAULT_TOOLKIT_INFO . msg ( messages . join ( '\n' ) ) ) ;
571
590
}
591
+
592
+ /**
593
+ * Formats a NonHotswappableChange
594
+ */
595
+ function nonHotswappableChangeMessage ( change : NonHotswappableChange ) : string {
596
+ const subject = change . subject ;
597
+ const reason = change . description ?? change . reason ;
598
+
599
+ switch ( subject . type ) {
600
+ case 'Output' :
601
+ return format (
602
+ 'output: %s, reason: %s' ,
603
+ chalk . bold ( subject . logicalId ) ,
604
+ chalk . red ( reason ) ,
605
+ ) ;
606
+ case 'Resource' :
607
+ return nonHotswappableResourceMessage ( subject , reason ) ;
608
+ }
609
+ }
610
+
611
+ /**
612
+ * Formats a non-hotswappable resource subject
613
+ */
614
+ function nonHotswappableResourceMessage ( subject : ResourceSubject , reason : string ) : string {
615
+ if ( subject . rejectedProperties ?. length ) {
616
+ return format (
617
+ 'resource: %s, type: %s, rejected changes: %s, reason: %s' ,
618
+ chalk . bold ( subject . logicalId ) ,
619
+ chalk . bold ( subject . resourceType ) ,
620
+ chalk . bold ( subject . rejectedProperties ) ,
621
+ chalk . red ( reason ) ,
622
+ ) ;
623
+ }
624
+
625
+ return format (
626
+ 'resource: %s, type: %s, reason: %s' ,
627
+ chalk . bold ( subject . logicalId ) ,
628
+ chalk . bold ( subject . resourceType ) ,
629
+ chalk . red ( reason ) ,
630
+ ) ;
631
+ }
0 commit comments