@@ -4,6 +4,7 @@ import type { FeatureFlagReportProperties } from '@aws-cdk/cloud-assembly-schema
44import { ArtifactType } from '@aws-cdk/cloud-assembly-schema' ;
55import type { TemplateDiff } from '@aws-cdk/cloudformation-diff' ;
66import * as cxapi from '@aws-cdk/cx-api' ;
7+ import type { DescribeChangeSetCommandOutput } from '@aws-sdk/client-cloudformation' ;
78import * as chalk from 'chalk' ;
89import * as chokidar from 'chokidar' ;
910import * as fs from 'fs-extra' ;
@@ -19,7 +20,7 @@ import type {
1920 EnvironmentBootstrapResult ,
2021} from '../actions/bootstrap' ;
2122import { BootstrapSource } from '../actions/bootstrap' ;
22- import { AssetBuildTime , type DeployOptions } from '../actions/deploy' ;
23+ import { AssetBuildTime , type DeploymentMethod , type DeployOptions } from '../actions/deploy' ;
2324import {
2425 buildParameterMap ,
2526 type PrivateDeployOptions ,
@@ -606,32 +607,6 @@ export class Toolkit extends CloudAssemblySourceBuilder {
606607 return ;
607608 }
608609
609- const currentTemplate = await deployments . readCurrentTemplate ( stack ) ;
610-
611- const formatter = new DiffFormatter ( {
612- templateInfo : {
613- oldTemplate : currentTemplate ,
614- newTemplate : stack ,
615- } ,
616- } ) ;
617-
618- const securityDiff = formatter . formatSecurityDiff ( ) ;
619-
620- // Send a request response with the formatted security diff as part of the message,
621- // and the template diff as data
622- // (IoHost decides whether to print depending on permissionChangeType)
623- const deployMotivation = '"--require-approval" is enabled and stack includes security-sensitive updates.' ;
624- const deployQuestion = `${ securityDiff . formattedDiff } \n\n${ deployMotivation } \nDo you wish to deploy these changes` ;
625- const deployConfirmed = await ioHelper . requestResponse ( IO . CDK_TOOLKIT_I5060 . req ( deployQuestion , {
626- motivation : deployMotivation ,
627- concurrency,
628- permissionChangeType : securityDiff . permissionChangeType ,
629- templateDiffs : formatter . diffs ,
630- } ) ) ;
631- if ( ! deployConfirmed ) {
632- throw new ToolkitError ( 'Aborted by user' ) ;
633- }
634-
635610 // Following are the same semantics we apply with respect to Notification ARNs (dictated by the SDK)
636611 //
637612 // - undefined => cdk ignores it, as if it wasn't supported (allows external management).
@@ -647,6 +622,63 @@ export class Toolkit extends CloudAssemblySourceBuilder {
647622 }
648623 }
649624
625+ const tags = ( options . tags && options . tags . length > 0 ) ? options . tags : tagsForStack ( stack ) ;
626+
627+ let deploymentMethod : DeploymentMethod | undefined ;
628+ let changeSet : DescribeChangeSetCommandOutput | undefined ;
629+ if ( options . deploymentMethod ?. method === 'change-set' ) {
630+ // Create a CloudFormation change set
631+ const changeSetName = options . deploymentMethod ?. changeSetName || `cdk-deploy-change-set-${ Date . now ( ) } ` ;
632+ await deployments . deployStack ( {
633+ stack,
634+ deployName : stack . stackName ,
635+ roleArn : options . roleArn ,
636+ toolkitStackName : this . toolkitStackName ,
637+ reuseAssets : options . reuseAssets ,
638+ notificationArns,
639+ tags,
640+ deploymentMethod : { method : 'change-set' as const , changeSetName, execute : false } ,
641+ forceDeployment : options . forceDeployment ,
642+ parameters : Object . assign ( { } , parameterMap [ '*' ] , parameterMap [ stack . stackName ] ) ,
643+ usePreviousParameters : options . parameters ?. keepExistingParameters ,
644+ extraUserAgent : options . extraUserAgent ,
645+ assetParallelism : options . assetParallelism ,
646+ } ) ;
647+
648+ // Describe the change set to be presented to the user
649+ changeSet = await deployments . describeChangeSet ( stack , changeSetName ) ;
650+
651+ // Don't continue deploying the stack if there are no changes (unless forced)
652+ if ( ! options . forceDeployment && changeSet . ChangeSetName && ( changeSet . Changes === undefined || changeSet . Changes . length === 0 ) ) {
653+ await deployments . deleteChangeSet ( stack , changeSet . ChangeSetName ) ;
654+ return ioHelper . notify ( IO . CDK_TOOLKIT_W5023 . msg ( `${ chalk . bold ( stack . displayName ) } : stack has no changes, skipping deployment.` ) ) ;
655+ }
656+
657+ // Adjust the deployment method for the subsequent deployment to execute the existing change set
658+ deploymentMethod = { ...options . deploymentMethod , changeSetName, executeExistingChangeSet : true } ;
659+ }
660+ // Present the diff to the user
661+ const oldTemplate = await deployments . readCurrentTemplate ( stack ) ;
662+ const formatter = new DiffFormatter ( { templateInfo : { oldTemplate, newTemplate : stack , changeSet } } ) ;
663+ const diff = formatter . formatStackDiff ( ) ;
664+
665+ // Send a request response with the formatted diff as part of the message, and the template diff as data
666+ // (IoHost decides whether to print depending on permissionChangeType)
667+ const deployMotivation = 'Approval required for stack deployment.' ;
668+ const deployQuestion = `${ diff . formattedDiff } \n\n${ deployMotivation } \nDo you wish to deploy these changes` ;
669+ const deployConfirmed = await ioHelper . requestResponse ( IO . CDK_TOOLKIT_I5060 . req ( deployQuestion , {
670+ motivation : deployMotivation ,
671+ concurrency,
672+ permissionChangeType : diff . permissionChangeType ,
673+ templateDiffs : formatter . diffs ,
674+ } ) ) ;
675+ if ( ! deployConfirmed ) {
676+ if ( changeSet ?. ChangeSetName ) {
677+ await deployments . deleteChangeSet ( stack , changeSet . ChangeSetName ) ;
678+ }
679+ throw new ToolkitError ( 'Aborted by user' ) ;
680+ }
681+
650682 const stackIndex = stacks . indexOf ( stack ) + 1 ;
651683 const deploySpan = await ioHelper . span ( SPAN . DEPLOY_STACK )
652684 . begin ( `${ chalk . bold ( stack . displayName ) } : deploying... [${ stackIndex } /${ stackCollection . stackCount } ]` , {
@@ -655,11 +687,6 @@ export class Toolkit extends CloudAssemblySourceBuilder {
655687 stack,
656688 } ) ;
657689
658- let tags = options . tags ;
659- if ( ! tags || tags . length === 0 ) {
660- tags = tagsForStack ( stack ) ;
661- }
662-
663690 let deployDuration ;
664691 try {
665692 let deployResult : SuccessfulDeployStackResult | undefined ;
@@ -679,7 +706,7 @@ export class Toolkit extends CloudAssemblySourceBuilder {
679706 reuseAssets : options . reuseAssets ,
680707 notificationArns,
681708 tags,
682- deploymentMethod : options . deploymentMethod ,
709+ deploymentMethod : deploymentMethod ?? options . deploymentMethod ,
683710 forceDeployment : options . forceDeployment ,
684711 parameters : Object . assign ( { } , parameterMap [ '*' ] , parameterMap [ stack . stackName ] ) ,
685712 usePreviousParameters : options . parameters ?. keepExistingParameters ,
@@ -1392,4 +1419,3 @@ export class Toolkit extends CloudAssemblySourceBuilder {
13921419 }
13931420 }
13941421}
1395-
0 commit comments