diff --git a/packages/@aws-cdk/tmp-toolkit-helpers/src/api/diff/diff-formatter.ts b/packages/@aws-cdk/tmp-toolkit-helpers/src/api/diff/diff-formatter.ts index b05b99427..4a21bd435 100644 --- a/packages/@aws-cdk/tmp-toolkit-helpers/src/api/diff/diff-formatter.ts +++ b/packages/@aws-cdk/tmp-toolkit-helpers/src/api/diff/diff-formatter.ts @@ -2,7 +2,6 @@ import { format } from 'node:util'; import { Writable } from 'stream'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { - type DescribeChangeSetOutput, type TemplateDiff, fullDiff, formatSecurityChanges, @@ -75,14 +74,10 @@ interface DiffFormatterProps { readonly ioHelper: IoHelper; /** - * The old/current state of the stack. + * The relevant information for the Template that is being diffed. + * Includes the old/current state of the stack as well as the new state. */ - readonly oldTemplate: any; - - /** - * The new/target state of the stack. - */ - readonly newTemplate: cxapi.CloudFormationStackArtifact; + readonly templateInfo: TemplateInfo; } /** @@ -93,18 +88,6 @@ interface FormatSecurityDiffOptions { * The approval level of the security diff */ readonly requireApproval: RequireApproval; - - /** - * The name of the Stack. - */ - readonly stackName?: string; - - /** - * The changeSet for the Stack. - * - * @default undefined - */ - readonly changeSet?: DescribeChangeSetOutput; } /** @@ -131,30 +114,57 @@ interface FormatStackDiffOptions { * @default false */ readonly quiet?: boolean; +} + +interface ReusableStackDiffOptions extends FormatStackDiffOptions { + readonly ioDefaultHelper: IoDefaultMessages; +} +/** + * Information on a template's old/new state + * that is used for diff. + */ +export interface TemplateInfo { /** - * The name of the stack + * The old/existing template */ - readonly stackName?: string; + readonly oldTemplate: any; + + /** + * The new template + */ + readonly newTemplate: cxapi.CloudFormationStackArtifact; + + /** + * A CloudFormation ChangeSet to help the diff operation. + * Probably created via `createDiffChangeSet`. + * + * @default undefined + */ + readonly changeSet?: any; /** + * The name of the stack + * * @default undefined */ - readonly changeSet?: DescribeChangeSetOutput; + readonly stackName?: string; /** + * Whether or not there are any imported resources + * * @default false */ readonly isImport?: boolean; /** - * @default undefined + * Any nested stacks included in the template + * + * @default {} */ - readonly nestedStackTemplates?: { [nestedStackLogicalId: string]: NestedStackTemplates }; -} - -interface ReusableStackDiffOptions extends Omit { - readonly ioDefaultHelper: IoDefaultMessages; + readonly nestedStacks?: { + [nestedStackLogicalId: string]: NestedStackTemplates; + }; } /** @@ -164,22 +174,30 @@ export class DiffFormatter { private readonly ioHelper: IoHelper; private readonly oldTemplate: any; private readonly newTemplate: cxapi.CloudFormationStackArtifact; + private readonly stackName?: string; + private readonly changeSet?: any; + private readonly nestedStacks: { [nestedStackLogicalId: string]: NestedStackTemplates } | undefined; + private readonly isImport: boolean; constructor(props: DiffFormatterProps) { this.ioHelper = props.ioHelper; - this.oldTemplate = props.oldTemplate; - this.newTemplate = props.newTemplate; + this.oldTemplate = props.templateInfo.oldTemplate; + this.newTemplate = props.templateInfo.newTemplate; + this.stackName = props.templateInfo.stackName; + this.changeSet = props.templateInfo.changeSet; + this.nestedStacks = props.templateInfo.nestedStacks; + this.isImport = props.templateInfo.isImport ?? false; } /** * Format the stack diff */ - public formatStackDiff(options: FormatStackDiffOptions): FormatStackDiffOutput { + public formatStackDiff(options: FormatStackDiffOptions = {}): FormatStackDiffOutput { const ioDefaultHelper = new IoDefaultMessages(this.ioHelper); return this.formatStackDiffHelper( this.oldTemplate, - options.stackName, - options.nestedStackTemplates, + this.stackName, + this.nestedStacks, { ...options, ioDefaultHelper, @@ -193,7 +211,7 @@ export class DiffFormatter { nestedStackTemplates: { [nestedStackLogicalId: string]: NestedStackTemplates } | undefined, options: ReusableStackDiffOptions, ) { - let diff = fullDiff(oldTemplate, this.newTemplate.template, options.changeSet, options.isImport); + let diff = fullDiff(oldTemplate, this.newTemplate.template, this.changeSet, this.isImport); // The stack diff is formatted via `Formatter`, which takes in a stream // and sends its output directly to that stream. To faciliate use of the @@ -211,14 +229,14 @@ export class DiffFormatter { stream.write(format(`Stack ${chalk.bold(stackName)}\n`)); } - if (!options.quiet && options.isImport) { + if (!options.quiet && this.isImport) { stream.write('Parameters and rules created during migration do not affect resource configuration.\n'); } // detect and filter out mangled characters from the diff if (diff.differenceCount && !options.strict) { const mangledNewTemplate = JSON.parse(mangleLikeCloudFormation(JSON.stringify(this.newTemplate.template))); - const mangledDiff = fullDiff(this.oldTemplate, mangledNewTemplate, options.changeSet); + const mangledDiff = fullDiff(this.oldTemplate, mangledNewTemplate, this.changeSet); filteredChangesCount = Math.max(0, diff.differenceCount - mangledDiff.differenceCount); if (filteredChangesCount > 0) { diff = mangledDiff; @@ -281,7 +299,7 @@ export class DiffFormatter { public formatSecurityDiff(options: FormatSecurityDiffOptions): FormatSecurityDiffOutput { const ioDefaultHelper = new IoDefaultMessages(this.ioHelper); - const diff = fullDiff(this.oldTemplate, this.newTemplate.template, options.changeSet); + const diff = fullDiff(this.oldTemplate, this.newTemplate.template, this.changeSet); if (diffRequiresApproval(diff, options.requireApproval)) { // The security diff is formatted via `Formatter`, which takes in a stream @@ -291,7 +309,7 @@ export class DiffFormatter { // `formatSecurityDiff` to decide what to do with it. const stream = new StringWriteStream(); - stream.write(format(`Stack ${chalk.bold(options.stackName)}\n`)); + stream.write(format(`Stack ${chalk.bold(this.stackName)}\n`)); // eslint-disable-next-line max-len ioDefaultHelper.warning(`This deployment will make potentially sensitive changes according to your current security approval level (--require-approval ${options.requireApproval}).`); diff --git a/packages/@aws-cdk/tmp-toolkit-helpers/test/api/diff/diff.test.ts b/packages/@aws-cdk/tmp-toolkit-helpers/test/api/diff/diff.test.ts index 945703004..23e9db33d 100644 --- a/packages/@aws-cdk/tmp-toolkit-helpers/test/api/diff/diff.test.ts +++ b/packages/@aws-cdk/tmp-toolkit-helpers/test/api/diff/diff.test.ts @@ -59,20 +59,13 @@ describe('formatStackDiff', () => { // WHEN const formatter = new DiffFormatter({ ioHelper: mockIoHelper, - oldTemplate: {}, - newTemplate: { - template: {}, - templateFile: 'template.json', + templateInfo: { + oldTemplate: mockNewTemplate.template, + newTemplate: mockNewTemplate, stackName: 'test-stack', - findMetadataByType: () => [], - } as any, - }); - const result = formatter.formatStackDiff({ - strict: false, - context: 3, - quiet: false, - stackName: 'test-stack', + }, }); + const result = formatter.formatStackDiff(); // THEN expect(result.numStacksWithChanges).toBe(0); @@ -88,12 +81,13 @@ describe('formatStackDiff', () => { // WHEN const formatter = new DiffFormatter({ ioHelper: mockIoHelper, - oldTemplate: {}, - newTemplate: mockNewTemplate, - }); - const result = formatter.formatStackDiff({ - stackName: 'test-stack', + templateInfo: { + oldTemplate: {}, + newTemplate: mockNewTemplate, + stackName: 'test-stack', + }, }); + const result = formatter.formatStackDiff(); // THEN expect(result.numStacksWithChanges).toBe(1); @@ -110,13 +104,14 @@ describe('formatStackDiff', () => { // WHEN const formatter = new DiffFormatter({ ioHelper: mockIoHelper, - oldTemplate: {}, - newTemplate: mockNewTemplate, - }); - const result = formatter.formatStackDiff({ - stackName: 'test-stack', - isImport: true, + templateInfo: { + oldTemplate: {}, + newTemplate: mockNewTemplate, + stackName: 'test-stack', + isImport: true, + }, }); + const result = formatter.formatStackDiff(); // THEN expect(result.numStacksWithChanges).toBe(1); @@ -132,7 +127,7 @@ describe('formatStackDiff', () => { test('handles nested stack templates', () => { // GIVEN - const nestedStackTemplates = { + const nestedStacks = { NestedStack1: { deployedTemplate: {}, generatedTemplate: {}, @@ -151,13 +146,14 @@ describe('formatStackDiff', () => { // WHEN const formatter = new DiffFormatter({ ioHelper: mockIoHelper, - oldTemplate: {}, - newTemplate: mockNewTemplate, - }); - const result = formatter.formatStackDiff({ - stackName: 'test-stack', - nestedStackTemplates, + templateInfo: { + oldTemplate: {}, + newTemplate: mockNewTemplate, + stackName: 'test-stack', + nestedStacks, + }, }); + const result = formatter.formatStackDiff(); // THEN expect(result.numStacksWithChanges).toBe(3); @@ -226,16 +222,13 @@ describe('formatSecurityDiff', () => { // WHEN const formatter = new DiffFormatter({ ioHelper: mockIoHelper, - oldTemplate: {}, - newTemplate: { - template: {}, - templateFile: 'template.json', + templateInfo: { + oldTemplate: mockNewTemplate.template, + newTemplate: mockNewTemplate, stackName: 'test-stack', - findMetadataByType: () => [], - } as any, + }, }); const result = formatter.formatSecurityDiff({ - stackName: 'test-stack', requireApproval: RequireApproval.BROADENING, }); @@ -248,11 +241,13 @@ describe('formatSecurityDiff', () => { // WHEN const formatter = new DiffFormatter({ ioHelper: mockIoHelper, - oldTemplate: {}, - newTemplate: mockNewTemplate, + templateInfo: { + oldTemplate: {}, + newTemplate: mockNewTemplate, + stackName: 'test-stack', + }, }); const result = formatter.formatSecurityDiff({ - stackName: 'test-stack', requireApproval: RequireApproval.BROADENING, }); @@ -281,11 +276,13 @@ describe('formatSecurityDiff', () => { // WHEN const formatter = new DiffFormatter({ ioHelper: mockIoHelper, - oldTemplate: {}, - newTemplate: mockNewTemplate, + templateInfo: { + oldTemplate: {}, + newTemplate: mockNewTemplate, + stackName: 'test-stack', + }, }); const result = formatter.formatSecurityDiff({ - stackName: 'test-stack', requireApproval: RequireApproval.ANY_CHANGE, }); @@ -317,11 +314,13 @@ describe('formatSecurityDiff', () => { // WHEN const formatter = new DiffFormatter({ ioHelper: mockIoHelper, - oldTemplate: {}, - newTemplate: mockNewTemplate, + templateInfo: { + oldTemplate: {}, + newTemplate: mockNewTemplate, + stackName: 'test-stack', + }, }); const result = formatter.formatSecurityDiff({ - stackName: 'test-stack', requireApproval: RequireApproval.NEVER, }); diff --git a/packages/@aws-cdk/toolkit-lib/lib/actions/diff/index.ts b/packages/@aws-cdk/toolkit-lib/lib/actions/diff/index.ts index 60ef58728..6255bfef1 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/actions/diff/index.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/actions/diff/index.ts @@ -1,4 +1,4 @@ -import type { StackSelector } from '../../api/cloud-assembly'; +import type { StackSelector } from '../../../lib/api/shared-public'; export interface CloudFormationDiffOptions { /** diff --git a/packages/@aws-cdk/toolkit-lib/lib/actions/diff/private/helpers.ts b/packages/@aws-cdk/toolkit-lib/lib/actions/diff/private/helpers.ts index 4917ce1b2..c447b0aab 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/actions/diff/private/helpers.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/actions/diff/private/helpers.ts @@ -1,7 +1,134 @@ import type { DescribeChangeSetOutput } from '@aws-cdk/cloudformation-diff'; import { fullDiff } from '@aws-cdk/cloudformation-diff'; import type * as cxapi from '@aws-cdk/cx-api'; -import { PermissionChangeType } from '../../../api/shared-public'; +import * as fs from 'fs-extra'; +import * as uuid from 'uuid'; +import type { ChangeSetDiffOptions, DiffOptions, LocalFileDiffOptions } from '..'; +import { DiffMethod } from '..'; +import type { Deployments, ResourcesToImport, SdkProvider, StackCollection } from '../../../api/aws-cdk'; +import { createDiffChangeSet, ResourceMigrator } from '../../../api/aws-cdk'; +import type { IoHelper, TemplateInfo } from '../../../api/shared-private'; +import { IO, removeNonImportResources } from '../../../api/shared-private'; +import { PermissionChangeType, ToolkitError } from '../../../api/shared-public'; +import { deserializeStructure, formatErrorMessage } from '../../../private/util'; + +export function makeTemplateInfos( + ioHelper: IoHelper, + stacks: StackCollection, + deployments: Deployments, + sdkProvider: SdkProvider, + options: DiffOptions, +): Promise { + switch (options.method?.method ?? DiffMethod.ChangeSet().method) { + case 'local-file': + return localFileDiff(stacks, options); + case 'template-only': + return cfnDiff(ioHelper, stacks, deployments, options, sdkProvider, false); + case 'change-set': + return cfnDiff(ioHelper, stacks, deployments, options, sdkProvider, true); + default: + throw new ToolkitError(formatErrorMessage(`Unknown diff method ${options.method}`)); + } +} + +async function localFileDiff(stacks: StackCollection, options: DiffOptions): Promise { + const methodOptions = (options.method?.options ?? {}) as LocalFileDiffOptions; + + // Compare single stack against fixed template + if (stacks.stackCount !== 1) { + throw new ToolkitError( + 'Can only select one stack when comparing to fixed template. Use --exclusively to avoid selecting multiple stacks.', + ); + } + + if (!(await fs.pathExists(methodOptions.path))) { + throw new ToolkitError(`There is no file at ${methodOptions.path}`); + } + + const file = fs.readFileSync(methodOptions.path).toString(); + const template = deserializeStructure(file); + + return [{ + oldTemplate: template, + newTemplate: stacks.firstStack, + }]; +} + +async function cfnDiff( + ioHelper: IoHelper, + stacks: StackCollection, + deployments: Deployments, + options: DiffOptions, + sdkProvider: SdkProvider, + changeSet: boolean, +): Promise { + const templateInfos = []; + const methodOptions = (options.method?.options ?? {}) as ChangeSetDiffOptions; + + // Compare N stacks against deployed templates + for (const stack of stacks.stackArtifacts) { + const templateWithNestedStacks = await deployments.readCurrentTemplateWithNestedStacks( + stack, + methodOptions.compareAgainstProcessedTemplate, + ); + const currentTemplate = templateWithNestedStacks.deployedRootTemplate; + const nestedStacks = templateWithNestedStacks.nestedStacks; + + const migrator = new ResourceMigrator({ deployments, ioHelper }); + const resourcesToImport = await migrator.tryGetResources(await deployments.resolveEnvironment(stack)); + if (resourcesToImport) { + removeNonImportResources(stack); + } + + templateInfos.push({ + oldTemplate: currentTemplate, + newTemplate: stack, + stackName: stack.stackName, + isImport: !!resourcesToImport, + nestedStacks, + changeSet: changeSet ? await changeSetDiff(ioHelper, deployments, stack, sdkProvider, resourcesToImport, methodOptions.parameters) : undefined, + }); + } + + return templateInfos; +} + +async function changeSetDiff( + ioHelper: IoHelper, + deployments: Deployments, + stack: cxapi.CloudFormationStackArtifact, + sdkProvider: SdkProvider, + resourcesToImport?: ResourcesToImport, + parameters: { [name: string]: string | undefined } = {}, +): Promise { + let stackExists = false; + try { + stackExists = await deployments.stackExists({ + stack, + deployName: stack.stackName, + tryLookupRole: true, + }); + } catch (e: any) { + await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`Checking if the stack ${stack.stackName} exists before creating the changeset has failed, will base the diff on template differences.\n`)); + await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(formatErrorMessage(e))); + stackExists = false; + } + + if (stackExists) { + return createDiffChangeSet(ioHelper, { + stack, + uuid: uuid.v4(), + deployments, + willExecute: false, + sdkProvider, + parameters: parameters, + resourcesToImport, + }); + } else { + await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`the stack '${stack.stackName}' has not been deployed to CloudFormation or describeStacks call failed, skipping changeset creation.`)); + return; + } +} /** * Return whether the diff has security-impacting changes that need confirmation. diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/aws-cdk.ts b/packages/@aws-cdk/toolkit-lib/lib/api/aws-cdk.ts index 8458989dd..8c1e50c40 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/aws-cdk.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/aws-cdk.ts @@ -12,6 +12,7 @@ export { CloudWatchLogEventMonitor, findCloudWatchLogGroups } from '../../../../ export { type WorkGraph, WorkGraphBuilder, AssetBuildNode, AssetPublishNode, StackNode, Concurrency } from '../../../../aws-cdk/lib/api/work-graph'; export { Bootstrapper } from '../../../../aws-cdk/lib/api/bootstrap'; export { loadTree, some } from '../../../../aws-cdk/lib/api/tree'; +export { ResourcesToImport } from '../../../../aws-cdk/lib/api/resource-import'; export { RWLock, type ILock } from '../../../../aws-cdk/lib/api/rwlock'; export { HotswapMode, HotswapPropertyOverrides, EcsHotswapProperties } from '../../../../aws-cdk/lib/api/hotswap'; diff --git a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts index 71ed5a00c..9ecf71f45 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts @@ -3,7 +3,6 @@ import * as cxapi from '@aws-cdk/cx-api'; import * as chalk from 'chalk'; import * as chokidar from 'chokidar'; import * as fs from 'fs-extra'; -import * as uuid from 'uuid'; import { NonInteractiveIoHost } from './non-interactive-io-host'; import type { ToolkitServices } from './private'; import { assemblyFromSource } from './private'; @@ -12,9 +11,8 @@ import { BootstrapSource } from '../actions/bootstrap'; import { AssetBuildTime, type DeployOptions } from '../actions/deploy'; import { type ExtendedDeployOptions, buildParameterMap, createHotswapPropertyOverrides, removePublishedAssetsFromWorkGraph } from '../actions/deploy/private'; import { type DestroyOptions } from '../actions/destroy'; -import type { ChangeSetDiffOptions, DiffOptions, LocalFileDiffOptions } from '../actions/diff'; -import { DiffMethod } from '../actions/diff'; -import { determinePermissionType } from '../actions/diff/private'; +import type { DiffOptions } from '../actions/diff'; +import { determinePermissionType, makeTemplateInfos as prepareDiff } from '../actions/diff/private'; import { type ListOptions } from '../actions/list'; import { type RollbackOptions } from '../actions/rollback'; import { type SynthOptions } from '../actions/synth'; @@ -22,7 +20,7 @@ import type { WatchOptions } from '../actions/watch'; import { patternsArrayForWatch } from '../actions/watch/private'; import { type SdkConfig } from '../api/aws-auth'; import type { SuccessfulDeployStackResult, StackCollection, Concurrency, AssetBuildNode, AssetPublishNode, StackNode } from '../api/aws-cdk'; -import { DEFAULT_TOOLKIT_STACK_NAME, Bootstrapper, SdkProvider, Deployments, HotswapMode, ResourceMigrator, tagsForStack, WorkGraphBuilder, CloudWatchLogEventMonitor, findCloudWatchLogGroups, createDiffChangeSet } from '../api/aws-cdk'; +import { DEFAULT_TOOLKIT_STACK_NAME, Bootstrapper, SdkProvider, Deployments, ResourceMigrator, tagsForStack, WorkGraphBuilder, CloudWatchLogEventMonitor, findCloudWatchLogGroups, HotswapMode } from '../api/aws-cdk'; import type { ICloudAssemblySource } from '../api/cloud-assembly'; import { StackSelectionStrategy } from '../api/cloud-assembly'; import type { StackAssembly } from '../api/cloud-assembly/private'; @@ -30,9 +28,9 @@ import { ALL_STACKS, CloudAssemblySourceBuilder, IdentityCloudAssemblySource } f import type { IIoHost, IoMessageLevel } from '../api/io'; import { IO, SPAN, asSdkLogger, withoutColor, withoutEmojis, withTrimmedWhitespace } from '../api/io/private'; import type { IoHelper } from '../api/shared-private'; -import { asIoHelper, DiffFormatter, RequireApproval, ToolkitError, removeNonImportResources } from '../api/shared-private'; +import { asIoHelper, DiffFormatter, RequireApproval, ToolkitError } from '../api/shared-private'; import type { ToolkitAction, AssemblyData, StackDetails } from '../api/shared-public'; -import { obscureTemplate, serializeStructure, validateSnsTopicArn, formatTime, formatErrorMessage, deserializeStructure } from '../private/util'; +import { obscureTemplate, serializeStructure, validateSnsTopicArn, formatTime, formatErrorMessage } from '../private/util'; import { pLimit } from '../util/concurrency'; import { promiseWithResolvers } from '../util/promises'; @@ -262,33 +260,19 @@ export class Toolkit extends CloudAssemblySourceBuilder { const strict = !!options.strict; const contextLines = options.contextLines || 3; - const diffMethod = options.method ?? DiffMethod.ChangeSet(); let diffs = 0; let formattedSecurityDiff = ''; let formattedStackDiff = ''; - if (diffMethod.method === 'local-file') { - const methodOptions = diffMethod.options as LocalFileDiffOptions; + const templateInfos = await prepareDiff(ioHelper, stacks, deployments, await this.sdkProvider('diff'), options); - // Compare single stack against fixed template - if (stacks.stackCount !== 1) { - throw new ToolkitError( - 'Can only select one stack when comparing to fixed template. Use --exclusively to avoid selecting multiple stacks.', - ); - } - - if (!(await fs.pathExists(methodOptions.path))) { - throw new ToolkitError(`There is no file at ${path}`); - } - - const file = fs.readFileSync(methodOptions.path).toString(); - const template = deserializeStructure(file); + for (const templateInfo of templateInfos) { const formatter = new DiffFormatter({ ioHelper, - oldTemplate: template, - newTemplate: stacks.firstStack, + templateInfo, }); + if (options.securityOnly) { const securityDiff = formatter.formatSecurityDiff({ requireApproval: RequireApproval.BROADENING, @@ -303,81 +287,6 @@ export class Toolkit extends CloudAssemblySourceBuilder { formattedStackDiff = diff.formattedDiff; diffs = diff.numStacksWithChanges; } - } else { - const methodOptions = diffMethod.options as ChangeSetDiffOptions; - // Compare N stacks against deployed templates - for (const stack of stacks.stackArtifacts) { - const templateWithNestedStacks = await deployments.readCurrentTemplateWithNestedStacks( - stack, - methodOptions.compareAgainstProcessedTemplate, - ); - const currentTemplate = templateWithNestedStacks.deployedRootTemplate; - const nestedStacks = templateWithNestedStacks.nestedStacks; - - const formatter = new DiffFormatter({ - ioHelper, - oldTemplate: currentTemplate, - newTemplate: stack, - }); - - const migrator = new ResourceMigrator({ deployments, ioHelper }); - const resourcesToImport = await migrator.tryGetResources(await deployments.resolveEnvironment(stack)); - if (resourcesToImport) { - removeNonImportResources(stack); - } - - let changeSet = undefined; - - if (diffMethod.method === 'change-set') { - let stackExists = false; - try { - stackExists = await deployments.stackExists({ - stack, - deployName: stack.stackName, - tryLookupRole: true, - }); - } catch (e: any) { - await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`Checking if the stack ${stack.stackName} exists before creating the changeset has failed, will base the diff on template differences.\n`)); - await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(formatErrorMessage(e))); - stackExists = false; - } - - if (stackExists) { - changeSet = await createDiffChangeSet(ioHelper, { - stack, - uuid: uuid.v4(), - deployments, - willExecute: false, - sdkProvider: await this.sdkProvider('diff'), - parameters: methodOptions.parameters ?? {}, - resourcesToImport, - }); - } else { - await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`the stack '${stack.stackName}' has not been deployed to CloudFormation or describeStacks call failed, skipping changeset creation.`)); - } - } - - if (options.securityOnly) { - const securityDiff = formatter.formatSecurityDiff({ - requireApproval: RequireApproval.BROADENING, - stackName: stack.displayName, - changeSet, - }); - formattedSecurityDiff = securityDiff.formattedDiff ?? ''; - diffs = securityDiff.formattedDiff ? diffs + 1 : diffs; - } else { - const diff = formatter.formatStackDiff({ - strict, - context: contextLines, - stackName: stack.displayName, - changeSet, - isImport: !!resourcesToImport, - nestedStackTemplates: nestedStacks, - }); - formattedStackDiff = diff.formattedDiff; - diffs = diff.numStacksWithChanges; - } - } } await diffSpan.end(`✨ Number of stacks with differences: ${diffs}`, { diff --git a/packages/aws-cdk/lib/cli/cdk-toolkit.ts b/packages/aws-cdk/lib/cli/cdk-toolkit.ts index c32df99e7..fe5a0cfa1 100644 --- a/packages/aws-cdk/lib/cli/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cli/cdk-toolkit.ts @@ -197,8 +197,10 @@ export class CdkToolkit { const template = deserializeStructure(await fs.readFile(options.templatePath, { encoding: 'UTF-8' })); const formatter = new DiffFormatter({ ioHelper: asIoHelper(this.ioHost, 'diff'), - oldTemplate: template, - newTemplate: stacks.firstStack, + templateInfo: { + oldTemplate: template, + newTemplate: stacks.firstStack, + }, }); if (options.securityOnly) { @@ -227,11 +229,6 @@ export class CdkToolkit { ); const currentTemplate = templateWithNestedStacks.deployedRootTemplate; const nestedStacks = templateWithNestedStacks.nestedStacks; - const formatter = new DiffFormatter({ - ioHelper: asIoHelper(this.ioHost, 'diff'), - oldTemplate: currentTemplate, - newTemplate: stack, - }); const migrator = new ResourceMigrator({ deployments: this.props.deployments, @@ -279,11 +276,21 @@ export class CdkToolkit { } } + const formatter = new DiffFormatter({ + ioHelper: asIoHelper(this.ioHost, 'diff'), + templateInfo: { + oldTemplate: currentTemplate, + newTemplate: stack, + stackName: stack.displayName, + changeSet, + isImport: !!resourcesToImport, + nestedStacks, + }, + }); + if (options.securityOnly) { const securityDiff = formatter.formatSecurityDiff({ requireApproval: RequireApproval.BROADENING, - stackName: stack.displayName, - changeSet, }); if (securityDiff.formattedDiff) { info(securityDiff.formattedDiff); @@ -294,10 +301,6 @@ export class CdkToolkit { strict, context: contextLines, quiet, - stackName: stack.displayName, - changeSet, - isImport: !!resourcesToImport, - nestedStackTemplates: nestedStacks, }); info(diff.formattedDiff); diffs += diff.numStacksWithChanges; @@ -424,8 +427,10 @@ export class CdkToolkit { const currentTemplate = await this.props.deployments.readCurrentTemplate(stack); const formatter = new DiffFormatter({ ioHelper: asIoHelper(this.ioHost, 'deploy'), - oldTemplate: currentTemplate, - newTemplate: stack, + templateInfo: { + oldTemplate: currentTemplate, + newTemplate: stack, + }, }); const securityDiff = formatter.formatSecurityDiff({ requireApproval,