@@ -4,6 +4,7 @@ import * as cxapi from '@aws-cdk/cloud-assembly-api';
44import type { FeatureFlagReportProperties } from '@aws-cdk/cloud-assembly-schema' ;
55import { ArtifactType } from '@aws-cdk/cloud-assembly-schema' ;
66import type { TemplateDiff } from '@aws-cdk/cloudformation-diff' ;
7+ import type { DescribeChangeSetCommandOutput } from '@aws-sdk/client-cloudformation' ;
78import * as chalk from 'chalk' ;
89import * as chokidar from 'chokidar' ;
910import { type EventName , EVENTS } from 'chokidar/handler.js' ;
@@ -35,7 +36,7 @@ import type {
3536 EnvironmentBootstrapResult ,
3637} from '../actions/bootstrap' ;
3738import { BootstrapSource } from '../actions/bootstrap' ;
38- import { AssetBuildTime , type DeployOptions } from '../actions/deploy' ;
39+ import { AssetBuildTime , type DeploymentMethod , type DeployOptions } from '../actions/deploy' ;
3940import {
4041 buildParameterMap ,
4142 type PrivateDeployOptions ,
@@ -622,32 +623,6 @@ export class Toolkit extends CloudAssemblySourceBuilder {
622623 return ;
623624 }
624625
625- const currentTemplate = await deployments . readCurrentTemplate ( stack ) ;
626-
627- const formatter = new DiffFormatter ( {
628- templateInfo : {
629- oldTemplate : currentTemplate ,
630- newTemplate : stack ,
631- } ,
632- } ) ;
633-
634- const securityDiff = formatter . formatSecurityDiff ( ) ;
635-
636- // Send a request response with the formatted security diff as part of the message,
637- // and the template diff as data
638- // (IoHost decides whether to print depending on permissionChangeType)
639- const deployMotivation = '"--require-approval" is enabled and stack includes security-sensitive updates.' ;
640- const deployQuestion = `${ securityDiff . formattedDiff } \n\n${ deployMotivation } \nDo you wish to deploy these changes` ;
641- const deployConfirmed = await ioHelper . requestResponse ( IO . CDK_TOOLKIT_I5060 . req ( deployQuestion , {
642- motivation : deployMotivation ,
643- concurrency,
644- permissionChangeType : securityDiff . permissionChangeType ,
645- templateDiffs : formatter . diffs ,
646- } ) ) ;
647- if ( ! deployConfirmed ) {
648- throw new ToolkitError ( 'Aborted by user' ) ;
649- }
650-
651626 // Following are the same semantics we apply with respect to Notification ARNs (dictated by the SDK)
652627 //
653628 // - undefined => cdk ignores it, as if it wasn't supported (allows external management).
@@ -663,6 +638,63 @@ export class Toolkit extends CloudAssemblySourceBuilder {
663638 }
664639 }
665640
641+ const tags = ( options . tags && options . tags . length > 0 ) ? options . tags : tagsForStack ( stack ) ;
642+
643+ let deploymentMethod : DeploymentMethod | undefined ;
644+ let changeSet : DescribeChangeSetCommandOutput | undefined ;
645+ if ( options . deploymentMethod ?. method === 'change-set' ) {
646+ // Create a CloudFormation change set
647+ const changeSetName = options . deploymentMethod ?. changeSetName || `cdk-deploy-change-set-${ Date . now ( ) } ` ;
648+ await deployments . deployStack ( {
649+ stack,
650+ deployName : stack . stackName ,
651+ roleArn : options . roleArn ,
652+ toolkitStackName : this . toolkitStackName ,
653+ reuseAssets : options . reuseAssets ,
654+ notificationArns,
655+ tags,
656+ deploymentMethod : { method : 'change-set' as const , changeSetName, execute : false } ,
657+ forceDeployment : options . forceDeployment ,
658+ parameters : Object . assign ( { } , parameterMap [ '*' ] , parameterMap [ stack . stackName ] ) ,
659+ usePreviousParameters : options . parameters ?. keepExistingParameters ,
660+ extraUserAgent : options . extraUserAgent ,
661+ assetParallelism : options . assetParallelism ,
662+ } ) ;
663+
664+ // Describe the change set to be presented to the user
665+ changeSet = await deployments . describeChangeSet ( stack , changeSetName ) ;
666+
667+ // Don't continue deploying the stack if there are no changes (unless forced)
668+ if ( ! options . forceDeployment && changeSet . ChangeSetName && ( changeSet . Changes === undefined || changeSet . Changes . length === 0 ) ) {
669+ await deployments . deleteChangeSet ( stack , changeSet . ChangeSetName ) ;
670+ return ioHelper . notify ( IO . CDK_TOOLKIT_W5023 . msg ( `${ chalk . bold ( stack . displayName ) } : stack has no changes, skipping deployment.` ) ) ;
671+ }
672+
673+ // Adjust the deployment method for the subsequent deployment to execute the existing change set
674+ deploymentMethod = { ...options . deploymentMethod , changeSetName, executeExistingChangeSet : true } ;
675+ }
676+ // Present the diff to the user
677+ const oldTemplate = await deployments . readCurrentTemplate ( stack ) ;
678+ const formatter = new DiffFormatter ( { templateInfo : { oldTemplate, newTemplate : stack , changeSet } } ) ;
679+ const diff = formatter . formatStackDiff ( ) ;
680+
681+ // Send a request response with the formatted diff as part of the message, and the template diff as data
682+ // (IoHost decides whether to print depending on permissionChangeType)
683+ const deployMotivation = 'Approval required for stack deployment.' ;
684+ const deployQuestion = `${ diff . formattedDiff } \n\n${ deployMotivation } \nDo you wish to deploy these changes` ;
685+ const deployConfirmed = await ioHelper . requestResponse ( IO . CDK_TOOLKIT_I5060 . req ( deployQuestion , {
686+ motivation : deployMotivation ,
687+ concurrency,
688+ permissionChangeType : diff . permissionChangeType ,
689+ templateDiffs : formatter . diffs ,
690+ } ) ) ;
691+ if ( ! deployConfirmed ) {
692+ if ( changeSet ?. ChangeSetName ) {
693+ await deployments . deleteChangeSet ( stack , changeSet . ChangeSetName ) ;
694+ }
695+ throw new ToolkitError ( 'Aborted by user' ) ;
696+ }
697+
666698 const stackIndex = stacks . indexOf ( stack ) + 1 ;
667699 const deploySpan = await ioHelper . span ( SPAN . DEPLOY_STACK )
668700 . begin ( `${ chalk . bold ( stack . displayName ) } : deploying... [${ stackIndex } /${ stackCollection . stackCount } ]` , {
@@ -671,11 +703,6 @@ export class Toolkit extends CloudAssemblySourceBuilder {
671703 stack,
672704 } ) ;
673705
674- let tags = options . tags ;
675- if ( ! tags || tags . length === 0 ) {
676- tags = tagsForStack ( stack ) ;
677- }
678-
679706 let deployDuration ;
680707 try {
681708 let deployResult : SuccessfulDeployStackResult | undefined ;
@@ -695,7 +722,7 @@ export class Toolkit extends CloudAssemblySourceBuilder {
695722 reuseAssets : options . reuseAssets ,
696723 notificationArns,
697724 tags,
698- deploymentMethod : options . deploymentMethod ,
725+ deploymentMethod : deploymentMethod ?? options . deploymentMethod ,
699726 forceDeployment : options . forceDeployment ,
700727 parameters : Object . assign ( { } , parameterMap [ '*' ] , parameterMap [ stack . stackName ] ) ,
701728 usePreviousParameters : options . parameters ?. keepExistingParameters ,
@@ -1409,4 +1436,3 @@ export class Toolkit extends CloudAssemblySourceBuilder {
14091436 }
14101437 }
14111438}
1412-
0 commit comments