@@ -3,7 +3,7 @@ import * as cfn_diff from '@aws-cdk/cloudformation-diff';
33import type * as cxapi from '@aws-cdk/cx-api' ;
44import type { WaiterResult } from '@smithy/util-waiter' ;
55import * 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' ;
77import { NonHotswappableReason } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads' ;
88import type { IMessageSpan , IoHelper } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private' ;
99import { IO , SPAN } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private' ;
@@ -21,7 +21,6 @@ import type {
2121 HotswapOperation ,
2222 RejectedChange ,
2323 HotswapPropertyOverrides ,
24- HotswapResult ,
2524} from '../hotswap/common' ;
2625import {
2726 ICON ,
@@ -156,22 +155,24 @@ async function hotswapDeployment(
156155 } ) ;
157156
158157 const stackChanges = cfn_diff . fullDiff ( currentTemplate . deployedRootTemplate , stack . template ) ;
159- const { hotswappable : hotswapOperations , nonHotswappable : nonHotswappableChanges } = await classifyResourceChanges (
158+ const { hotswappable, nonHotswappable } = await classifyResourceChanges (
160159 stackChanges ,
161160 evaluateCfnTemplate ,
162161 sdk ,
163162 currentTemplate . nestedStacks , hotswapPropertyOverrides ,
164163 ) ;
165164
166- await logNonHotswappableChanges ( ioSpan , nonHotswappableChanges , hotswapMode ) ;
165+ await logNonHotswappableChanges ( ioSpan , nonHotswappable , hotswapMode ) ;
167166
168- const hotswappableChanges = hotswapOperations . map ( o => o . change ) ;
167+ const hotswappableChanges = hotswappable . map ( o => o . change ) ;
168+ const nonHotswappableChanges = nonHotswappable . map ( n => n . change ) ;
169169
170170 // preserve classic hotswap behavior
171171 if ( hotswapMode === 'fall-back' ) {
172172 if ( nonHotswappableChanges . length > 0 ) {
173173 return {
174174 stack,
175+ mode : hotswapMode ,
175176 hotswapped : false ,
176177 hotswappableChanges,
177178 nonHotswappableChanges,
@@ -180,10 +181,11 @@ async function hotswapDeployment(
180181 }
181182
182183 // apply the short-circuitable changes
183- await applyAllHotswappableChanges ( sdk , ioSpan , hotswapOperations ) ;
184+ await applyAllHotswappableChanges ( sdk , ioSpan , hotswappable ) ;
184185
185186 return {
186187 stack,
188+ mode : hotswapMode ,
187189 hotswapped : true ,
188190 hotswappableChanges,
189191 nonHotswappableChanges,
@@ -214,10 +216,14 @@ async function classifyResourceChanges(
214216 for ( const logicalId of Object . keys ( stackChanges . outputs . changes ) ) {
215217 nonHotswappableResources . push ( {
216218 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+ } ,
226+ } ,
221227 } ) ;
222228 }
223229 // gather the results of the detector functions
@@ -348,10 +354,15 @@ async function findNestedHotswappableChanges(
348354 nonHotswappable : [
349355 {
350356 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' ,
357+ change : {
358+ reason : NonHotswappableReason . NESTED_STACK_CREATION ,
359+ description : 'newly created nested stacks cannot be hotswapped' ,
360+ subject : {
361+ type : 'Resource' ,
362+ resourceType : 'AWS::CloudFormation::Stack' ,
363+ logicalId,
364+ } ,
365+ } ,
355366 } ,
356367 ] ,
357368 } ;
@@ -421,29 +432,45 @@ function isCandidateForHotswapping(
421432 if ( ! change . oldValue ) {
422433 return {
423434 hotswappable : false ,
424- resourceType : change . newValue ! . Type ,
425- logicalId,
426- reason : NonHotswappableReason . RESOURCE_CREATION ,
427- description : `resource '${ logicalId } ' was created by this deployment` ,
435+ change : {
436+ reason : NonHotswappableReason . RESOURCE_CREATION ,
437+ description : `resource '${ logicalId } ' was created by this deployment` ,
438+ subject : {
439+ type : 'Resource' ,
440+ resourceType : change . newValue ! . Type ,
441+ logicalId,
442+ } ,
443+ } ,
428444 } ;
429445 } else if ( ! change . newValue ) {
430446 return {
431447 hotswappable : false ,
432- resourceType : change . oldValue ! . Type ,
433448 logicalId,
434- reason : NonHotswappableReason . RESOURCE_DELETION ,
435- description : `resource '${ logicalId } ' was destroyed by this deployment` ,
449+ change : {
450+ reason : NonHotswappableReason . RESOURCE_DELETION ,
451+ description : `resource '${ logicalId } ' was destroyed by this deployment` ,
452+ subject : {
453+ type : 'Resource' ,
454+ resourceType : change . oldValue ?. Type ,
455+ logicalId,
456+ } ,
457+ } ,
436458 } ;
437459 }
438460
439461 // a resource has had its type changed
440462 if ( change . newValue ?. Type !== change . oldValue ?. Type ) {
441463 return {
442464 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 } '` ,
465+ change : {
466+ reason : NonHotswappableReason . RESOURCE_TYPE_CHANGED ,
467+ description : `resource '${ logicalId } ' had its type changed from '${ change . oldValue ?. Type } ' to '${ change . newValue ?. Type } '` ,
468+ subject : {
469+ type : 'Resource' ,
470+ resourceType : change . newValue ?. Type ,
471+ logicalId,
472+ } ,
473+ } ,
447474 } ;
448475 }
449476
@@ -547,25 +574,51 @@ async function logNonHotswappableChanges(
547574 messages . push ( format ( '%s %s' , chalk . red ( '⚠️' ) , chalk . red ( 'The following non-hotswappable changes were found:' ) ) ) ;
548575 }
549576
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- }
577+ for ( const rejection of nonHotswappableChanges ) {
578+ messages . push ( ' ' + nonHotswappableChangeMessage ( rejection . change ) ) ;
567579 }
568580 messages . push ( '' ) ; // newline
569581
570582 await ioSpan . notify ( IO . DEFAULT_TOOLKIT_INFO . msg ( messages . join ( '\n' ) ) ) ;
571583}
584+
585+ /**
586+ * Formats a NonHotswappableChange
587+ */
588+ function nonHotswappableChangeMessage ( change : NonHotswappableChange ) : string {
589+ const subject = change . subject ;
590+ const reason = change . description ?? change . reason ;
591+
592+ switch ( subject . type ) {
593+ case 'Output' :
594+ return format (
595+ 'output: %s, reason: %s' ,
596+ chalk . bold ( subject . logicalId ) ,
597+ chalk . red ( reason ) ,
598+ ) ;
599+ case 'Resource' :
600+ return nonHotswappableResourceMessage ( subject , reason ) ;
601+ }
602+ }
603+
604+ /**
605+ * Formats a non-hotswappable resource subject
606+ */
607+ function nonHotswappableResourceMessage ( subject : ResourceSubject , reason : string ) : string {
608+ if ( subject . rejectedProperties ?. length ) {
609+ return format (
610+ 'resource: %s, type: %s, rejected changes: %s, reason: %s' ,
611+ chalk . bold ( subject . logicalId ) ,
612+ chalk . bold ( subject . resourceType ) ,
613+ chalk . bold ( subject . rejectedProperties ) ,
614+ chalk . red ( reason ) ,
615+ ) ;
616+ }
617+
618+ return format (
619+ 'resource: %s, type: %s, reason: %s' ,
620+ chalk . bold ( subject . logicalId ) ,
621+ chalk . bold ( subject . resourceType ) ,
622+ chalk . red ( reason ) ,
623+ ) ;
624+ }
0 commit comments