From 48d40f2897782e7102e3fa95841a4f6b78a5ced5 Mon Sep 17 00:00:00 2001 From: 9pace Date: Thu, 15 Oct 2026 02:51:52 +0000 Subject: [PATCH 01/16] fix(toolkit-lib): deploy approval diff hides an added key in a JSON string with an intrinsic Since #1785 deploy describes its change set with IncludePropertyValues too, so the diff shown for --require-approval can leave out the same changes as cdk diff did (#2036). Pass the templates to the describer when updating a stack, so it describes the change set again when the approval diff would otherwise hide a template difference. --- .../lib/api/deployments/deploy-stack.ts | 2 ++ .../test/api/deployments/deploy-stack.test.ts | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deploy-stack.ts b/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deploy-stack.ts index d5abea441..333498c33 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deploy-stack.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deploy-stack.ts @@ -647,6 +647,8 @@ class FullCloudFormationDeployment { ioHelper: this.ioHelper, stackNameOrArn: changeSet.StackId ?? this.stackName, changeSetNameOrArn: changeSet.Id ?? changeSetName, + // The deployed template is cached, and an ordinary deploy has already read it to decide whether it can skip. + templates: this.update ? { deployed: () => this.cloudFormationStack.template(), generated: this.stackArtifact.template } : undefined, }).waitAndThrowOnProblem({ diagnoser: this.diagnoser, }); diff --git a/packages/@aws-cdk/toolkit-lib/test/api/deployments/deploy-stack.test.ts b/packages/@aws-cdk/toolkit-lib/test/api/deployments/deploy-stack.test.ts index 1a5f59e34..64aa182db 100644 --- a/packages/@aws-cdk/toolkit-lib/test/api/deployments/deploy-stack.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/api/deployments/deploy-stack.test.ts @@ -1003,6 +1003,29 @@ test('deployStack reports no change if describeChangeSet returns an error that i expect(deployResult.type === 'did-deploy-stack' && deployResult.noOp).toEqual(true); }); +test('describes the change set again without property values when it would hide a template difference (#2036)', async () => { + // GIVEN - a JSON string built with `Stack.toJsonString()` gains a key. Described with + // IncludePropertyValues, CloudFormation can leave such a change out without a warning. + const json = (lastFragment: string) => ({ 'Fn::Join': ['', ['{"stack":"', { Ref: 'AWS::StackName' }, lastFragment]] }); + const parameterTemplate = (value: any) => ({ Resources: { Param: { Type: 'AWS::SSM::Parameter', Properties: { Type: 'String', Value: value } } } }); + givenStackExists(); + givenTemplateIs(parameterTemplate(json('","a":"1"}'))); + mockCloudFormationClient.on(DescribeChangeSetCommand, { IncludePropertyValues: true }).callsFake(async (input) => ({ + ...await fakeCfn.describeChangeSet(input), + Changes: [], + })); + + // WHEN + await testDeployStack({ + ...standardDeployStackArguments(testStack({ stackName: 'withouterrors', template: parameterTemplate(json('","a":"1","b":"2"}')) })), + }); + + // THEN - the change set is described again, so the approval diff and the execution see the change + const withoutPropertyValues = mockCloudFormationClient.commandCalls(DescribeChangeSetCommand) + .filter((call) => call.args[0].input.IncludePropertyValues === false); + expect(withoutPropertyValues).toHaveLength(1); +}); + test('deployStack throws error in case of early validation failures', async () => { mockCloudFormationClient.on(DescribeChangeSetCommand).resolves({ ChangeSetName: 'cdk-deploy-change-set', From dba1231fb32348c0650bf19b46dc8e92ce25f875 Mon Sep 17 00:00:00 2001 From: 9pace Date: Thu, 15 Oct 2026 14:29:32 +0000 Subject: [PATCH 02/16] fix(toolkit-lib): complete the approval diff for execute-change-set, and test the approval diff end to end --- ...-json-built-with-an-intrinsic.integtest.ts | 31 +++++++++++++++++++ .../lib/api/deployments/deployments.ts | 4 +++ .../toolkit-lib/lib/toolkit/toolkit.ts | 8 ++--- .../toolkit-lib/test/actions/deploy.test.ts | 18 +++++++++++ .../test/api/deployments/deploy-stack.test.ts | 26 +++++++++++++--- 5 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts new file mode 100644 index 000000000..7961debd9 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts @@ -0,0 +1,31 @@ +import { integTest, withSpecificFixture } from '../../../lib'; + +/** + * Regression test for the deploy side of https://github.com/aws/aws-cdk-cli/issues/2036 + * + * Adding a key to JSON that is built with an intrinsic and stored in a string property is a change + * CloudFormation leaves out of a change set described with IncludePropertyValues, without a warning. + * The diff shown for --require-approval must show it anyway. + * + * This only exercises the recovery for as long as CloudFormation keeps leaving the change out. If it + * stops, the test still passes; the toolkit-lib unit tests are the lasting coverage. + */ +integTest( + 'cdk deploy --require-approval any-change shows a key added to JSON built with an intrinsic', + withSpecificFixture('json-intrinsic-app', async (fixture) => { + // GIVEN - a deployed parameter holding {"stack":"","a":"1"} + await fixture.cdkDeploy('json-intrinsic'); + + // WHEN - a key is added to the JSON, and the deploy asks for approval (auto-confirmed) + const output = await fixture.cdkDeploy('json-intrinsic', { + options: ['--require-approval=any-change', '--yes'], + neverRequireApproval: false, + modEnv: { VARIANT: 'add-key' }, + }); + + // THEN - the approval diff shows the parameter value as changed + expect(output).toContain('Do you wish to deploy these changes'); + expect(output).toMatch(/\[~\].*AWS::SSM::Parameter(?!::)/); + expect(output).toMatch(/\[~\].*Value/); + }), +); diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts b/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts index 5c73ba961..7ee643c76 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts @@ -524,12 +524,15 @@ export class Deployments { * @param stack - The Stack artifact * @param changeSetName - The name of the change set * @param stackArn - Optional the exact arn of the stack. Prefer this if you have it available. + * @param deployedTemplate - Optional the deployed template the diff compares against, so that a + * description that would hide a template difference is completed (see `ChangeSetDescriber`) * @returns the DescribeChangeSet response */ public async describeChangeSet( stack: cxapi.CloudFormationStackArtifact, changeSetName: string, stackArn?: string, + deployedTemplate?: Template, ): Promise { const env = await this.envs.accessStackForMutableStackOperations(stack); const cfn = env.sdk.cloudFormation(); @@ -538,6 +541,7 @@ export class Deployments { ioHelper: this.ioHelper, stackNameOrArn: stackArn ?? stack.stackName, changeSetNameOrArn: changeSetName, + templates: deployedTemplate ? { deployed: async () => deployedTemplate, generated: stack.template } : undefined, }).waitAndThrowOnProblem({ diagnoser: new CloudFormationStackDiagnoser({ sdk: env.sdk, diff --git a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts index 18d24aec8..2decbd0c7 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts @@ -936,13 +936,13 @@ export class Toolkit extends CloudAssemblySourceBuilder { // changes — there is nothing for the user to approve. Outputs, stack ARN, // and timings are still emitted via the normal no-op deploy path below. if (!prepareResult?.noOp) { + // Nested stacks are included, so that changes to their resources are part of the diff and destructive changes + const { deployedRootTemplate, nestedStacks } = await deployments.readCurrentTemplateWithNestedStacks(stack); + // For execute-change-set, describe the existing change set so we can show an accurate diff const diffChangeSet = isExecuteChangeSetDeployment(options.deploymentMethod) - ? (await deployments.describeChangeSet(stack, options.deploymentMethod.changeSetName, prepareResult?.stackArn)).changeSet + ? (await deployments.describeChangeSet(stack, options.deploymentMethod.changeSetName, prepareResult?.stackArn, deployedRootTemplate)).changeSet : prepareResult?.changeSet; - - // Nested stacks are included, so that changes to their resources are part of the diff and destructive changes - const { deployedRootTemplate, nestedStacks } = await deployments.readCurrentTemplateWithNestedStacks(stack); const formatter = new DiffFormatter({ templateInfo: { oldTemplate: deployedRootTemplate, diff --git a/packages/@aws-cdk/toolkit-lib/test/actions/deploy.test.ts b/packages/@aws-cdk/toolkit-lib/test/actions/deploy.test.ts index 0d8539f5d..f716b8f07 100644 --- a/packages/@aws-cdk/toolkit-lib/test/actions/deploy.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/actions/deploy.test.ts @@ -262,6 +262,24 @@ IAM Statement Changes ); }); + test('execute-change-set describes the change set with the deployed template, so the approval diff can be completed (#2036)', async () => { + // GIVEN + const deployedRootTemplate = { Resources: { Param: { Type: 'AWS::SSM::Parameter', Properties: { Type: 'String', Value: 'deployed' } } } }; + jest.spyOn(deployments.Deployments.prototype, 'readCurrentTemplateWithNestedStacks').mockResolvedValue({ deployedRootTemplate, nestedStacks: {} }); + const describeChangeSet = jest.spyOn(deployments.Deployments.prototype, 'describeChangeSet').mockResolvedValue({ + changeSet: { Status: 'CREATE_COMPLETE', Changes: [], $metadata: {} }, + } as any); + + // WHEN + const cx = await cdkOutFixture(toolkit, 'stack-with-bucket'); + await toolkit.deploy(cx, { + deploymentMethod: { method: 'execute-change-set', changeSetName: 'my-change-set' }, + }); + + // THEN + expect(describeChangeSet).toHaveBeenCalledWith(expect.anything(), 'my-change-set', undefined, deployedRootTemplate); + }); + test('uses template-only diff with method=direct', async () => { // WHEN const cx = await cdkOutFixture(toolkit, 'stack-with-bucket'); diff --git a/packages/@aws-cdk/toolkit-lib/test/api/deployments/deploy-stack.test.ts b/packages/@aws-cdk/toolkit-lib/test/api/deployments/deploy-stack.test.ts index 64aa182db..8e02cb92f 100644 --- a/packages/@aws-cdk/toolkit-lib/test/api/deployments/deploy-stack.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/api/deployments/deploy-stack.test.ts @@ -1004,26 +1004,42 @@ test('deployStack reports no change if describeChangeSet returns an error that i }); test('describes the change set again without property values when it would hide a template difference (#2036)', async () => { - // GIVEN - a JSON string built with `Stack.toJsonString()` gains a key. Described with - // IncludePropertyValues, CloudFormation can leave such a change out without a warning. + // GIVEN - a JSON string built with `Stack.toJsonString()` gains a key. CloudFormation reports the + // change when described without property values, and leaves it out, without a warning, when + // described with them. const json = (lastFragment: string) => ({ 'Fn::Join': ['', ['{"stack":"', { Ref: 'AWS::StackName' }, lastFragment]] }); const parameterTemplate = (value: any) => ({ Resources: { Param: { Type: 'AWS::SSM::Parameter', Properties: { Type: 'String', Value: value } } } }); + const parameterValueChange: Change = { + Type: 'Resource', + ResourceChange: { + Action: 'Modify', + LogicalResourceId: 'Param', + ResourceType: 'AWS::SSM::Parameter', + Replacement: 'False', + Details: [{ Target: { Attribute: 'Properties', Name: 'Value', RequiresRecreation: 'Never' }, Evaluation: 'Static', ChangeSource: 'DirectModification' }], + }, + }; givenStackExists(); givenTemplateIs(parameterTemplate(json('","a":"1"}'))); + fakeCfn.overrideChangeSetChanges = [parameterValueChange]; mockCloudFormationClient.on(DescribeChangeSetCommand, { IncludePropertyValues: true }).callsFake(async (input) => ({ ...await fakeCfn.describeChangeSet(input), Changes: [], })); - // WHEN - await testDeployStack({ + // WHEN - deploy prepares the change set before showing the approval diff + const result = await testDeployStack({ ...standardDeployStackArguments(testStack({ stackName: 'withouterrors', template: parameterTemplate(json('","a":"1","b":"2"}')) })), + deploymentMethod: { method: 'change-set', execute: false }, + willExecuteChangeSet: true, }); - // THEN - the change set is described again, so the approval diff and the execution see the change + // THEN - the change set is described again, and the returned description, which the approval + // diff uses, includes the change const withoutPropertyValues = mockCloudFormationClient.commandCalls(DescribeChangeSetCommand) .filter((call) => call.args[0].input.IncludePropertyValues === false); expect(withoutPropertyValues).toHaveLength(1); + expect(result.type === 'did-deploy-stack' ? result.changeSet?.Changes : undefined).toEqual([parameterValueChange]); }); test('deployStack throws error in case of early validation failures', async () => { From 53ddb7dc5d9d6b7edb924bc4e3c83b61d5b99f3e Mon Sep 17 00:00:00 2001 From: 9pace Date: Tue, 20 Oct 2026 14:26:28 +0000 Subject: [PATCH 03/16] fix(toolkit-lib): pass the deployed template to the describer as a value, as on main --- .../@aws-cdk/toolkit-lib/lib/api/deployments/deploy-stack.ts | 2 +- .../@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deploy-stack.ts b/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deploy-stack.ts index 333498c33..b7b123ef6 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deploy-stack.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deploy-stack.ts @@ -648,7 +648,7 @@ class FullCloudFormationDeployment { stackNameOrArn: changeSet.StackId ?? this.stackName, changeSetNameOrArn: changeSet.Id ?? changeSetName, // The deployed template is cached, and an ordinary deploy has already read it to decide whether it can skip. - templates: this.update ? { deployed: () => this.cloudFormationStack.template(), generated: this.stackArtifact.template } : undefined, + templates: this.update ? { deployed: await this.cloudFormationStack.template(), generated: this.stackArtifact.template } : undefined, }).waitAndThrowOnProblem({ diagnoser: this.diagnoser, }); diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts b/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts index 7ee643c76..a3fa5cd74 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts @@ -541,7 +541,7 @@ export class Deployments { ioHelper: this.ioHelper, stackNameOrArn: stackArn ?? stack.stackName, changeSetNameOrArn: changeSetName, - templates: deployedTemplate ? { deployed: async () => deployedTemplate, generated: stack.template } : undefined, + templates: deployedTemplate ? { deployed: deployedTemplate, generated: stack.template } : undefined, }).waitAndThrowOnProblem({ diagnoser: new CloudFormationStackDiagnoser({ sdk: env.sdk, From 5b8488cdad53e6373e0001ce0656def3f0179237 Mon Sep 17 00:00:00 2001 From: 9pace Date: Tue, 20 Oct 2026 22:47:42 +0000 Subject: [PATCH 04/16] test: integ tests for the deploy approval, execute-change-set and nested change set cases --- .../cdk-apps/json-intrinsic-app/app.js | 68 +++++++++++++------ ...an-intrinsic-then-executes-it.integtest.ts | 40 +++++++++++ ...ormation-drops-with-a-warning.integtest.ts | 26 +++++++ ...-json-built-with-an-intrinsic.integtest.ts | 28 ++++++++ ...ormation-drops-with-a-warning.integtest.ts | 28 ++++++++ ...lacement-cloudformation-drops.integtest.ts | 26 +++++++ ...-json-built-with-an-intrinsic.integtest.ts | 27 ++++++++ ...ded-to-json-in-a-nested-stack.integtest.ts | 27 ++++++++ ...-json-built-with-an-intrinsic.integtest.ts | 39 +++++++++++ ...lue-in-json-in-a-nested-stack.integtest.ts | 34 ++++++++++ ...ded-to-json-in-a-nested-stack.integtest.ts | 34 ++++++++++ ...-json-built-with-an-intrinsic.integtest.ts | 43 ++++++++++++ .../toolkit-helpers.ts | 36 +++++++++- 13 files changed, 436 insertions(+), 20 deletions(-) create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-execute-shows-a-key-added-to-json-built-with-an-intrinsic-then-executes-it.integtest.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-replacement-cloudformation-drops-with-a-warning.integtest.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---revert-drift-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-deploy-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-changed-value-in-json-in-a-nested-stack.integtest.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts create mode 100644 packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-execute-change-set-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts diff --git a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js index 26df22527..03ab62c42 100644 --- a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js +++ b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js @@ -1,4 +1,5 @@ const cdk = require('aws-cdk-lib/core'); +const ecs = require('aws-cdk-lib/aws-ecs'); const ssm = require('aws-cdk-lib/aws-ssm'); const stackPrefix = process.env.STACK_NAME_PREFIX; @@ -15,33 +16,62 @@ class JsonIntrinsicStack extends cdk.Stack { constructor(scope, id, props) { super(scope, id, props); new ssm.StringParameter(this, 'Param', { - stringValue: this.parameterValue(process.env.VARIANT), + stringValue: jsonValue(this, process.env.VARIANT), description: process.env.VARIANT === 'add-key-and-description' ? 'd2' : 'd1', }); } +} + +// The same value in a container environment variable of an ECS task definition. Any change to the +// container definitions replaces the task definition. CloudFormation leaves that replacement out of a +// change set described with IncludePropertyValues too, but says so in a warning. +class JsonIntrinsicReplaceStack extends cdk.Stack { + constructor(scope, id, props) { + super(scope, id, props); + const taskDefinition = new ecs.TaskDefinition(this, 'Task', { + compatibility: ecs.Compatibility.FARGATE, + cpu: '256', + memoryMiB: '512', + }); + taskDefinition.addContainer('Container', { + image: ecs.ContainerImage.fromRegistry('public.ecr.aws/docker/library/busybox:latest'), + environment: { CONFIG: jsonValue(this, process.env.VARIANT) }, + }); + } +} + +// The same parameter in a nested stack. +class JsonIntrinsicNestedStack extends cdk.Stack { + constructor(scope, id, props) { + super(scope, id, props); + const nested = new cdk.NestedStack(this, 'Nested'); + new ssm.StringParameter(nested, 'Param', { stringValue: jsonValue(nested, process.env.VARIANT) }); + } +} - parameterValue(variant) { - switch (variant) { - case undefined: - return this.toJsonString({ stack: cdk.Aws.STACK_NAME, a: '1' }); - case 'change-value': - return this.toJsonString({ stack: cdk.Aws.STACK_NAME, a: '2' }); - case 'add-key': - case 'add-key-and-description': - return this.toJsonString({ stack: cdk.Aws.STACK_NAME, a: '1', b: '2' }); - case 'add-key-with-token': - return this.toJsonString({ stack: cdk.Aws.STACK_NAME, a: '1', region: cdk.Aws.REGION }); - case 'reorder-keys': - return this.toJsonString({ a: '1', stack: cdk.Aws.STACK_NAME }); - case 'same-value-with-fn-sub': - return cdk.Fn.sub('{"stack":"${AWS::StackName}","a":"1"}'); - default: - throw new Error(`unknown VARIANT: ${variant}`); - } +function jsonValue(stack, variant) { + switch (variant) { + case undefined: + return stack.toJsonString({ stack: cdk.Aws.STACK_NAME, a: '1' }); + case 'change-value': + return stack.toJsonString({ stack: cdk.Aws.STACK_NAME, a: '2' }); + case 'add-key': + case 'add-key-and-description': + return stack.toJsonString({ stack: cdk.Aws.STACK_NAME, a: '1', b: '2' }); + case 'add-key-with-token': + return stack.toJsonString({ stack: cdk.Aws.STACK_NAME, a: '1', region: cdk.Aws.REGION }); + case 'reorder-keys': + return stack.toJsonString({ a: '1', stack: cdk.Aws.STACK_NAME }); + case 'same-value-with-fn-sub': + return cdk.Fn.sub('{"stack":"${AWS::StackName}","a":"1"}'); + default: + throw new Error(`unknown VARIANT: ${variant}`); } } const app = new cdk.App(); new JsonIntrinsicStack(app, `${stackPrefix}-json-intrinsic`); +new JsonIntrinsicReplaceStack(app, `${stackPrefix}-json-intrinsic-replace`); +new JsonIntrinsicNestedStack(app, `${stackPrefix}-json-intrinsic-nested`); app.synth(); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-execute-shows-a-key-added-to-json-built-with-an-intrinsic-then-executes-it.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-execute-shows-a-key-added-to-json-built-with-an-intrinsic-then-executes-it.integtest.ts new file mode 100644 index 000000000..a2f85defa --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-execute-shows-a-key-added-to-json-built-with-an-intrinsic-then-executes-it.integtest.ts @@ -0,0 +1,40 @@ +import { integTest, withSpecificFixture } from '../../../lib'; + +/** + * Regression test for the deploy side of https://github.com/aws/aws-cdk-cli/issues/2036 + * + * A change set created with --no-execute and --require-approval asks for approval with a diff. Adding a + * key to JSON built with an intrinsic is left out of the change set described with IncludePropertyValues, + * without a warning; the diff must show it anyway. Executing the change set afterwards applies the key. + */ +integTest( + 'cdk deploy --no-execute shows a key added to JSON built with an intrinsic, then executes it', + withSpecificFixture('json-intrinsic-app', async (fixture) => { + const stackName = fixture.fullStackName('json-intrinsic'); + const changeSetName = 'json-intrinsic-add-key'; + + // GIVEN - a deployed parameter holding {"stack":"","a":"1"} + await fixture.cdkDeploy('json-intrinsic'); + + // WHEN - a key is added, and a change set is created without executing it (approval auto-confirmed) + const prepare = await fixture.cdkDeploy('json-intrinsic', { + options: ['--no-execute', `--change-set-name=${changeSetName}`, '--require-approval=any-change', '--yes'], + neverRequireApproval: false, + modEnv: { VARIANT: 'add-key' }, + }); + + // THEN - the approval diff shows the parameter value as changed + expect(prepare).toMatch(/\[~\].*AWS::SSM::Parameter(?!::)/); + expect(prepare).toMatch(/\[~\].*Value/); + + // WHEN - the change set is executed + await fixture.cdkDeploy('json-intrinsic', { + options: ['--method=execute-change-set', `--change-set-name=${changeSetName}`], + modEnv: { VARIANT: 'add-key' }, + }); + + // THEN - the deployed template has the key: a template diff finds nothing left to change + const diff = await fixture.cdk(['diff', '--method=template', stackName], { modEnv: { VARIANT: 'add-key' } }); + expect(diff).toContain('There were no differences'); + }), +); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts new file mode 100644 index 000000000..dbf69fbfa --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts @@ -0,0 +1,26 @@ +import { integTest, withSpecificFixture } from '../../../lib'; + +/** + * Guard for https://github.com/aws/aws-cdk-cli/issues/2036 + * + * A replacement cannot be deployed with rollback disabled. The task definition replacement + * CloudFormation leaves out of a change set described with IncludePropertyValues (with a warning) must + * still be found when the change set is executed. + */ +integTest( + 'cdk deploy --no-rollback stops for a replacement CloudFormation drops with a warning', + withSpecificFixture('json-intrinsic-app', async (fixture) => { + // GIVEN - a deployed task definition whose environment holds {"stack":"","a":"1"} + await fixture.cdkDeploy('json-intrinsic-replace'); + + // WHEN - a key is added to the JSON and the deploy runs with rollback disabled (the offer to deploy + // with rollback enabled is auto-confirmed) + const output = await fixture.cdkDeploy('json-intrinsic-replace', { + options: ['--no-rollback', '--yes'], + modEnv: { VARIANT: 'add-key' }, + }); + + // THEN - CDK refuses to replace with rollback disabled + expect(output).toContain('Change includes a replacement which cannot be deployed while rollback is disabled'); + }), +); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts new file mode 100644 index 000000000..63140dd8a --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts @@ -0,0 +1,28 @@ +import { integTest, withSpecificFixture } from '../../../lib'; + +/** + * Companion to the deploy regression test for https://github.com/aws/aws-cdk-cli/issues/2036 + * + * Changing an existing value in JSON built with an intrinsic is a change CloudFormation lists even in a + * change set described with IncludePropertyValues. The approval diff shows it with or without the + * recovery for changes CloudFormation leaves out. + */ +integTest( + 'cdk deploy --require-approval any-change shows a changed value in JSON built with an intrinsic', + withSpecificFixture('json-intrinsic-app', async (fixture) => { + // GIVEN - a deployed parameter holding {"stack":"","a":"1"} + await fixture.cdkDeploy('json-intrinsic'); + + // WHEN - an existing value changes, and the deploy asks for approval (auto-confirmed) + const output = await fixture.cdkDeploy('json-intrinsic', { + options: ['--require-approval=any-change', '--yes'], + neverRequireApproval: false, + modEnv: { VARIANT: 'change-value' }, + }); + + // THEN - the approval diff shows the parameter value as changed + expect(output).toContain('Do you wish to deploy these changes'); + expect(output).toMatch(/\[~\].*AWS::SSM::Parameter(?!::)/); + expect(output).toMatch(/\[~\].*Value/); + }), +); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-replacement-cloudformation-drops-with-a-warning.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-replacement-cloudformation-drops-with-a-warning.integtest.ts new file mode 100644 index 000000000..99ceda55e --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-replacement-cloudformation-drops-with-a-warning.integtest.ts @@ -0,0 +1,28 @@ +import { integTest, withSpecificFixture } from '../../../lib'; + +/** + * Guard for https://github.com/aws/aws-cdk-cli/issues/2036 + * + * Adding a key to JSON in a container environment variable replaces the task definition. Described with + * IncludePropertyValues, CloudFormation leaves the replacement out but warns that the change set is + * incomplete, and the CLI describes it again without property values. The approval diff must show the + * replacement. + */ +integTest( + 'cdk deploy --require-approval any-change shows a replacement CloudFormation drops with a warning', + withSpecificFixture('json-intrinsic-app', async (fixture) => { + // GIVEN - a deployed task definition whose environment holds {"stack":"","a":"1"} + await fixture.cdkDeploy('json-intrinsic-replace'); + + // WHEN - a key is added to the JSON, and the deploy asks for approval (auto-confirmed) + const output = await fixture.cdkDeploy('json-intrinsic-replace', { + options: ['--require-approval=any-change', '--yes'], + neverRequireApproval: false, + modEnv: { VARIANT: 'add-key' }, + }); + + // THEN - the approval diff shows the task definition being replaced + expect(output).toContain('Do you wish to deploy these changes'); + expect(output).toMatch(/\[~\].*AWS::ECS::TaskDefinition.*replace/); + }), +); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts new file mode 100644 index 000000000..e9054e2a5 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts @@ -0,0 +1,26 @@ +import { integTest, withSpecificFixture } from '../../../lib'; + +/** + * Guard for https://github.com/aws/aws-cdk-cli/issues/2036 + * + * The task definition replacement CloudFormation leaves out of a change set described with + * IncludePropertyValues (with a warning) is destructive, so --require-approval destructive must ask. + */ +integTest( + 'cdk deploy --require-approval destructive prompts for a replacement CloudFormation drops', + withSpecificFixture('json-intrinsic-app', async (fixture) => { + // GIVEN - a deployed task definition whose environment holds {"stack":"","a":"1"} + await fixture.cdkDeploy('json-intrinsic-replace'); + + // WHEN - a key is added to the JSON, and the deploy asks for approval of destructive changes (auto-confirmed) + const output = await fixture.cdkDeploy('json-intrinsic-replace', { + options: ['--require-approval=destructive', '--yes'], + neverRequireApproval: false, + modEnv: { VARIANT: 'add-key' }, + }); + + // THEN - the replacement is listed as destructive + expect(output).toContain('Do you wish to deploy these changes'); + expect(output).toMatch(/AWS::ECS::TaskDefinition.*will be replaced/); + }), +); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---revert-drift-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---revert-drift-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts new file mode 100644 index 000000000..3c0b617af --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---revert-drift-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts @@ -0,0 +1,27 @@ +import { integTest, withSpecificFixture } from '../../../lib'; + +/** + * Guard for https://github.com/aws/aws-cdk-cli/issues/2036 + * + * A drift-aware change set (--revert-drift) keeps a key added to JSON built with an intrinsic, even when + * described with IncludePropertyValues. The approval diff must show it. + */ +integTest( + 'cdk deploy --revert-drift shows a key added to JSON built with an intrinsic', + withSpecificFixture('json-intrinsic-app', async (fixture) => { + // GIVEN - a deployed parameter holding {"stack":"","a":"1"} + await fixture.cdkDeploy('json-intrinsic'); + + // WHEN - a key is added, and a drift-aware deploy asks for approval (auto-confirmed) + const output = await fixture.cdkDeploy('json-intrinsic', { + options: ['--revert-drift', '--require-approval=any-change', '--yes'], + neverRequireApproval: false, + modEnv: { VARIANT: 'add-key' }, + }); + + // THEN - the approval diff shows the parameter value as changed + expect(output).toContain('Do you wish to deploy these changes'); + expect(output).toMatch(/\[~\].*AWS::SSM::Parameter(?!::)/); + expect(output).toMatch(/\[~\].*Value/); + }), +); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts new file mode 100644 index 000000000..ed2df890b --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts @@ -0,0 +1,27 @@ +import { integTest, withSpecificFixture } from '../../../lib'; +import '../../../lib/assertions'; + +/** + * Guard for https://github.com/aws/aws-cdk-cli/issues/2036 + * + * The CLI diffs nested stacks from their templates, not their change sets, so a key added to JSON built + * with an intrinsic in a nested stack is shown even though CloudFormation leaves it out of the nested + * change set described with IncludePropertyValues. + */ +integTest( + 'cdk diff --method=change-set shows a key added to JSON in a nested stack', + withSpecificFixture('json-intrinsic-app', async (fixture) => { + const stackName = fixture.fullStackName('json-intrinsic-nested'); + + // GIVEN - a deployed nested parameter holding {"stack":"","a":"1"} + await fixture.cdkDeploy('json-intrinsic-nested'); + + // WHEN - a key is added to the JSON + const diff = await fixture.cdk(['diff', '--method=change-set', stackName], { + modEnv: { VARIANT: 'add-key' }, + }); + + // THEN - the nested parameter value is shown as changed + expect(diff).toHaveNextLineMatching(/\[~\].*AWS::SSM::Parameter(?!::)/, /\[~\].*Value/); + }), +); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-deploy-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-deploy-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts new file mode 100644 index 000000000..ad1187fa0 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-deploy-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts @@ -0,0 +1,39 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import * as toolkit from '@aws-cdk/toolkit-lib'; +import { approvalRecordingIoHost, assemblyFromCdkAppDir, hasParameterValueChange, toolkitFromFixture } from './toolkit-helpers'; +import { integTest, withSpecificFixture } from '../../lib'; + +/** + * Regression test for the deploy side of https://github.com/aws/aws-cdk-cli/issues/2036 + * + * Adding a key to JSON built with an intrinsic is a change CloudFormation leaves out of a change set + * described with IncludePropertyValues, without a warning. The diff that toolkit-lib sends with the + * deploy approval request must contain it anyway. + */ +integTest( + 'toolkit deploy approval shows a key added to JSON built with an intrinsic', + withSpecificFixture('json-intrinsic-app', async (fixture) => { + const stacks: toolkit.StackSelector = { + strategy: toolkit.StackSelectionStrategy.PATTERN_MUST_MATCH_SINGLE, + patterns: [fixture.fullStackName('json-intrinsic')], + }; + + const approving = toolkitFromFixture(fixture, { ioHost: approvalRecordingIoHost(true).ioHost }); + + // GIVEN - a deployed parameter holding {"stack":"","a":"1"} + await approving.deploy(await assemblyFromCdkAppDir(approving, fixture), { stacks }); + + // WHEN - a key is added, and the deploy approval is declined + const { ioHost, approvals } = approvalRecordingIoHost(false); + const tk = toolkitFromFixture(fixture, { ioHost }); + await expect(tk.deploy(await assemblyFromCdkAppDir(tk, fixture, { VARIANT: 'add-key' }), { + stacks, + deploymentMethod: { method: 'change-set' }, + })).rejects.toThrow(); + + // THEN - the diff sent with the approval request shows the parameter value as changed + expect(approvals).toHaveLength(1); + const diffs = Object.values(approvals[0].data.templateDiffs); + expect(diffs.some(hasParameterValueChange)).toBe(true); + }), +); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-changed-value-in-json-in-a-nested-stack.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-changed-value-in-json-in-a-nested-stack.integtest.ts new file mode 100644 index 000000000..2dc018d6d --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-changed-value-in-json-in-a-nested-stack.integtest.ts @@ -0,0 +1,34 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import * as toolkit from '@aws-cdk/toolkit-lib'; +import { approvalRecordingIoHost, assemblyFromCdkAppDir, hasParameterValueChange, toolkitFromFixture } from './toolkit-helpers'; +import { integTest, withSpecificFixture } from '../../lib'; + +/** + * Companion to the nested regression test for https://github.com/aws/aws-cdk-cli/issues/2036 + * + * Changing an existing value in JSON built with an intrinsic is a change CloudFormation lists even in a + * change set described with IncludePropertyValues. The nested diff shows it with or without the recovery + * for changes CloudFormation leaves out. + */ +integTest( + 'toolkit diff --method=change-set shows a changed value in JSON in a nested stack', + withSpecificFixture('json-intrinsic-app', async (fixture) => { + const stacks: toolkit.StackSelector = { + strategy: toolkit.StackSelectionStrategy.PATTERN_MUST_MATCH_SINGLE, + patterns: [fixture.fullStackName('json-intrinsic-nested')], + }; + const tk = toolkitFromFixture(fixture, { ioHost: approvalRecordingIoHost(true).ioHost }); + + // GIVEN - a deployed nested parameter holding {"stack":"","a":"1"} + await tk.deploy(await assemblyFromCdkAppDir(tk, fixture), { stacks }); + + // WHEN - an existing value changes + const result = await tk.diff(await assemblyFromCdkAppDir(tk, fixture, { VARIANT: 'change-value' }), { + stacks, + method: toolkit.DiffMethod.ChangeSet({ fallbackToTemplate: false }), + }); + + // THEN - the nested parameter value is shown as changed + expect(Object.values(result).some(hasParameterValueChange)).toBe(true); + }), +); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts new file mode 100644 index 000000000..e5d4e5752 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts @@ -0,0 +1,34 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import * as toolkit from '@aws-cdk/toolkit-lib'; +import { approvalRecordingIoHost, assemblyFromCdkAppDir, hasParameterValueChange, toolkitFromFixture } from './toolkit-helpers'; +import { integTest, withSpecificFixture } from '../../lib'; + +/** + * Regression test for https://github.com/aws/aws-cdk-cli/issues/2036 + * + * Adding a key to JSON built with an intrinsic is a change CloudFormation leaves out of a change set + * described with IncludePropertyValues, without a warning. toolkit-lib describes nested change sets too, + * and must show the change for a parameter in a nested stack. + */ +integTest( + 'toolkit diff --method=change-set shows a key added to JSON in a nested stack', + withSpecificFixture('json-intrinsic-app', async (fixture) => { + const stacks: toolkit.StackSelector = { + strategy: toolkit.StackSelectionStrategy.PATTERN_MUST_MATCH_SINGLE, + patterns: [fixture.fullStackName('json-intrinsic-nested')], + }; + const tk = toolkitFromFixture(fixture, { ioHost: approvalRecordingIoHost(true).ioHost }); + + // GIVEN - a deployed nested parameter holding {"stack":"","a":"1"} + await tk.deploy(await assemblyFromCdkAppDir(tk, fixture), { stacks }); + + // WHEN - a key is added to the JSON + const result = await tk.diff(await assemblyFromCdkAppDir(tk, fixture, { VARIANT: 'add-key' }), { + stacks, + method: toolkit.DiffMethod.ChangeSet({ fallbackToTemplate: false }), + }); + + // THEN - the nested parameter value is shown as changed + expect(Object.values(result).some(hasParameterValueChange)).toBe(true); + }), +); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-execute-change-set-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-execute-change-set-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts new file mode 100644 index 000000000..8ccada3e7 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-execute-change-set-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts @@ -0,0 +1,43 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import * as toolkit from '@aws-cdk/toolkit-lib'; +import { approvalRecordingIoHost, assemblyFromCdkAppDir, hasParameterValueChange, toolkitFromFixture } from './toolkit-helpers'; +import { integTest, withSpecificFixture } from '../../lib'; + +/** + * Regression test for the deploy side of https://github.com/aws/aws-cdk-cli/issues/2036 + * + * Executing an existing change set asks for approval with a diff of that change set. Adding a key to + * JSON built with an intrinsic is left out of the change set described with IncludePropertyValues, + * without a warning. The diff sent with the approval request must contain it anyway. + */ +integTest( + 'toolkit execute-change-set approval shows a key added to JSON built with an intrinsic', + withSpecificFixture('json-intrinsic-app', async (fixture) => { + const stacks: toolkit.StackSelector = { + strategy: toolkit.StackSelectionStrategy.PATTERN_MUST_MATCH_SINGLE, + patterns: [fixture.fullStackName('json-intrinsic')], + }; + const changeSetName = 'json-intrinsic-add-key'; + const approving = toolkitFromFixture(fixture, { ioHost: approvalRecordingIoHost(true).ioHost }); + + // GIVEN - a deployed parameter holding {"stack":"","a":"1"}, and a change set that adds a key + await approving.deploy(await assemblyFromCdkAppDir(approving, fixture), { stacks }); + await approving.deploy(await assemblyFromCdkAppDir(approving, fixture, { VARIANT: 'add-key' }), { + stacks, + deploymentMethod: { method: 'change-set', execute: false, changeSetName }, + }); + + // WHEN - the change set is executed, and the approval is declined + const { ioHost, approvals } = approvalRecordingIoHost(false); + const tk = toolkitFromFixture(fixture, { ioHost }); + await expect(tk.deploy(await assemblyFromCdkAppDir(tk, fixture, { VARIANT: 'add-key' }), { + stacks, + deploymentMethod: { method: 'execute-change-set', changeSetName }, + })).rejects.toThrow(); + + // THEN - the diff sent with the approval request shows the parameter value as changed + expect(approvals).toHaveLength(1); + const diffs = Object.values(approvals[0].data.templateDiffs); + expect(diffs.some(hasParameterValueChange)).toBe(true); + }), +); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-helpers.ts b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-helpers.ts index ea28cfefc..fd22bffd9 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-helpers.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-helpers.ts @@ -29,7 +29,7 @@ export function toolkitFromFixture(fixture: AwsContext, options?: Omit = {}) { const cdkAppDir = fixture.integTestDir; const cdkJson = await JSON.parse(await fs.readFile(path.join(cdkAppDir, 'cdk.json'), 'utf-8')); const app = cdkJson.app; @@ -39,6 +39,40 @@ export async function assemblyFromCdkAppDir(tk: toolkit.Toolkit, fixture: TestFi env: { STACK_NAME_PREFIX: fixture.stackNamePrefix, PACKAGE_LAYOUT_VERSION: '2', + ...env, }, }); } + +/** + * An IoHost that answers deploy approval requests with `approve`, gives every other request its + * default response, and records the approval requests it was asked + */ +export function approvalRecordingIoHost(approve: boolean) { + const approvals: toolkit.IoRequest[] = []; + const ioHost: toolkit.IIoHost = { + notify: async () => { + }, + requestResponse: async (msg: toolkit.IoRequest): Promise => { + if (msg.code !== 'CDK_TOOLKIT_I5060') { + return msg.defaultResponse; + } + approvals.push(msg); + return approve as unknown as T; + }, + }; + return { ioHost, approvals }; +} + +/** + * Whether a template diff contains a change to the `Value` of an SSM parameter + */ +export function hasParameterValueChange(diff: any): boolean { + let found = false; + diff.resources.forEachDifference((_logicalId: string, change: any) => { + if (change.resourceType === 'AWS::SSM::Parameter' && change.propertyUpdates.Value) { + found = true; + } + }); + return found; +} From a6e64af0a504c9fc31212259a3203fa26189d928 Mon Sep 17 00:00:00 2001 From: 9pace Date: Wed, 21 Oct 2026 15:04:47 +0000 Subject: [PATCH 05/16] test: name the integ app's switches after what they change, and document them --- .../cdk-apps/json-intrinsic-app/app.js | 56 +++++++++++++------ ...an-intrinsic-then-executes-it.integtest.ts | 6 +- ...ormation-drops-with-a-warning.integtest.ts | 2 +- ...-json-built-with-an-intrinsic.integtest.ts | 2 +- ...-json-built-with-an-intrinsic.integtest.ts | 2 +- ...ormation-drops-with-a-warning.integtest.ts | 2 +- ...lacement-cloudformation-drops.integtest.ts | 2 +- ...-json-built-with-an-intrinsic.integtest.ts | 2 +- ...n-rewritten-to-the-same-value.integtest.ts | 2 +- ...-json-built-with-an-intrinsic.integtest.ts | 2 +- ...when-another-property-changes.integtest.ts | 2 +- ...ded-to-json-in-a-nested-stack.integtest.ts | 2 +- ...-json-built-with-an-intrinsic.integtest.ts | 2 +- ...-json-built-with-an-intrinsic.integtest.ts | 2 +- ...-json-built-with-an-intrinsic.integtest.ts | 2 +- ...-json-built-with-an-intrinsic.integtest.ts | 2 +- ...lue-in-json-in-a-nested-stack.integtest.ts | 2 +- ...ded-to-json-in-a-nested-stack.integtest.ts | 2 +- ...-json-built-with-an-intrinsic.integtest.ts | 4 +- 19 files changed, 60 insertions(+), 38 deletions(-) diff --git a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js index 03ab62c42..5816549d3 100644 --- a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js +++ b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js @@ -7,24 +7,48 @@ if (!stackPrefix) { throw new Error(`the STACK_NAME_PREFIX environment variable is required`); } -// A string parameter holding JSON that is built with an intrinsic, the way Stack.toJsonString renders -// it (the EKS KubernetesManifest and HelmChart constructs use it too). VARIANT changes the JSON in a -// way that CloudFormation leaves out of a change set described with IncludePropertyValues, without a -// warning (on its own, or together with the description), changes a value (which CloudFormation does -// list), or rewrites it so that it resolves to the same value. See aws/aws-cdk-cli#2036. +/** + * Holds JSON built with an intrinsic, the way Stack.toJsonString renders it (the EKS KubernetesManifest + * and HelmChart constructs use it too), in three places. See aws/aws-cdk-cli#2036. + * + * - `-json-intrinsic`: an SSM string parameter + * - `-json-intrinsic-replace`: a container environment variable of an ECS task definition. Any + * change to it replaces the task definition. + * - `-json-intrinsic-nested`: an SSM string parameter in a nested stack + * + * The JSON is `{"stack":"","a":"1"}`, changed by JSON_CHANGE: + * + * - (unset): no change + * - `change-value`: `a` becomes "2" (CloudFormation lists this change) + * - `add-key`: adds `"b":"2"` + * - `add-key-with-token`: adds `"region":""` + * - `reorder-keys`: puts `a` before `stack` + * - `same-value-with-fn-sub`: the same JSON, built with Fn::Sub instead of Fn::Join + * + * Described with IncludePropertyValues, CloudFormation leaves `add-key`, `add-key-with-token` and + * `reorder-keys` out of the change set, without a warning (with a warning when the change is the task + * definition replacement). + * + * DESCRIPTION sets the description of the SSM parameter in `-json-intrinsic` (default "d1"). + * + * To exercise this app: + * + * ``` + * npx cdk deploy -json-intrinsic + * env JSON_CHANGE=add-key npx cdk diff --method=change-set -json-intrinsic + * env JSON_CHANGE=add-key DESCRIPTION=d2 npx cdk deploy --require-approval=any-change -json-intrinsic + * ``` + */ class JsonIntrinsicStack extends cdk.Stack { constructor(scope, id, props) { super(scope, id, props); new ssm.StringParameter(this, 'Param', { - stringValue: jsonValue(this, process.env.VARIANT), - description: process.env.VARIANT === 'add-key-and-description' ? 'd2' : 'd1', + stringValue: jsonValue(this, process.env.JSON_CHANGE), + description: process.env.DESCRIPTION ?? 'd1', }); } } -// The same value in a container environment variable of an ECS task definition. Any change to the -// container definitions replaces the task definition. CloudFormation leaves that replacement out of a -// change set described with IncludePropertyValues too, but says so in a warning. class JsonIntrinsicReplaceStack extends cdk.Stack { constructor(scope, id, props) { super(scope, id, props); @@ -35,28 +59,26 @@ class JsonIntrinsicReplaceStack extends cdk.Stack { }); taskDefinition.addContainer('Container', { image: ecs.ContainerImage.fromRegistry('public.ecr.aws/docker/library/busybox:latest'), - environment: { CONFIG: jsonValue(this, process.env.VARIANT) }, + environment: { CONFIG: jsonValue(this, process.env.JSON_CHANGE) }, }); } } -// The same parameter in a nested stack. class JsonIntrinsicNestedStack extends cdk.Stack { constructor(scope, id, props) { super(scope, id, props); const nested = new cdk.NestedStack(this, 'Nested'); - new ssm.StringParameter(nested, 'Param', { stringValue: jsonValue(nested, process.env.VARIANT) }); + new ssm.StringParameter(nested, 'Param', { stringValue: jsonValue(nested, process.env.JSON_CHANGE) }); } } -function jsonValue(stack, variant) { - switch (variant) { +function jsonValue(stack, change) { + switch (change) { case undefined: return stack.toJsonString({ stack: cdk.Aws.STACK_NAME, a: '1' }); case 'change-value': return stack.toJsonString({ stack: cdk.Aws.STACK_NAME, a: '2' }); case 'add-key': - case 'add-key-and-description': return stack.toJsonString({ stack: cdk.Aws.STACK_NAME, a: '1', b: '2' }); case 'add-key-with-token': return stack.toJsonString({ stack: cdk.Aws.STACK_NAME, a: '1', region: cdk.Aws.REGION }); @@ -65,7 +87,7 @@ function jsonValue(stack, variant) { case 'same-value-with-fn-sub': return cdk.Fn.sub('{"stack":"${AWS::StackName}","a":"1"}'); default: - throw new Error(`unknown VARIANT: ${variant}`); + throw new Error(`unknown JSON_CHANGE: ${change}`); } } diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-execute-shows-a-key-added-to-json-built-with-an-intrinsic-then-executes-it.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-execute-shows-a-key-added-to-json-built-with-an-intrinsic-then-executes-it.integtest.ts index a2f85defa..c544b9687 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-execute-shows-a-key-added-to-json-built-with-an-intrinsic-then-executes-it.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-execute-shows-a-key-added-to-json-built-with-an-intrinsic-then-executes-it.integtest.ts @@ -20,7 +20,7 @@ integTest( const prepare = await fixture.cdkDeploy('json-intrinsic', { options: ['--no-execute', `--change-set-name=${changeSetName}`, '--require-approval=any-change', '--yes'], neverRequireApproval: false, - modEnv: { VARIANT: 'add-key' }, + modEnv: { JSON_CHANGE: 'add-key' }, }); // THEN - the approval diff shows the parameter value as changed @@ -30,11 +30,11 @@ integTest( // WHEN - the change set is executed await fixture.cdkDeploy('json-intrinsic', { options: ['--method=execute-change-set', `--change-set-name=${changeSetName}`], - modEnv: { VARIANT: 'add-key' }, + modEnv: { JSON_CHANGE: 'add-key' }, }); // THEN - the deployed template has the key: a template diff finds nothing left to change - const diff = await fixture.cdk(['diff', '--method=template', stackName], { modEnv: { VARIANT: 'add-key' } }); + const diff = await fixture.cdk(['diff', '--method=template', stackName], { modEnv: { JSON_CHANGE: 'add-key' } }); expect(diff).toContain('There were no differences'); }), ); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts index dbf69fbfa..a2ede1c8a 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts @@ -17,7 +17,7 @@ integTest( // with rollback enabled is auto-confirmed) const output = await fixture.cdkDeploy('json-intrinsic-replace', { options: ['--no-rollback', '--yes'], - modEnv: { VARIANT: 'add-key' }, + modEnv: { JSON_CHANGE: 'add-key' }, }); // THEN - CDK refuses to replace with rollback disabled diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts index 63140dd8a..14041a4cd 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts @@ -17,7 +17,7 @@ integTest( const output = await fixture.cdkDeploy('json-intrinsic', { options: ['--require-approval=any-change', '--yes'], neverRequireApproval: false, - modEnv: { VARIANT: 'change-value' }, + modEnv: { JSON_CHANGE: 'change-value' }, }); // THEN - the approval diff shows the parameter value as changed diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts index 7961debd9..1491f9eab 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts @@ -20,7 +20,7 @@ integTest( const output = await fixture.cdkDeploy('json-intrinsic', { options: ['--require-approval=any-change', '--yes'], neverRequireApproval: false, - modEnv: { VARIANT: 'add-key' }, + modEnv: { JSON_CHANGE: 'add-key' }, }); // THEN - the approval diff shows the parameter value as changed diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-replacement-cloudformation-drops-with-a-warning.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-replacement-cloudformation-drops-with-a-warning.integtest.ts index 99ceda55e..dac8bef55 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-replacement-cloudformation-drops-with-a-warning.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-replacement-cloudformation-drops-with-a-warning.integtest.ts @@ -18,7 +18,7 @@ integTest( const output = await fixture.cdkDeploy('json-intrinsic-replace', { options: ['--require-approval=any-change', '--yes'], neverRequireApproval: false, - modEnv: { VARIANT: 'add-key' }, + modEnv: { JSON_CHANGE: 'add-key' }, }); // THEN - the approval diff shows the task definition being replaced diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts index e9054e2a5..d1dcab793 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts @@ -16,7 +16,7 @@ integTest( const output = await fixture.cdkDeploy('json-intrinsic-replace', { options: ['--require-approval=destructive', '--yes'], neverRequireApproval: false, - modEnv: { VARIANT: 'add-key' }, + modEnv: { JSON_CHANGE: 'add-key' }, }); // THEN - the replacement is listed as destructive diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---revert-drift-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---revert-drift-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts index 3c0b617af..13e6ac431 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---revert-drift-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---revert-drift-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts @@ -16,7 +16,7 @@ integTest( const output = await fixture.cdkDeploy('json-intrinsic', { options: ['--revert-drift', '--require-approval=any-change', '--yes'], neverRequireApproval: false, - modEnv: { VARIANT: 'add-key' }, + modEnv: { JSON_CHANGE: 'add-key' }, }); // THEN - the approval diff shows the parameter value as changed diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-does-not-show-json-rewritten-to-the-same-value.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-does-not-show-json-rewritten-to-the-same-value.integtest.ts index c067e60f9..d62502f5c 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-does-not-show-json-rewritten-to-the-same-value.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-does-not-show-json-rewritten-to-the-same-value.integtest.ts @@ -17,7 +17,7 @@ integTest( // WHEN - the same JSON is built with Fn::Sub const diff = await fixture.cdk(['diff', '--method=change-set', stackName], { - modEnv: { VARIANT: 'same-value-with-fn-sub' }, + modEnv: { JSON_CHANGE: 'same-value-with-fn-sub' }, }); // THEN - nothing is shown diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts index b7c11a86c..b6d93a756 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts @@ -18,7 +18,7 @@ integTest( // WHEN - an existing value in the JSON changes const diff = await fixture.cdk(['diff', '--method=change-set', stackName], { - modEnv: { VARIANT: 'change-value' }, + modEnv: { JSON_CHANGE: 'change-value' }, }); // THEN - the parameter value is shown as changed diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-built-with-an-intrinsic-when-another-property-changes.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-built-with-an-intrinsic-when-another-property-changes.integtest.ts index 1b0059fdd..265153bd6 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-built-with-an-intrinsic-when-another-property-changes.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-built-with-an-intrinsic-when-another-property-changes.integtest.ts @@ -21,7 +21,7 @@ integTest( // WHEN - a key is added to the JSON, and the description changes const diff = await fixture.cdk(['diff', '--method=change-set', stackName], { - modEnv: { VARIANT: 'add-key-and-description' }, + modEnv: { JSON_CHANGE: 'add-key', DESCRIPTION: 'd2' }, }); // THEN - both property changes are shown diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts index ed2df890b..c60937f9f 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts @@ -18,7 +18,7 @@ integTest( // WHEN - a key is added to the JSON const diff = await fixture.cdk(['diff', '--method=change-set', stackName], { - modEnv: { VARIANT: 'add-key' }, + modEnv: { JSON_CHANGE: 'add-key' }, }); // THEN - the nested parameter value is shown as changed diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-with-a-token-value-added-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-with-a-token-value-added-to-json-built-with-an-intrinsic.integtest.ts index bf77eb0f4..f34b4cb64 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-with-a-token-value-added-to-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-with-a-token-value-added-to-json-built-with-an-intrinsic.integtest.ts @@ -17,7 +17,7 @@ integTest( // WHEN - a key whose value is the region is added to the JSON const diff = await fixture.cdk(['diff', '--method=change-set', stackName], { - modEnv: { VARIANT: 'add-key-with-token' }, + modEnv: { JSON_CHANGE: 'add-key-with-token' }, }); // THEN - the parameter value is shown as changed diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-changes-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-changes-to-json-built-with-an-intrinsic.integtest.ts index 6d2bf226e..6f0f89031 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-changes-to-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-changes-to-json-built-with-an-intrinsic.integtest.ts @@ -21,7 +21,7 @@ integTest( // WHEN - a key is added to the JSON const diff = await fixture.cdk(['diff', '--method=change-set', stackName], { - modEnv: { VARIANT: 'add-key' }, + modEnv: { JSON_CHANGE: 'add-key' }, }); // THEN - the parameter value is shown as changed diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-reordered-keys-in-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-reordered-keys-in-json-built-with-an-intrinsic.integtest.ts index 7a0233481..88c53e35d 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-reordered-keys-in-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-reordered-keys-in-json-built-with-an-intrinsic.integtest.ts @@ -17,7 +17,7 @@ integTest( // WHEN - the keys of the JSON are reordered const diff = await fixture.cdk(['diff', '--method=change-set', stackName], { - modEnv: { VARIANT: 'reorder-keys' }, + modEnv: { JSON_CHANGE: 'reorder-keys' }, }); // THEN - the parameter value is shown as changed diff --git a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-deploy-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-deploy-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts index ad1187fa0..c4a0855d3 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-deploy-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-deploy-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts @@ -26,7 +26,7 @@ integTest( // WHEN - a key is added, and the deploy approval is declined const { ioHost, approvals } = approvalRecordingIoHost(false); const tk = toolkitFromFixture(fixture, { ioHost }); - await expect(tk.deploy(await assemblyFromCdkAppDir(tk, fixture, { VARIANT: 'add-key' }), { + await expect(tk.deploy(await assemblyFromCdkAppDir(tk, fixture, { JSON_CHANGE: 'add-key' }), { stacks, deploymentMethod: { method: 'change-set' }, })).rejects.toThrow(); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-changed-value-in-json-in-a-nested-stack.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-changed-value-in-json-in-a-nested-stack.integtest.ts index 2dc018d6d..02364446e 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-changed-value-in-json-in-a-nested-stack.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-changed-value-in-json-in-a-nested-stack.integtest.ts @@ -23,7 +23,7 @@ integTest( await tk.deploy(await assemblyFromCdkAppDir(tk, fixture), { stacks }); // WHEN - an existing value changes - const result = await tk.diff(await assemblyFromCdkAppDir(tk, fixture, { VARIANT: 'change-value' }), { + const result = await tk.diff(await assemblyFromCdkAppDir(tk, fixture, { JSON_CHANGE: 'change-value' }), { stacks, method: toolkit.DiffMethod.ChangeSet({ fallbackToTemplate: false }), }); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts index e5d4e5752..90579e652 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts @@ -23,7 +23,7 @@ integTest( await tk.deploy(await assemblyFromCdkAppDir(tk, fixture), { stacks }); // WHEN - a key is added to the JSON - const result = await tk.diff(await assemblyFromCdkAppDir(tk, fixture, { VARIANT: 'add-key' }), { + const result = await tk.diff(await assemblyFromCdkAppDir(tk, fixture, { JSON_CHANGE: 'add-key' }), { stacks, method: toolkit.DiffMethod.ChangeSet({ fallbackToTemplate: false }), }); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-execute-change-set-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-execute-change-set-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts index 8ccada3e7..bfefe025a 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-execute-change-set-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-execute-change-set-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts @@ -22,7 +22,7 @@ integTest( // GIVEN - a deployed parameter holding {"stack":"","a":"1"}, and a change set that adds a key await approving.deploy(await assemblyFromCdkAppDir(approving, fixture), { stacks }); - await approving.deploy(await assemblyFromCdkAppDir(approving, fixture, { VARIANT: 'add-key' }), { + await approving.deploy(await assemblyFromCdkAppDir(approving, fixture, { JSON_CHANGE: 'add-key' }), { stacks, deploymentMethod: { method: 'change-set', execute: false, changeSetName }, }); @@ -30,7 +30,7 @@ integTest( // WHEN - the change set is executed, and the approval is declined const { ioHost, approvals } = approvalRecordingIoHost(false); const tk = toolkitFromFixture(fixture, { ioHost }); - await expect(tk.deploy(await assemblyFromCdkAppDir(tk, fixture, { VARIANT: 'add-key' }), { + await expect(tk.deploy(await assemblyFromCdkAppDir(tk, fixture, { JSON_CHANGE: 'add-key' }), { stacks, deploymentMethod: { method: 'execute-change-set', changeSetName }, })).rejects.toThrow(); From a93a2dcff621d15c3a5441d03e35680274a1da9b Mon Sep 17 00:00:00 2001 From: 9pace Date: Wed, 21 Oct 2026 15:11:47 +0000 Subject: [PATCH 06/16] test: show each JSON_CHANGE as the JSON it produces, and link the issue --- .../cdk-apps/json-intrinsic-app/app.js | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js index 5816549d3..1cbf1ae8c 100644 --- a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js +++ b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js @@ -8,26 +8,27 @@ if (!stackPrefix) { } /** - * Holds JSON built with an intrinsic, the way Stack.toJsonString renders it (the EKS KubernetesManifest - * and HelmChart constructs use it too), in three places. See aws/aws-cdk-cli#2036. + * Holds JSON built with an intrinsic, the way Stack.toJsonString renders it, in three places: * * - `-json-intrinsic`: an SSM string parameter * - `-json-intrinsic-replace`: a container environment variable of an ECS task definition. Any * change to it replaces the task definition. * - `-json-intrinsic-nested`: an SSM string parameter in a nested stack * - * The JSON is `{"stack":"","a":"1"}`, changed by JSON_CHANGE: + * Each test deploys the JSON as it is without JSON_CHANGE, then sets JSON_CHANGE to change it: * - * - (unset): no change - * - `change-value`: `a` becomes "2" (CloudFormation lists this change) - * - `add-key`: adds `"b":"2"` - * - `add-key-with-token`: adds `"region":""` - * - `reorder-keys`: puts `a` before `stack` - * - `same-value-with-fn-sub`: the same JSON, built with Fn::Sub instead of Fn::Join + * ``` + * (unset, deployed) {"stack":"","a":"1"} + * change-value {"stack":"","a":"2"} + * add-key {"stack":"","a":"1","b":"2"} + * add-key-with-token {"stack":"","a":"1","region":""} + * reorder-keys {"a":"1","stack":""} + * same-value-with-fn-sub {"stack":"","a":"1"}, built with Fn::Sub instead of Fn::Join + * ``` * * Described with IncludePropertyValues, CloudFormation leaves `add-key`, `add-key-with-token` and - * `reorder-keys` out of the change set, without a warning (with a warning when the change is the task - * definition replacement). + * `reorder-keys` out of the change set, without a warning, or with one when the change is the task + * definition replacement (https://github.com/aws/aws-cdk-cli/issues/2036). * * DESCRIPTION sets the description of the SSM parameter in `-json-intrinsic` (default "d1"). * From dffcdf63a644c651c404926842be2e462826f339 Mon Sep 17 00:00:00 2001 From: 9pace Date: Wed, 21 Oct 2026 15:20:33 +0000 Subject: [PATCH 07/16] test: say the JSON is an Fn::Join --- .../cli-integ/resources/cdk-apps/json-intrinsic-app/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js index 1cbf1ae8c..87d650c5c 100644 --- a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js +++ b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js @@ -8,7 +8,7 @@ if (!stackPrefix) { } /** - * Holds JSON built with an intrinsic, the way Stack.toJsonString renders it, in three places: + * Holds JSON built with an intrinsic (an Fn::Join, from Stack.toJsonString) in three places: * * - `-json-intrinsic`: an SSM string parameter * - `-json-intrinsic-replace`: a container environment variable of an ECS task definition. Any From 796a070b6148f1371797950c3b4446d0e7915597 Mon Sep 17 00:00:00 2001 From: 9pace Date: Wed, 21 Oct 2026 15:21:39 +0000 Subject: [PATCH 08/16] test: list the nested stack second, and say why the task definition stack exists --- .../cli-integ/resources/cdk-apps/json-intrinsic-app/app.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js index 87d650c5c..7b72db128 100644 --- a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js +++ b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js @@ -11,9 +11,11 @@ if (!stackPrefix) { * Holds JSON built with an intrinsic (an Fn::Join, from Stack.toJsonString) in three places: * * - `-json-intrinsic`: an SSM string parameter - * - `-json-intrinsic-replace`: a container environment variable of an ECS task definition. Any - * change to it replaces the task definition. * - `-json-intrinsic-nested`: an SSM string parameter in a nested stack + * - `-json-intrinsic-replace`: a container environment variable of an ECS task definition. + * Changing a parameter's value updates it in place, but any change to a task definition replaces it. + * This stack is for a dropped change that is a replacement, which `--require-approval=destructive` + * and `--no-rollback` act on. * * Each test deploys the JSON as it is without JSON_CHANGE, then sets JSON_CHANGE to change it: * From 59a797e17a46f674248b4a31c00e1d5e928f6fee Mon Sep 17 00:00:00 2001 From: 9pace Date: Wed, 21 Oct 2026 15:22:34 +0000 Subject: [PATCH 09/16] test: drop the redundant deployed label --- .../cli-integ/resources/cdk-apps/json-intrinsic-app/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js index 7b72db128..3292d3d19 100644 --- a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js +++ b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js @@ -20,7 +20,7 @@ if (!stackPrefix) { * Each test deploys the JSON as it is without JSON_CHANGE, then sets JSON_CHANGE to change it: * * ``` - * (unset, deployed) {"stack":"","a":"1"} + * (unset) {"stack":"","a":"1"} * change-value {"stack":"","a":"2"} * add-key {"stack":"","a":"1","b":"2"} * add-key-with-token {"stack":"","a":"1","region":""} From 73ee59eecd0beb3b2293793ba393554db70f9025 Mon Sep 17 00:00:00 2001 From: 9pace Date: Wed, 21 Oct 2026 16:18:53 +0000 Subject: [PATCH 10/16] test: keep the integ tests that can fail on this change, and stop the --no-rollback test before it replaces anything --- ...ormation-drops-with-a-warning.integtest.ts | 18 +++++----- ...-json-built-with-an-intrinsic.integtest.ts | 28 --------------- ...ormation-drops-with-a-warning.integtest.ts | 28 --------------- ...lacement-cloudformation-drops.integtest.ts | 7 ++-- ...-json-built-with-an-intrinsic.integtest.ts | 27 --------------- ...ded-to-json-in-a-nested-stack.integtest.ts | 9 +++-- ...lue-in-json-in-a-nested-stack.integtest.ts | 34 ------------------- 7 files changed, 16 insertions(+), 135 deletions(-) delete mode 100644 packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts delete mode 100644 packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-replacement-cloudformation-drops-with-a-warning.integtest.ts delete mode 100644 packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---revert-drift-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts delete mode 100644 packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-changed-value-in-json-in-a-nested-stack.integtest.ts diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts index a2ede1c8a..5f5d2bcc2 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts @@ -1,11 +1,10 @@ import { integTest, withSpecificFixture } from '../../../lib'; /** - * Guard for https://github.com/aws/aws-cdk-cli/issues/2036 - * - * A replacement cannot be deployed with rollback disabled. The task definition replacement - * CloudFormation leaves out of a change set described with IncludePropertyValues (with a warning) must - * still be found when the change set is executed. + * A replacement cannot be deployed with rollback disabled, so CDK stops before executing the change set. + * Described with IncludePropertyValues, CloudFormation leaves the replacement of a task definition out + * of the change set, and warns that it did (https://github.com/aws/aws-cdk-cli/issues/2036). The + * description CDK checks before executing must still contain the replacement. */ integTest( 'cdk deploy --no-rollback stops for a replacement CloudFormation drops with a warning', @@ -13,14 +12,15 @@ integTest( // GIVEN - a deployed task definition whose environment holds {"stack":"","a":"1"} await fixture.cdkDeploy('json-intrinsic-replace'); - // WHEN - a key is added to the JSON and the deploy runs with rollback disabled (the offer to deploy - // with rollback enabled is auto-confirmed) + // WHEN - a key is added to the JSON and the deploy runs with rollback disabled. Without a terminal, + // CDK cannot ask to deploy with rollback enabled instead, so it fails and nothing is replaced. const output = await fixture.cdkDeploy('json-intrinsic-replace', { - options: ['--no-rollback', '--yes'], + options: ['--no-rollback'], modEnv: { JSON_CHANGE: 'add-key' }, + allowErrExit: true, }); - // THEN - CDK refuses to replace with rollback disabled + // THEN - CDK found the replacement and refused to deploy it with rollback disabled expect(output).toContain('Change includes a replacement which cannot be deployed while rollback is disabled'); }), ); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts deleted file mode 100644 index 14041a4cd..000000000 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { integTest, withSpecificFixture } from '../../../lib'; - -/** - * Companion to the deploy regression test for https://github.com/aws/aws-cdk-cli/issues/2036 - * - * Changing an existing value in JSON built with an intrinsic is a change CloudFormation lists even in a - * change set described with IncludePropertyValues. The approval diff shows it with or without the - * recovery for changes CloudFormation leaves out. - */ -integTest( - 'cdk deploy --require-approval any-change shows a changed value in JSON built with an intrinsic', - withSpecificFixture('json-intrinsic-app', async (fixture) => { - // GIVEN - a deployed parameter holding {"stack":"","a":"1"} - await fixture.cdkDeploy('json-intrinsic'); - - // WHEN - an existing value changes, and the deploy asks for approval (auto-confirmed) - const output = await fixture.cdkDeploy('json-intrinsic', { - options: ['--require-approval=any-change', '--yes'], - neverRequireApproval: false, - modEnv: { JSON_CHANGE: 'change-value' }, - }); - - // THEN - the approval diff shows the parameter value as changed - expect(output).toContain('Do you wish to deploy these changes'); - expect(output).toMatch(/\[~\].*AWS::SSM::Parameter(?!::)/); - expect(output).toMatch(/\[~\].*Value/); - }), -); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-replacement-cloudformation-drops-with-a-warning.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-replacement-cloudformation-drops-with-a-warning.integtest.ts deleted file mode 100644 index dac8bef55..000000000 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-replacement-cloudformation-drops-with-a-warning.integtest.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { integTest, withSpecificFixture } from '../../../lib'; - -/** - * Guard for https://github.com/aws/aws-cdk-cli/issues/2036 - * - * Adding a key to JSON in a container environment variable replaces the task definition. Described with - * IncludePropertyValues, CloudFormation leaves the replacement out but warns that the change set is - * incomplete, and the CLI describes it again without property values. The approval diff must show the - * replacement. - */ -integTest( - 'cdk deploy --require-approval any-change shows a replacement CloudFormation drops with a warning', - withSpecificFixture('json-intrinsic-app', async (fixture) => { - // GIVEN - a deployed task definition whose environment holds {"stack":"","a":"1"} - await fixture.cdkDeploy('json-intrinsic-replace'); - - // WHEN - a key is added to the JSON, and the deploy asks for approval (auto-confirmed) - const output = await fixture.cdkDeploy('json-intrinsic-replace', { - options: ['--require-approval=any-change', '--yes'], - neverRequireApproval: false, - modEnv: { JSON_CHANGE: 'add-key' }, - }); - - // THEN - the approval diff shows the task definition being replaced - expect(output).toContain('Do you wish to deploy these changes'); - expect(output).toMatch(/\[~\].*AWS::ECS::TaskDefinition.*replace/); - }), -); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts index d1dcab793..e52337b14 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts @@ -1,10 +1,9 @@ import { integTest, withSpecificFixture } from '../../../lib'; /** - * Guard for https://github.com/aws/aws-cdk-cli/issues/2036 - * - * The task definition replacement CloudFormation leaves out of a change set described with - * IncludePropertyValues (with a warning) is destructive, so --require-approval destructive must ask. + * Described with IncludePropertyValues, CloudFormation leaves the replacement of a task definition out + * of the change set, and warns that it did (https://github.com/aws/aws-cdk-cli/issues/2036). The + * replacement is destructive, so --require-approval destructive must still ask for it. */ integTest( 'cdk deploy --require-approval destructive prompts for a replacement CloudFormation drops', diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---revert-drift-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---revert-drift-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts deleted file mode 100644 index 13e6ac431..000000000 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---revert-drift-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { integTest, withSpecificFixture } from '../../../lib'; - -/** - * Guard for https://github.com/aws/aws-cdk-cli/issues/2036 - * - * A drift-aware change set (--revert-drift) keeps a key added to JSON built with an intrinsic, even when - * described with IncludePropertyValues. The approval diff must show it. - */ -integTest( - 'cdk deploy --revert-drift shows a key added to JSON built with an intrinsic', - withSpecificFixture('json-intrinsic-app', async (fixture) => { - // GIVEN - a deployed parameter holding {"stack":"","a":"1"} - await fixture.cdkDeploy('json-intrinsic'); - - // WHEN - a key is added, and a drift-aware deploy asks for approval (auto-confirmed) - const output = await fixture.cdkDeploy('json-intrinsic', { - options: ['--revert-drift', '--require-approval=any-change', '--yes'], - neverRequireApproval: false, - modEnv: { JSON_CHANGE: 'add-key' }, - }); - - // THEN - the approval diff shows the parameter value as changed - expect(output).toContain('Do you wish to deploy these changes'); - expect(output).toMatch(/\[~\].*AWS::SSM::Parameter(?!::)/); - expect(output).toMatch(/\[~\].*Value/); - }), -); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts index c60937f9f..1e2c05ad1 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts @@ -2,11 +2,10 @@ import { integTest, withSpecificFixture } from '../../../lib'; import '../../../lib/assertions'; /** - * Guard for https://github.com/aws/aws-cdk-cli/issues/2036 - * - * The CLI diffs nested stacks from their templates, not their change sets, so a key added to JSON built - * with an intrinsic in a nested stack is shown even though CloudFormation leaves it out of the nested - * change set described with IncludePropertyValues. + * The CLI diffs nested stacks from their templates, not their change sets. A key added to JSON built with + * an intrinsic in a nested stack is shown, even though CloudFormation leaves it out of the nested change + * set described with IncludePropertyValues (https://github.com/aws/aws-cdk-cli/issues/2036). If the CLI + * starts reading nested change sets, it must keep showing it. */ integTest( 'cdk diff --method=change-set shows a key added to JSON in a nested stack', diff --git a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-changed-value-in-json-in-a-nested-stack.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-changed-value-in-json-in-a-nested-stack.integtest.ts deleted file mode 100644 index 02364446e..000000000 --- a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-changed-value-in-json-in-a-nested-stack.integtest.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import * as toolkit from '@aws-cdk/toolkit-lib'; -import { approvalRecordingIoHost, assemblyFromCdkAppDir, hasParameterValueChange, toolkitFromFixture } from './toolkit-helpers'; -import { integTest, withSpecificFixture } from '../../lib'; - -/** - * Companion to the nested regression test for https://github.com/aws/aws-cdk-cli/issues/2036 - * - * Changing an existing value in JSON built with an intrinsic is a change CloudFormation lists even in a - * change set described with IncludePropertyValues. The nested diff shows it with or without the recovery - * for changes CloudFormation leaves out. - */ -integTest( - 'toolkit diff --method=change-set shows a changed value in JSON in a nested stack', - withSpecificFixture('json-intrinsic-app', async (fixture) => { - const stacks: toolkit.StackSelector = { - strategy: toolkit.StackSelectionStrategy.PATTERN_MUST_MATCH_SINGLE, - patterns: [fixture.fullStackName('json-intrinsic-nested')], - }; - const tk = toolkitFromFixture(fixture, { ioHost: approvalRecordingIoHost(true).ioHost }); - - // GIVEN - a deployed nested parameter holding {"stack":"","a":"1"} - await tk.deploy(await assemblyFromCdkAppDir(tk, fixture), { stacks }); - - // WHEN - an existing value changes - const result = await tk.diff(await assemblyFromCdkAppDir(tk, fixture, { JSON_CHANGE: 'change-value' }), { - stacks, - method: toolkit.DiffMethod.ChangeSet({ fallbackToTemplate: false }), - }); - - // THEN - the nested parameter value is shown as changed - expect(Object.values(result).some(hasParameterValueChange)).toBe(true); - }), -); From 12b3b92ff11532217e37b62a003cc646ab595219 Mon Sep 17 00:00:00 2001 From: 9pace Date: Wed, 21 Oct 2026 16:23:04 +0000 Subject: [PATCH 11/16] test: explain the replace stack and DESCRIPTION --- .../cli-integ/resources/cdk-apps/json-intrinsic-app/app.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js index 3292d3d19..d72629dab 100644 --- a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js +++ b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js @@ -14,10 +14,10 @@ if (!stackPrefix) { * - `-json-intrinsic-nested`: an SSM string parameter in a nested stack * - `-json-intrinsic-replace`: a container environment variable of an ECS task definition. * Changing a parameter's value updates it in place, but any change to a task definition replaces it. - * This stack is for a dropped change that is a replacement, which `--require-approval=destructive` - * and `--no-rollback` act on. + * This stack makes sure if a change is dropped, it won't affect the functionality of + * `--require-approval=destructive` and `--no-rollback`, which depend on replacement. * - * Each test deploys the JSON as it is without JSON_CHANGE, then sets JSON_CHANGE to change it: + * Each test deploys without JSON_CHANGE (unset), then sets JSON_CHANGE to switch on these changes: * * ``` * (unset) {"stack":"","a":"1"} @@ -33,6 +33,7 @@ if (!stackPrefix) { * definition replacement (https://github.com/aws/aws-cdk-cli/issues/2036). * * DESCRIPTION sets the description of the SSM parameter in `-json-intrinsic` (default "d1"). + * This allows a test to change a separate property of the Parameter, and ensure that change is kept. * * To exercise this app: * From a777653028bbac6c1c08f57efd0384e8480607fc Mon Sep 17 00:00:00 2001 From: 9pace Date: Wed, 21 Oct 2026 16:23:04 +0000 Subject: [PATCH 12/16] test: describe each integ test as its start, end, command and expected result --- ...-an-intrinsic-then-executes-it.integtest.ts | 18 ++++++++++++++---- ...formation-drops-with-a-warning.integtest.ts | 16 ++++++++++++---- ...o-json-built-with-an-intrinsic.integtest.ts | 17 +++++++++++------ ...placement-cloudformation-drops.integtest.ts | 13 ++++++++++--- ...on-rewritten-to-the-same-value.integtest.ts | 12 +++++++++--- ...n-json-built-with-an-intrinsic.integtest.ts | 13 +++++++++---- ...-when-another-property-changes.integtest.ts | 17 +++++++++++------ ...dded-to-json-in-a-nested-stack.integtest.ts | 15 +++++++++++---- ...o-json-built-with-an-intrinsic.integtest.ts | 14 +++++++++++--- ...o-json-built-with-an-intrinsic.integtest.ts | 16 ++++++++++------ ...n-json-built-with-an-intrinsic.integtest.ts | 15 ++++++++++++--- ...o-json-built-with-an-intrinsic.integtest.ts | 16 ++++++++++++---- ...dded-to-json-in-a-nested-stack.integtest.ts | 16 ++++++++++++---- ...o-json-built-with-an-intrinsic.integtest.ts | 18 ++++++++++++++---- 14 files changed, 158 insertions(+), 58 deletions(-) diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-execute-shows-a-key-added-to-json-built-with-an-intrinsic-then-executes-it.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-execute-shows-a-key-added-to-json-built-with-an-intrinsic-then-executes-it.integtest.ts index c544b9687..ae7861ed2 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-execute-shows-a-key-added-to-json-built-with-an-intrinsic-then-executes-it.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-execute-shows-a-key-added-to-json-built-with-an-intrinsic-then-executes-it.integtest.ts @@ -1,11 +1,21 @@ import { integTest, withSpecificFixture } from '../../../lib'; /** - * Regression test for the deploy side of https://github.com/aws/aws-cdk-cli/issues/2036 + * A key is added, a change set is created without executing it, and the change set is executed afterwards. * - * A change set created with --no-execute and --require-approval asks for approval with a diff. Adding a - * key to JSON built with an intrinsic is left out of the change set described with IncludePropertyValues, - * without a warning; the diff must show it anyway. Executing the change set afterwards applies the key. + * - Start: `{"stack":"","a":"1"}` + * - End: `{"stack":"","a":"1","b":"2"}` + * - Command: `cdk deploy --no-execute --change-set-name= --require-approval=any-change --yes`, then + * `cdk deploy --method=execute-change-set --change-set-name=` + * - Expected: the approval diff of the first command shows the parameter's `Value` as changed, and after + * executing the change set a template diff finds nothing left to change + * + * Described with IncludePropertyValues, CloudFormation leaves this change out of the change set, + * without a warning (https://github.com/aws/aws-cdk-cli/issues/2036). `--no-execute` reads the same + * change set, and executing it shows the deploy itself applies the key. + * + * If CloudFormation stops leaving the change out, this test still passes; the unit tests are the + * lasting coverage. */ integTest( 'cdk deploy --no-execute shows a key added to JSON built with an intrinsic, then executes it', diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts index 5f5d2bcc2..ccd4e187c 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts @@ -1,10 +1,18 @@ import { integTest, withSpecificFixture } from '../../../lib'; /** - * A replacement cannot be deployed with rollback disabled, so CDK stops before executing the change set. - * Described with IncludePropertyValues, CloudFormation leaves the replacement of a task definition out - * of the change set, and warns that it did (https://github.com/aws/aws-cdk-cli/issues/2036). The - * description CDK checks before executing must still contain the replacement. + * A key is added to JSON in a container environment variable of an ECS task definition, which replaces it, and + * the deploy runs with rollback disabled. + * + * - Start: `{"stack":"","a":"1"}` + * - End: `{"stack":"","a":"1","b":"2"}` + * - Command: `cdk deploy --no-rollback` + * - Expected: CDK refuses to deploy the replacement with rollback disabled. Without a terminal it cannot offer + * to deploy with rollback enabled instead, so it fails and nothing is replaced + * + * Described with IncludePropertyValues, CloudFormation leaves the replacement out of the change set, + * but warns that it did (https://github.com/aws/aws-cdk-cli/issues/2036). The check runs on the + * description CDK makes before executing the change set, not the one the approval uses. */ integTest( 'cdk deploy --no-rollback stops for a replacement CloudFormation drops with a warning', diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts index 1491f9eab..c122ac9e9 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts @@ -1,14 +1,19 @@ import { integTest, withSpecificFixture } from '../../../lib'; /** - * Regression test for the deploy side of https://github.com/aws/aws-cdk-cli/issues/2036 + * A key is added to JSON built with an intrinsic in an SSM parameter, and the deploy asks for approval. * - * Adding a key to JSON that is built with an intrinsic and stored in a string property is a change - * CloudFormation leaves out of a change set described with IncludePropertyValues, without a warning. - * The diff shown for --require-approval must show it anyway. + * - Start: `{"stack":"","a":"1"}` + * - End: `{"stack":"","a":"1","b":"2"}` + * - Command: `cdk deploy --require-approval=any-change --yes` + * - Expected: the approval diff shows the parameter's `Value` as changed * - * This only exercises the recovery for as long as CloudFormation keeps leaving the change out. If it - * stops, the test still passes; the toolkit-lib unit tests are the lasting coverage. + * Described with IncludePropertyValues, CloudFormation leaves this change out of the change set, + * without a warning (https://github.com/aws/aws-cdk-cli/issues/2036). The approval diff is built from + * that change set. + * + * If CloudFormation stops leaving the change out, this test still passes; the unit tests are the + * lasting coverage. */ integTest( 'cdk deploy --require-approval any-change shows a key added to JSON built with an intrinsic', diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts index e52337b14..a6290c899 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts @@ -1,9 +1,16 @@ import { integTest, withSpecificFixture } from '../../../lib'; /** - * Described with IncludePropertyValues, CloudFormation leaves the replacement of a task definition out - * of the change set, and warns that it did (https://github.com/aws/aws-cdk-cli/issues/2036). The - * replacement is destructive, so --require-approval destructive must still ask for it. + * A key is added to JSON in a container environment variable of an ECS task definition, which replaces it. + * + * - Start: `{"stack":"","a":"1"}` + * - End: `{"stack":"","a":"1","b":"2"}` + * - Command: `cdk deploy --require-approval=destructive --yes` + * - Expected: the deploy asks for approval and lists the task definition as replaced + * + * Described with IncludePropertyValues, CloudFormation leaves the replacement out of the change set, + * but warns that it did (https://github.com/aws/aws-cdk-cli/issues/2036). CDK then describes the + * change set again, so the replacement is found. */ integTest( 'cdk deploy --require-approval destructive prompts for a replacement CloudFormation drops', diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-does-not-show-json-rewritten-to-the-same-value.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-does-not-show-json-rewritten-to-the-same-value.integtest.ts index d62502f5c..212d74bc4 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-does-not-show-json-rewritten-to-the-same-value.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-does-not-show-json-rewritten-to-the-same-value.integtest.ts @@ -2,10 +2,16 @@ import { integTest, withSpecificFixture } from '../../../lib'; import '../../../lib/assertions'; /** - * Regression test for https://github.com/aws/aws-cdk-cli/issues/2036 + * JSON built with Fn::Join in an SSM parameter is rewritten with Fn::Sub, resolving to the same value. * - * The JSON is built with Fn::Sub instead of Fn::Join and resolves to the same value. The template - * text differs, so the changeset-based diff must keep trusting CloudFormation that nothing changes. + * - Start: `{"stack":"","a":"1"}`, built with Fn::Join + * - End: `{"stack":"","a":"1"}`, built with Fn::Sub + * - Command: `cdk diff --method=change-set` + * - Expected: "There were no differences" + * + * The template text differs, but nothing changes when it is deployed, and CloudFormation leaves it out + * of the change set either way. The recovery for changes CloudFormation leaves out + * (https://github.com/aws/aws-cdk-cli/issues/2036) must not show it. */ integTest( 'cdk diff --method=change-set does not show JSON rewritten to the same value', diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts index b6d93a756..8139c5932 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts @@ -2,11 +2,16 @@ import { integTest, withSpecificFixture } from '../../../lib'; import '../../../lib/assertions'; /** - * Companion to the regression tests for https://github.com/aws/aws-cdk-cli/issues/2036 + * An existing value changes in JSON built with an intrinsic in an SSM parameter. * - * Changing an existing value in JSON that is built with an intrinsic is a change CloudFormation lists - * even in a change set described with IncludePropertyValues. The changeset-based diff shows it with - * or without the recovery for changes CloudFormation leaves out. + * - Start: `{"stack":"","a":"1"}` + * - End: `{"stack":"","a":"2"}` + * - Command: `cdk diff --method=change-set` + * - Expected: the parameter's `Value` is shown as changed + * + * CloudFormation lists this change even when described with IncludePropertyValues. It makes sure the + * recovery for the changes it leaves out (https://github.com/aws/aws-cdk-cli/issues/2036) does not + * affect the ones it lists. */ integTest( 'cdk diff --method=change-set shows a changed value in JSON built with an intrinsic', diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-built-with-an-intrinsic-when-another-property-changes.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-built-with-an-intrinsic-when-another-property-changes.integtest.ts index 265153bd6..4af4a669f 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-built-with-an-intrinsic-when-another-property-changes.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-built-with-an-intrinsic-when-another-property-changes.integtest.ts @@ -2,14 +2,19 @@ import { integTest, withSpecificFixture } from '../../../lib'; import '../../../lib/assertions'; /** - * Regression test for https://github.com/aws/aws-cdk-cli/issues/2036 + * A key is added to JSON built with an intrinsic in an SSM parameter, while its description also changes. * - * When another property of the parameter changes too, CloudFormation keeps the parameter in a change - * set described with IncludePropertyValues, but leaves out the value whose JSON gained a key, without - * a warning. The changeset-based diff must show both changes. + * - Start: `{"stack":"","a":"1"}`, description `d1` + * - End: `{"stack":"","a":"1","b":"2"}`, description `d2` + * - Command: `cdk diff --method=change-set` + * - Expected: both the parameter's `Description` and its `Value` are shown as changed * - * This only exercises the recovery for as long as CloudFormation keeps leaving the change out. If it - * stops, the test still passes; the toolkit-lib unit tests are the lasting coverage. + * Described with IncludePropertyValues, CloudFormation keeps the parameter in the change set for the + * description, but leaves out its `Value`, without a warning + * (https://github.com/aws/aws-cdk-cli/issues/2036). + * + * If CloudFormation stops leaving the change out, this test still passes; the unit tests are the + * lasting coverage. */ integTest( 'cdk diff --method=change-set shows a key added to JSON built with an intrinsic when another property changes', diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts index 1e2c05ad1..ab769f1b4 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts @@ -2,10 +2,17 @@ import { integTest, withSpecificFixture } from '../../../lib'; import '../../../lib/assertions'; /** - * The CLI diffs nested stacks from their templates, not their change sets. A key added to JSON built with - * an intrinsic in a nested stack is shown, even though CloudFormation leaves it out of the nested change - * set described with IncludePropertyValues (https://github.com/aws/aws-cdk-cli/issues/2036). If the CLI - * starts reading nested change sets, it must keep showing it. + * A key is added to JSON built with an intrinsic in an SSM parameter in a nested stack. + * + * - Start: `{"stack":"","a":"1"}` + * - End: `{"stack":"","a":"1","b":"2"}` + * - Command: `cdk diff --method=change-set` + * - Expected: the nested parameter's `Value` is shown as changed + * + * CloudFormation leaves this change out of the nested change set described with IncludePropertyValues + * (https://github.com/aws/aws-cdk-cli/issues/2036). The CLI diffs nested stacks from their templates, + * not their change sets, so it shows the change. If the CLI starts reading nested change sets, it must + * keep showing it. */ integTest( 'cdk diff --method=change-set shows a key added to JSON in a nested stack', diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-with-a-token-value-added-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-with-a-token-value-added-to-json-built-with-an-intrinsic.integtest.ts index f34b4cb64..ae1012eae 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-with-a-token-value-added-to-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-with-a-token-value-added-to-json-built-with-an-intrinsic.integtest.ts @@ -2,10 +2,18 @@ import { integTest, withSpecificFixture } from '../../../lib'; import '../../../lib/assertions'; /** - * Regression test for https://github.com/aws/aws-cdk-cli/issues/2036 + * A key whose value is a token is added to JSON built with an intrinsic in an SSM parameter. * - * Like adding a key with a fixed value, but the new value is itself resolved at deploy time - * (`Aws.REGION`), so the deploy-time parts of the value change too. + * - Start: `{"stack":"","a":"1"}` + * - End: `{"stack":"","a":"1","region":""}` + * - Command: `cdk diff --method=change-set` + * - Expected: the parameter's `Value` is shown as changed + * + * Described with IncludePropertyValues, CloudFormation leaves this change out of the change set, + * without a warning (https://github.com/aws/aws-cdk-cli/issues/2036). + * + * If CloudFormation stops leaving the change out, this test still passes; the unit tests are the + * lasting coverage. */ integTest( 'cdk diff --method=change-set shows a key with a token value added to JSON built with an intrinsic', diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-changes-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-changes-to-json-built-with-an-intrinsic.integtest.ts index 6f0f89031..678a7cae0 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-changes-to-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-changes-to-json-built-with-an-intrinsic.integtest.ts @@ -2,14 +2,18 @@ import { integTest, withSpecificFixture } from '../../../lib'; import '../../../lib/assertions'; /** - * Regression test for https://github.com/aws/aws-cdk-cli/issues/2036 + * A key is added to JSON built with an intrinsic in an SSM parameter. * - * Adding a key to JSON that is built with an intrinsic and stored in a string property is a change - * CloudFormation leaves out of a change set described with IncludePropertyValues, without a warning. - * The changeset-based diff must show it anyway. + * - Start: `{"stack":"","a":"1"}` + * - End: `{"stack":"","a":"1","b":"2"}` + * - Command: `cdk diff --method=change-set` + * - Expected: the parameter's `Value` is shown as changed * - * This only exercises the recovery for as long as CloudFormation keeps leaving the change out. If it - * stops, the test still passes; the toolkit-lib unit tests are the lasting coverage. + * Described with IncludePropertyValues, CloudFormation leaves this change out of the change set, + * without a warning (https://github.com/aws/aws-cdk-cli/issues/2036). + * + * If CloudFormation stops leaving the change out, this test still passes; the unit tests are the + * lasting coverage. */ integTest( 'cdk diff --method=change-set shows changes to JSON built with an intrinsic', diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-reordered-keys-in-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-reordered-keys-in-json-built-with-an-intrinsic.integtest.ts index 88c53e35d..b45da1c90 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-reordered-keys-in-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-reordered-keys-in-json-built-with-an-intrinsic.integtest.ts @@ -2,10 +2,19 @@ import { integTest, withSpecificFixture } from '../../../lib'; import '../../../lib/assertions'; /** - * Regression test for https://github.com/aws/aws-cdk-cli/issues/2036 + * The keys of JSON built with an intrinsic in an SSM parameter are reordered. * - * Reordering the keys changes the stored string, but CloudFormation leaves the change out of a change - * set described with IncludePropertyValues, without a warning. + * - Start: `{"stack":"","a":"1"}` + * - End: `{"a":"1","stack":""}` + * - Command: `cdk diff --method=change-set` + * - Expected: the parameter's `Value` is shown as changed + * + * The stored string changes, so this is a real change. Described with IncludePropertyValues, + * CloudFormation leaves this change out of the change set, without a warning + * (https://github.com/aws/aws-cdk-cli/issues/2036). + * + * If CloudFormation stops leaving the change out, this test still passes; the unit tests are the + * lasting coverage. */ integTest( 'cdk diff --method=change-set shows reordered keys in JSON built with an intrinsic', diff --git a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-deploy-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-deploy-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts index c4a0855d3..64e97960d 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-deploy-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-deploy-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts @@ -4,11 +4,19 @@ import { approvalRecordingIoHost, assemblyFromCdkAppDir, hasParameterValueChange import { integTest, withSpecificFixture } from '../../lib'; /** - * Regression test for the deploy side of https://github.com/aws/aws-cdk-cli/issues/2036 + * A key is added to JSON built with an intrinsic in an SSM parameter, and the deploy approval is declined. * - * Adding a key to JSON built with an intrinsic is a change CloudFormation leaves out of a change set - * described with IncludePropertyValues, without a warning. The diff that toolkit-lib sends with the - * deploy approval request must contain it anyway. + * - Start: `{"stack":"","a":"1"}` + * - End: `{"stack":"","a":"1","b":"2"}` + * - Command: `toolkit.deploy(cx, { deploymentMethod: { method: 'change-set' } })` + * - Expected: the diff in the approval request (`CDK_TOOLKIT_I5060` `templateDiffs`) has the parameter's `Value` + * + * Described with IncludePropertyValues, CloudFormation leaves this change out of the change set, + * without a warning (https://github.com/aws/aws-cdk-cli/issues/2036). Tools that embed toolkit-lib get + * the diff as data, so this checks the data rather than printed text. + * + * If CloudFormation stops leaving the change out, this test still passes; the unit tests are the + * lasting coverage. */ integTest( 'toolkit deploy approval shows a key added to JSON built with an intrinsic', diff --git a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts index 90579e652..9550d7538 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts @@ -4,11 +4,19 @@ import { approvalRecordingIoHost, assemblyFromCdkAppDir, hasParameterValueChange import { integTest, withSpecificFixture } from '../../lib'; /** - * Regression test for https://github.com/aws/aws-cdk-cli/issues/2036 + * A key is added to JSON built with an intrinsic in an SSM parameter in a nested stack. * - * Adding a key to JSON built with an intrinsic is a change CloudFormation leaves out of a change set - * described with IncludePropertyValues, without a warning. toolkit-lib describes nested change sets too, - * and must show the change for a parameter in a nested stack. + * - Start: `{"stack":"","a":"1"}` + * - End: `{"stack":"","a":"1","b":"2"}` + * - Command: `toolkit.diff(cx, { method: DiffMethod.ChangeSet({ fallbackToTemplate: false }) })` + * - Expected: the nested stack's diff has the parameter's `Value` + * + * Unlike the CLI, toolkit-lib reads the nested change sets. Described with IncludePropertyValues, + * CloudFormation leaves this change out of the nested change set, without a warning + * (https://github.com/aws/aws-cdk-cli/issues/2036). + * + * If CloudFormation stops leaving the change out, this test still passes; the unit tests are the + * lasting coverage. */ integTest( 'toolkit diff --method=change-set shows a key added to JSON in a nested stack', diff --git a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-execute-change-set-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-execute-change-set-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts index bfefe025a..98cacaca4 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-execute-change-set-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-execute-change-set-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts @@ -4,11 +4,21 @@ import { approvalRecordingIoHost, assemblyFromCdkAppDir, hasParameterValueChange import { integTest, withSpecificFixture } from '../../lib'; /** - * Regression test for the deploy side of https://github.com/aws/aws-cdk-cli/issues/2036 + * A key is added, a change set is created without executing it, and executing it is declined at the approval. * - * Executing an existing change set asks for approval with a diff of that change set. Adding a key to - * JSON built with an intrinsic is left out of the change set described with IncludePropertyValues, - * without a warning. The diff sent with the approval request must contain it anyway. + * - Start: `{"stack":"","a":"1"}` + * - End: `{"stack":"","a":"1","b":"2"}` + * - Command: `toolkit.deploy(cx, { deploymentMethod: { method: 'change-set', execute: false, changeSetName } })`, then + * `toolkit.deploy(cx, { deploymentMethod: { method: 'execute-change-set', changeSetName } })` + * - Expected: the diff in the approval request (`CDK_TOOLKIT_I5060` `templateDiffs`) has the parameter's `Value` + * + * Described with IncludePropertyValues, CloudFormation leaves this change out of the change set, + * without a warning (https://github.com/aws/aws-cdk-cli/issues/2036). Executing an existing change set + * describes it separately from creating one, and the CLI does not show this diff, so only toolkit-lib + * can check it. + * + * If CloudFormation stops leaving the change out, this test still passes; the unit tests are the + * lasting coverage. */ integTest( 'toolkit execute-change-set approval shows a key added to JSON built with an intrinsic', From 8ae503c625b5af17fdb750f2e57b3ad54db8974c Mon Sep 17 00:00:00 2001 From: 9pace Date: Wed, 21 Oct 2026 16:40:10 +0000 Subject: [PATCH 13/16] test: say each case once, in the test name, the GIVEN/WHEN/THEN comments and the app --- .../cdk-apps/json-intrinsic-app/app.js | 2 ++ ...h-an-intrinsic-then-executes-it.integtest.ts | 17 ----------------- ...dformation-drops-with-a-warning.integtest.ts | 15 +++------------ ...to-json-built-with-an-intrinsic.integtest.ts | 15 --------------- ...eplacement-cloudformation-drops.integtest.ts | 13 +++---------- ...son-rewritten-to-the-same-value.integtest.ts | 11 ++--------- ...in-json-built-with-an-intrinsic.integtest.ts | 12 ------------ ...c-when-another-property-changes.integtest.ts | 15 --------------- ...added-to-json-in-a-nested-stack.integtest.ts | 15 ++++----------- ...to-json-built-with-an-intrinsic.integtest.ts | 14 -------------- ...to-json-built-with-an-intrinsic.integtest.ts | 14 -------------- ...in-json-built-with-an-intrinsic.integtest.ts | 15 --------------- ...to-json-built-with-an-intrinsic.integtest.ts | 15 --------------- ...added-to-json-in-a-nested-stack.integtest.ts | 15 --------------- ...to-json-built-with-an-intrinsic.integtest.ts | 17 ----------------- 15 files changed, 14 insertions(+), 191 deletions(-) diff --git a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js index d72629dab..78483b774 100644 --- a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js +++ b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/json-intrinsic-app/app.js @@ -31,6 +31,8 @@ if (!stackPrefix) { * Described with IncludePropertyValues, CloudFormation leaves `add-key`, `add-key-with-token` and * `reorder-keys` out of the change set, without a warning, or with one when the change is the task * definition replacement (https://github.com/aws/aws-cdk-cli/issues/2036). + * The tests that rely on this only exercise the recovery while CloudFormation keeps leaving these + * changes out; the toolkit-lib and cloudformation-diff unit tests are the lasting coverage. * * DESCRIPTION sets the description of the SSM parameter in `-json-intrinsic` (default "d1"). * This allows a test to change a separate property of the Parameter, and ensure that change is kept. diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-execute-shows-a-key-added-to-json-built-with-an-intrinsic-then-executes-it.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-execute-shows-a-key-added-to-json-built-with-an-intrinsic-then-executes-it.integtest.ts index ae7861ed2..a332f7834 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-execute-shows-a-key-added-to-json-built-with-an-intrinsic-then-executes-it.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-execute-shows-a-key-added-to-json-built-with-an-intrinsic-then-executes-it.integtest.ts @@ -1,22 +1,5 @@ import { integTest, withSpecificFixture } from '../../../lib'; -/** - * A key is added, a change set is created without executing it, and the change set is executed afterwards. - * - * - Start: `{"stack":"","a":"1"}` - * - End: `{"stack":"","a":"1","b":"2"}` - * - Command: `cdk deploy --no-execute --change-set-name= --require-approval=any-change --yes`, then - * `cdk deploy --method=execute-change-set --change-set-name=` - * - Expected: the approval diff of the first command shows the parameter's `Value` as changed, and after - * executing the change set a template diff finds nothing left to change - * - * Described with IncludePropertyValues, CloudFormation leaves this change out of the change set, - * without a warning (https://github.com/aws/aws-cdk-cli/issues/2036). `--no-execute` reads the same - * change set, and executing it shows the deploy itself applies the key. - * - * If CloudFormation stops leaving the change out, this test still passes; the unit tests are the - * lasting coverage. - */ integTest( 'cdk deploy --no-execute shows a key added to JSON built with an intrinsic, then executes it', withSpecificFixture('json-intrinsic-app', async (fixture) => { diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts index ccd4e187c..5cc153b3b 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---no-rollback-stops-for-a-replacement-cloudformation-drops-with-a-warning.integtest.ts @@ -1,18 +1,9 @@ import { integTest, withSpecificFixture } from '../../../lib'; /** - * A key is added to JSON in a container environment variable of an ECS task definition, which replaces it, and - * the deploy runs with rollback disabled. - * - * - Start: `{"stack":"","a":"1"}` - * - End: `{"stack":"","a":"1","b":"2"}` - * - Command: `cdk deploy --no-rollback` - * - Expected: CDK refuses to deploy the replacement with rollback disabled. Without a terminal it cannot offer - * to deploy with rollback enabled instead, so it fails and nothing is replaced - * - * Described with IncludePropertyValues, CloudFormation leaves the replacement out of the change set, - * but warns that it did (https://github.com/aws/aws-cdk-cli/issues/2036). The check runs on the - * description CDK makes before executing the change set, not the one the approval uses. + * CDK checks for replacements on the change set it describes just before executing, not the one the + * approval uses. CloudFormation leaves the task definition replacement out of it, with a warning + * (https://github.com/aws/aws-cdk-cli/issues/2036). */ integTest( 'cdk deploy --no-rollback stops for a replacement CloudFormation drops with a warning', diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts index c122ac9e9..15d91c461 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-any-change-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts @@ -1,20 +1,5 @@ import { integTest, withSpecificFixture } from '../../../lib'; -/** - * A key is added to JSON built with an intrinsic in an SSM parameter, and the deploy asks for approval. - * - * - Start: `{"stack":"","a":"1"}` - * - End: `{"stack":"","a":"1","b":"2"}` - * - Command: `cdk deploy --require-approval=any-change --yes` - * - Expected: the approval diff shows the parameter's `Value` as changed - * - * Described with IncludePropertyValues, CloudFormation leaves this change out of the change set, - * without a warning (https://github.com/aws/aws-cdk-cli/issues/2036). The approval diff is built from - * that change set. - * - * If CloudFormation stops leaving the change out, this test still passes; the unit tests are the - * lasting coverage. - */ integTest( 'cdk deploy --require-approval any-change shows a key added to JSON built with an intrinsic', withSpecificFixture('json-intrinsic-app', async (fixture) => { diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts index a6290c899..613ca0d3c 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/deploy/cdk-deploy---require-approval-destructive-prompts-for-a-replacement-cloudformation-drops.integtest.ts @@ -1,16 +1,9 @@ import { integTest, withSpecificFixture } from '../../../lib'; /** - * A key is added to JSON in a container environment variable of an ECS task definition, which replaces it. - * - * - Start: `{"stack":"","a":"1"}` - * - End: `{"stack":"","a":"1","b":"2"}` - * - Command: `cdk deploy --require-approval=destructive --yes` - * - Expected: the deploy asks for approval and lists the task definition as replaced - * - * Described with IncludePropertyValues, CloudFormation leaves the replacement out of the change set, - * but warns that it did (https://github.com/aws/aws-cdk-cli/issues/2036). CDK then describes the - * change set again, so the replacement is found. + * CloudFormation leaves the task definition replacement out of the change set described with + * IncludePropertyValues, but warns that it did (https://github.com/aws/aws-cdk-cli/issues/2036), so + * CDK describes it again and still finds the replacement. */ integTest( 'cdk deploy --require-approval destructive prompts for a replacement CloudFormation drops', diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-does-not-show-json-rewritten-to-the-same-value.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-does-not-show-json-rewritten-to-the-same-value.integtest.ts index 212d74bc4..8f8b921ce 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-does-not-show-json-rewritten-to-the-same-value.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-does-not-show-json-rewritten-to-the-same-value.integtest.ts @@ -2,15 +2,8 @@ import { integTest, withSpecificFixture } from '../../../lib'; import '../../../lib/assertions'; /** - * JSON built with Fn::Join in an SSM parameter is rewritten with Fn::Sub, resolving to the same value. - * - * - Start: `{"stack":"","a":"1"}`, built with Fn::Join - * - End: `{"stack":"","a":"1"}`, built with Fn::Sub - * - Command: `cdk diff --method=change-set` - * - Expected: "There were no differences" - * - * The template text differs, but nothing changes when it is deployed, and CloudFormation leaves it out - * of the change set either way. The recovery for changes CloudFormation leaves out + * The template text changes but the deployed value does not, so CloudFormation leaves it out of the + * change set either way. The recovery for changes CloudFormation leaves out * (https://github.com/aws/aws-cdk-cli/issues/2036) must not show it. */ integTest( diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts index 8139c5932..2ad2e9d10 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-changed-value-in-json-built-with-an-intrinsic.integtest.ts @@ -1,18 +1,6 @@ import { integTest, withSpecificFixture } from '../../../lib'; import '../../../lib/assertions'; -/** - * An existing value changes in JSON built with an intrinsic in an SSM parameter. - * - * - Start: `{"stack":"","a":"1"}` - * - End: `{"stack":"","a":"2"}` - * - Command: `cdk diff --method=change-set` - * - Expected: the parameter's `Value` is shown as changed - * - * CloudFormation lists this change even when described with IncludePropertyValues. It makes sure the - * recovery for the changes it leaves out (https://github.com/aws/aws-cdk-cli/issues/2036) does not - * affect the ones it lists. - */ integTest( 'cdk diff --method=change-set shows a changed value in JSON built with an intrinsic', withSpecificFixture('json-intrinsic-app', async (fixture) => { diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-built-with-an-intrinsic-when-another-property-changes.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-built-with-an-intrinsic-when-another-property-changes.integtest.ts index 4af4a669f..0971203c3 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-built-with-an-intrinsic-when-another-property-changes.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-built-with-an-intrinsic-when-another-property-changes.integtest.ts @@ -1,21 +1,6 @@ import { integTest, withSpecificFixture } from '../../../lib'; import '../../../lib/assertions'; -/** - * A key is added to JSON built with an intrinsic in an SSM parameter, while its description also changes. - * - * - Start: `{"stack":"","a":"1"}`, description `d1` - * - End: `{"stack":"","a":"1","b":"2"}`, description `d2` - * - Command: `cdk diff --method=change-set` - * - Expected: both the parameter's `Description` and its `Value` are shown as changed - * - * Described with IncludePropertyValues, CloudFormation keeps the parameter in the change set for the - * description, but leaves out its `Value`, without a warning - * (https://github.com/aws/aws-cdk-cli/issues/2036). - * - * If CloudFormation stops leaving the change out, this test still passes; the unit tests are the - * lasting coverage. - */ integTest( 'cdk diff --method=change-set shows a key added to JSON built with an intrinsic when another property changes', withSpecificFixture('json-intrinsic-app', async (fixture) => { diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts index ab769f1b4..1fc76453b 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts @@ -2,17 +2,10 @@ import { integTest, withSpecificFixture } from '../../../lib'; import '../../../lib/assertions'; /** - * A key is added to JSON built with an intrinsic in an SSM parameter in a nested stack. - * - * - Start: `{"stack":"","a":"1"}` - * - End: `{"stack":"","a":"1","b":"2"}` - * - Command: `cdk diff --method=change-set` - * - Expected: the nested parameter's `Value` is shown as changed - * - * CloudFormation leaves this change out of the nested change set described with IncludePropertyValues - * (https://github.com/aws/aws-cdk-cli/issues/2036). The CLI diffs nested stacks from their templates, - * not their change sets, so it shows the change. If the CLI starts reading nested change sets, it must - * keep showing it. + * The CLI diffs nested stacks from their templates, not their change sets, so it shows this change + * even though CloudFormation leaves it out of the nested change set + * (https://github.com/aws/aws-cdk-cli/issues/2036). It must keep showing it if the CLI starts reading + * nested change sets. */ integTest( 'cdk diff --method=change-set shows a key added to JSON in a nested stack', diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-with-a-token-value-added-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-with-a-token-value-added-to-json-built-with-an-intrinsic.integtest.ts index ae1012eae..8434bb770 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-with-a-token-value-added-to-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-a-key-with-a-token-value-added-to-json-built-with-an-intrinsic.integtest.ts @@ -1,20 +1,6 @@ import { integTest, withSpecificFixture } from '../../../lib'; import '../../../lib/assertions'; -/** - * A key whose value is a token is added to JSON built with an intrinsic in an SSM parameter. - * - * - Start: `{"stack":"","a":"1"}` - * - End: `{"stack":"","a":"1","region":""}` - * - Command: `cdk diff --method=change-set` - * - Expected: the parameter's `Value` is shown as changed - * - * Described with IncludePropertyValues, CloudFormation leaves this change out of the change set, - * without a warning (https://github.com/aws/aws-cdk-cli/issues/2036). - * - * If CloudFormation stops leaving the change out, this test still passes; the unit tests are the - * lasting coverage. - */ integTest( 'cdk diff --method=change-set shows a key with a token value added to JSON built with an intrinsic', withSpecificFixture('json-intrinsic-app', async (fixture) => { diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-changes-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-changes-to-json-built-with-an-intrinsic.integtest.ts index 678a7cae0..5203d6948 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-changes-to-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-changes-to-json-built-with-an-intrinsic.integtest.ts @@ -1,20 +1,6 @@ import { integTest, withSpecificFixture } from '../../../lib'; import '../../../lib/assertions'; -/** - * A key is added to JSON built with an intrinsic in an SSM parameter. - * - * - Start: `{"stack":"","a":"1"}` - * - End: `{"stack":"","a":"1","b":"2"}` - * - Command: `cdk diff --method=change-set` - * - Expected: the parameter's `Value` is shown as changed - * - * Described with IncludePropertyValues, CloudFormation leaves this change out of the change set, - * without a warning (https://github.com/aws/aws-cdk-cli/issues/2036). - * - * If CloudFormation stops leaving the change out, this test still passes; the unit tests are the - * lasting coverage. - */ integTest( 'cdk diff --method=change-set shows changes to JSON built with an intrinsic', withSpecificFixture('json-intrinsic-app', async (fixture) => { diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-reordered-keys-in-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-reordered-keys-in-json-built-with-an-intrinsic.integtest.ts index b45da1c90..4fa3270d6 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-reordered-keys-in-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-shows-reordered-keys-in-json-built-with-an-intrinsic.integtest.ts @@ -1,21 +1,6 @@ import { integTest, withSpecificFixture } from '../../../lib'; import '../../../lib/assertions'; -/** - * The keys of JSON built with an intrinsic in an SSM parameter are reordered. - * - * - Start: `{"stack":"","a":"1"}` - * - End: `{"a":"1","stack":""}` - * - Command: `cdk diff --method=change-set` - * - Expected: the parameter's `Value` is shown as changed - * - * The stored string changes, so this is a real change. Described with IncludePropertyValues, - * CloudFormation leaves this change out of the change set, without a warning - * (https://github.com/aws/aws-cdk-cli/issues/2036). - * - * If CloudFormation stops leaving the change out, this test still passes; the unit tests are the - * lasting coverage. - */ integTest( 'cdk diff --method=change-set shows reordered keys in JSON built with an intrinsic', withSpecificFixture('json-intrinsic-app', async (fixture) => { diff --git a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-deploy-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-deploy-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts index 64e97960d..81174b914 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-deploy-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-deploy-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts @@ -3,21 +3,6 @@ import * as toolkit from '@aws-cdk/toolkit-lib'; import { approvalRecordingIoHost, assemblyFromCdkAppDir, hasParameterValueChange, toolkitFromFixture } from './toolkit-helpers'; import { integTest, withSpecificFixture } from '../../lib'; -/** - * A key is added to JSON built with an intrinsic in an SSM parameter, and the deploy approval is declined. - * - * - Start: `{"stack":"","a":"1"}` - * - End: `{"stack":"","a":"1","b":"2"}` - * - Command: `toolkit.deploy(cx, { deploymentMethod: { method: 'change-set' } })` - * - Expected: the diff in the approval request (`CDK_TOOLKIT_I5060` `templateDiffs`) has the parameter's `Value` - * - * Described with IncludePropertyValues, CloudFormation leaves this change out of the change set, - * without a warning (https://github.com/aws/aws-cdk-cli/issues/2036). Tools that embed toolkit-lib get - * the diff as data, so this checks the data rather than printed text. - * - * If CloudFormation stops leaving the change out, this test still passes; the unit tests are the - * lasting coverage. - */ integTest( 'toolkit deploy approval shows a key added to JSON built with an intrinsic', withSpecificFixture('json-intrinsic-app', async (fixture) => { diff --git a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts index 9550d7538..08fc998cc 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-diff-change-set-shows-a-key-added-to-json-in-a-nested-stack.integtest.ts @@ -3,21 +3,6 @@ import * as toolkit from '@aws-cdk/toolkit-lib'; import { approvalRecordingIoHost, assemblyFromCdkAppDir, hasParameterValueChange, toolkitFromFixture } from './toolkit-helpers'; import { integTest, withSpecificFixture } from '../../lib'; -/** - * A key is added to JSON built with an intrinsic in an SSM parameter in a nested stack. - * - * - Start: `{"stack":"","a":"1"}` - * - End: `{"stack":"","a":"1","b":"2"}` - * - Command: `toolkit.diff(cx, { method: DiffMethod.ChangeSet({ fallbackToTemplate: false }) })` - * - Expected: the nested stack's diff has the parameter's `Value` - * - * Unlike the CLI, toolkit-lib reads the nested change sets. Described with IncludePropertyValues, - * CloudFormation leaves this change out of the nested change set, without a warning - * (https://github.com/aws/aws-cdk-cli/issues/2036). - * - * If CloudFormation stops leaving the change out, this test still passes; the unit tests are the - * lasting coverage. - */ integTest( 'toolkit diff --method=change-set shows a key added to JSON in a nested stack', withSpecificFixture('json-intrinsic-app', async (fixture) => { diff --git a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-execute-change-set-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-execute-change-set-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts index 98cacaca4..d1d47f17e 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-execute-change-set-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/toolkit-lib-integ-tests/toolkit-execute-change-set-approval-shows-a-key-added-to-json-built-with-an-intrinsic.integtest.ts @@ -3,23 +3,6 @@ import * as toolkit from '@aws-cdk/toolkit-lib'; import { approvalRecordingIoHost, assemblyFromCdkAppDir, hasParameterValueChange, toolkitFromFixture } from './toolkit-helpers'; import { integTest, withSpecificFixture } from '../../lib'; -/** - * A key is added, a change set is created without executing it, and executing it is declined at the approval. - * - * - Start: `{"stack":"","a":"1"}` - * - End: `{"stack":"","a":"1","b":"2"}` - * - Command: `toolkit.deploy(cx, { deploymentMethod: { method: 'change-set', execute: false, changeSetName } })`, then - * `toolkit.deploy(cx, { deploymentMethod: { method: 'execute-change-set', changeSetName } })` - * - Expected: the diff in the approval request (`CDK_TOOLKIT_I5060` `templateDiffs`) has the parameter's `Value` - * - * Described with IncludePropertyValues, CloudFormation leaves this change out of the change set, - * without a warning (https://github.com/aws/aws-cdk-cli/issues/2036). Executing an existing change set - * describes it separately from creating one, and the CLI does not show this diff, so only toolkit-lib - * can check it. - * - * If CloudFormation stops leaving the change out, this test still passes; the unit tests are the - * lasting coverage. - */ integTest( 'toolkit execute-change-set approval shows a key added to JSON built with an intrinsic', withSpecificFixture('json-intrinsic-app', async (fixture) => { From 9d8b51abd2d993e2ac81b71623019cf6d6fed986 Mon Sep 17 00:00:00 2001 From: 9pace Date: Wed, 21 Oct 2026 20:00:06 +0000 Subject: [PATCH 14/16] fix(toolkit-lib): check every change set description against the templates Make `templates` required on `ChangeSetDescriber`, so that every description is completed when CloudFormation silently leaves a change out: - deploy and `execute-change-set` pass the deployed and synthesized templates, so `hasReplacement` (`--no-rollback`, paused and express stacks) sees a replacement the description dropped - `cdk validate` and the change set cleanup pass them too - toolkit-lib `diff` passes each nested stack its own templates - `Deployments.describeChangeSet` requires the deployed template - the failure diagnoser describes without property values, which never drops changes, using the paging describe the describer now shares --- .../lib/actions/diff/private/helpers.ts | 1 + .../api/change-sets/change-set-describer.ts | 84 +++++++++++-------- .../lib/api/deployments/cfn-api.ts | 6 +- .../lib/api/deployments/deploy-stack.ts | 14 +++- .../lib/api/deployments/deployments.ts | 8 +- .../lib/api/diagnosing/stack-diagnoser.ts | 17 +--- .../toolkit-lib/lib/toolkit/toolkit.ts | 4 +- .../toolkit-lib/test/actions/deploy.test.ts | 2 +- .../toolkit-lib/test/actions/diff.test.ts | 58 +++++++++++++ .../change-sets/change-set-describer.test.ts | 2 + .../test/api/deployments/deploy-stack.test.ts | 44 ++++++++-- .../api/diagnosing/stack-diagnoser.test.ts | 56 +++++++++++++ 12 files changed, 230 insertions(+), 66 deletions(-) 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 a2df50fd2..94a073e24 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 @@ -159,6 +159,7 @@ async function attachNestedChangeSetData( ioHelper, stackNameOrArn: rc.PhysicalResourceId ?? rc.LogicalResourceId, changeSetNameOrArn: rc.ChangeSetId, + templates: { deployed: nested.deployedTemplate, generated: nested.generatedTemplate }, }).waitForSettled(); // Replace the entry with one that includes the changeset diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/change-sets/change-set-describer.ts b/packages/@aws-cdk/toolkit-lib/lib/api/change-sets/change-set-describer.ts index a4e733a06..93bc267b7 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/change-sets/change-set-describer.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/change-sets/change-set-describer.ts @@ -30,15 +30,11 @@ export interface ChangeSetDescriberProps { /** * The templates the change set goes from and to. * - * When given, a created change set whose description would hide one of these template differences - * from the diff is described a second time without property values, and whatever the first - * description left out is put back. - * - * Optional because only callers that show the change set as a diff need the check. - * - * @default - no templates, so only the incomplete-data warning causes a second description + * A created change set whose description would hide one of these template differences is + * described a second time without property values, and whatever the first description left + * out is put back. */ - readonly templates?: ChangeSetTemplates; + readonly templates: ChangeSetTemplates; } /** @@ -98,7 +94,7 @@ export class ChangeSetDescriber { private readonly changeSetNameOrArn: string; private readonly stackDisplayName: string; private readonly changeSetDisplayName: string; - private readonly templates?: ChangeSetTemplates; + private readonly templates: ChangeSetTemplates; constructor(props: ChangeSetDescriberProps) { this.cfn = props.cfn; @@ -240,7 +236,7 @@ export class ChangeSetDescriber { * Only a created change set can hide anything. */ private async hidesTemplateDifferences(description: DescribeChangeSetCommandOutput): Promise { - if (!this.templates || description.Status !== ChangeSetStatus.CREATE_COMPLETE) { + if (description.Status !== ChangeSetStatus.CREATE_COMPLETE) { return false; } @@ -256,32 +252,7 @@ export class ChangeSetDescriber { } private async describeOnce(includePropertyValues: boolean): Promise { - const response = await this.cfn.describeChangeSet({ - StackName: this.stackNameOrArn, - ChangeSetName: this.changeSetNameOrArn, - IncludePropertyValues: includePropertyValues, - }); - - // Traverse all pages, so that `Changes` is always the complete set of changes. A truncated - // list would silently make callers believe a resource is unchanged. - while (response.NextToken != null) { - const nextPage = await this.cfn.describeChangeSet({ - StackName: response.StackId ?? this.stackNameOrArn, - ChangeSetName: response.ChangeSetId ?? this.changeSetNameOrArn, - NextToken: response.NextToken, - IncludePropertyValues: includePropertyValues, - }); - - // Consolidate the changes - if (nextPage.Changes != null) { - response.Changes = response.Changes != null ? response.Changes.concat(nextPage.Changes) : nextPage.Changes; - } - - // Forward the new NextToken - response.NextToken = nextPage.NextToken; - } - - return response; + return describeAllChangeSetPages(this.cfn, this.stackNameOrArn, this.changeSetNameOrArn, { includePropertyValues }); } /** @@ -364,3 +335,44 @@ const INCOMPLETE_PROPERTY_VALUES_WARNING = '[WARN] --include-property-values opt function hasIncompletePropertyValuesWarning(description: DescribeChangeSetCommandOutput): boolean { return description.StatusReason?.includes(INCOMPLETE_PROPERTY_VALUES_WARNING) ?? false; } + +/** + * Describe a change set once, with all pages of `Changes`. + * + * Use `ChangeSetDescriber` to show a change set: described with property values, CloudFormation can + * leave changes out, and only the describer puts them back. Described without property values, it + * lists every change, so this is enough for callers that only need which resources change. + */ +export async function describeAllChangeSetPages( + cfn: ICloudFormationClient, + stackNameOrArn: string, + changeSetNameOrArn: string, + options: { includePropertyValues: boolean }, +): Promise { + const response = await cfn.describeChangeSet({ + StackName: stackNameOrArn, + ChangeSetName: changeSetNameOrArn, + IncludePropertyValues: options.includePropertyValues, + }); + + // Traverse all pages, so that `Changes` is always the complete set of changes. A truncated + // list would silently make callers believe a resource is unchanged. + while (response.NextToken != null) { + const nextPage = await cfn.describeChangeSet({ + StackName: response.StackId ?? stackNameOrArn, + ChangeSetName: response.ChangeSetId ?? changeSetNameOrArn, + NextToken: response.NextToken, + IncludePropertyValues: options.includePropertyValues, + }); + + // Consolidate the changes + if (nextPage.Changes != null) { + response.Changes = response.Changes != null ? response.Changes.concat(nextPage.Changes) : nextPage.Changes; + } + + // Forward the new NextToken + response.NextToken = nextPage.NextToken; + } + + return response; +} diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/deployments/cfn-api.ts b/packages/@aws-cdk/toolkit-lib/lib/api/deployments/cfn-api.ts index 16be61da3..a6ae50422 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/deployments/cfn-api.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/deployments/cfn-api.ts @@ -166,6 +166,7 @@ interface PreparedChangeSetEnv { stackExistedBefore: boolean; executionRoleArn: string | undefined; diagnoser: CloudFormationStackDiagnoser; + deployedStack: CloudFormationStack; } async function prepareChangeSetEnv( @@ -195,7 +196,7 @@ async function prepareChangeSetEnv( topLevelStackHierarchicalId: options.stack.hierarchicalId, }); - return { cfn, bodyParameter, exists, stackExistedBefore: stack.exists, executionRoleArn, diagnoser }; + return { cfn, bodyParameter, exists, stackExistedBefore: stack.exists, executionRoleArn, diagnoser, deployedStack: stack }; } /** @@ -318,7 +319,7 @@ export async function createValidationChangeSet( ioHelper: IoHelper, options: Omit, ): Promise { - const { cfn, bodyParameter, exists, stackExistedBefore, executionRoleArn, diagnoser } = await prepareChangeSetEnv(ioHelper, options); + const { cfn, bodyParameter, exists, stackExistedBefore, executionRoleArn, diagnoser, deployedStack } = await prepareChangeSetEnv(ioHelper, options); const changeSetName = `cdk-validate-${options.uuid}`; const templateParams = TemplateParameters.fromTemplate(options.stack.template); @@ -345,6 +346,7 @@ export async function createValidationChangeSet( ioHelper, stackNameOrArn: changeSet.StackId ?? options.stack.stackName, changeSetNameOrArn: changeSet.Id ?? changeSetName, + templates: { deployed: await deployedStack.template(), generated: options.stack.template }, }).waitForReport({ diagnoser }); return report; diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deploy-stack.ts b/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deploy-stack.ts index b7b123ef6..3598ad577 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deploy-stack.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deploy-stack.ts @@ -33,7 +33,7 @@ import type { StabilizingResource } from '../../toolkit/types'; import { formatErrorMessage } from '../../util'; import { changeSetNameFromArn } from '../../util/cloudformation'; import type { SDK, SdkProvider, ICloudFormationClient } from '../aws-auth/private'; -import type { ChangeSetReport } from '../change-sets'; +import type { ChangeSetReport, ChangeSetTemplates } from '../change-sets'; import { ChangeSetDescriber } from '../change-sets'; import type { TemplateBodyParameter } from '../cloudformation'; import { makeBodyParameter, CfnEvaluationException, CloudFormationStack } from '../cloudformation'; @@ -546,6 +546,7 @@ class FullCloudFormationDeployment { ioHelper: this.ioHelper, stackNameOrArn: this.stackName, changeSetNameOrArn: deploymentMethod.changeSetName, + templates: await this.changeSetTemplates(), }).describeForExecution({ diagnoser: this.diagnoser }); return this.checkAndExecuteChangeSet(changeSetReport); @@ -647,8 +648,7 @@ class FullCloudFormationDeployment { ioHelper: this.ioHelper, stackNameOrArn: changeSet.StackId ?? this.stackName, changeSetNameOrArn: changeSet.Id ?? changeSetName, - // The deployed template is cached, and an ordinary deploy has already read it to decide whether it can skip. - templates: this.update ? { deployed: await this.cloudFormationStack.template(), generated: this.stackArtifact.template } : undefined, + templates: await this.changeSetTemplates(), }).waitAndThrowOnProblem({ diagnoser: this.diagnoser, }); @@ -671,10 +671,18 @@ class FullCloudFormationDeployment { ioHelper: this.ioHelper, stackNameOrArn: this.stackName, changeSetNameOrArn: changeSetNameOrArn, + templates: await this.changeSetTemplates(), }).waitForGone(); } } + /** + * The templates a change set on this stack goes from and to. + */ + private async changeSetTemplates(): Promise { + return { deployed: await this.cloudFormationStack.template(), generated: this.stackArtifact.template }; + } + private async updateTerminationProtection() { // Update termination protection only if it has changed. const terminationProtection = this.stackArtifact.terminationProtection ?? false; diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts b/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts index a3fa5cd74..93c904d98 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts @@ -523,16 +523,16 @@ export class Deployments { * * @param stack - The Stack artifact * @param changeSetName - The name of the change set - * @param stackArn - Optional the exact arn of the stack. Prefer this if you have it available. - * @param deployedTemplate - Optional the deployed template the diff compares against, so that a + * @param deployedTemplate - The deployed template the diff compares against, so that a * description that would hide a template difference is completed (see `ChangeSetDescriber`) + * @param stackArn - Optional the exact arn of the stack. Prefer this if you have it available. * @returns the DescribeChangeSet response */ public async describeChangeSet( stack: cxapi.CloudFormationStackArtifact, changeSetName: string, + deployedTemplate: Template, stackArn?: string, - deployedTemplate?: Template, ): Promise { const env = await this.envs.accessStackForMutableStackOperations(stack); const cfn = env.sdk.cloudFormation(); @@ -541,7 +541,7 @@ export class Deployments { ioHelper: this.ioHelper, stackNameOrArn: stackArn ?? stack.stackName, changeSetNameOrArn: changeSetName, - templates: deployedTemplate ? { deployed: deployedTemplate, generated: stack.template } : undefined, + templates: { deployed: deployedTemplate, generated: stack.template }, }).waitAndThrowOnProblem({ diagnoser: new CloudFormationStackDiagnoser({ sdk: env.sdk, diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/diagnosing/stack-diagnoser.ts b/packages/@aws-cdk/toolkit-lib/lib/api/diagnosing/stack-diagnoser.ts index 2a9165d0a..e8a4ae3d1 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/diagnosing/stack-diagnoser.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/diagnosing/stack-diagnoser.ts @@ -7,7 +7,7 @@ import { Diagnosis } from './diagnosis'; import { investigateResource } from './resource-investigation'; import type { AdditionalDiagnosticContext, StackProblemSource, TracedResourceError } from '../../actions/diagnose'; import type { ICloudFormationClient, SDK } from '../aws-auth/sdk'; -import { ChangeSetDescriber } from '../change-sets'; +import { describeAllChangeSetPages } from '../change-sets'; import type { EnvironmentResources } from '../environment'; import type { IoHelper } from '../io/private/io-helper'; import type { ISourceTracer } from '../source-tracing/private/source-tracing'; @@ -562,12 +562,8 @@ export class CloudFormationStackDiagnoser { }); } - const nestedCs = await new ChangeSetDescriber({ - cfn: this.cfn, - ioHelper: this.props.ioHelper, - stackNameOrArn: nested.stackArn, - changeSetNameOrArn: nested.changeSetArn, - }).describeCurrentState(); + // Diagnosing only needs the status and which resources change, not property values. + const nestedCs = await describeAllChangeSetPages(this.cfn, nested.stackArn, nested.changeSetArn, { includePropertyValues: false }); const nestedDiag = new CloudFormationStackDiagnoser(this.props); nestedDiag.parentStackLogicalIds = [...this.parentStackLogicalIds, nested.logicalId]; @@ -582,12 +578,7 @@ export class CloudFormationStackDiagnoser { // The status reason only includes the change set ID, but we also need the stack name. The way to get this is // describe the current change set, then from the Changes find the stack whose ChangeSetId is mentioned in the // status reason, then look up that change set and recurse into a regular change set diagnosis. - const resp = await new ChangeSetDescriber({ - cfn: this.cfn, - ioHelper: this.props.ioHelper, - stackNameOrArn: changeSet.StackId!, - changeSetNameOrArn: changeSet.ChangeSetId!, - }).describeCurrentState(); + const resp = await describeAllChangeSetPages(this.cfn, changeSet.StackId!, changeSet.ChangeSetId!, { includePropertyValues: false }); for (const change of resp.Changes ?? []) { if (change.Type === ChangeType.Resource && change.ResourceChange?.ResourceType === 'AWS::CloudFormation::Stack' && change.ResourceChange?.ChangeSetId && changeSet.StatusReason?.includes(change.ResourceChange?.ChangeSetId)) { diff --git a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts index 2decbd0c7..2102bbe77 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts @@ -941,7 +941,9 @@ export class Toolkit extends CloudAssemblySourceBuilder { // For execute-change-set, describe the existing change set so we can show an accurate diff const diffChangeSet = isExecuteChangeSetDeployment(options.deploymentMethod) - ? (await deployments.describeChangeSet(stack, options.deploymentMethod.changeSetName, prepareResult?.stackArn, deployedRootTemplate)).changeSet + ? (await deployments.describeChangeSet( + stack, options.deploymentMethod.changeSetName, deployedRootTemplate, prepareResult?.stackArn, + )).changeSet : prepareResult?.changeSet; const formatter = new DiffFormatter({ templateInfo: { diff --git a/packages/@aws-cdk/toolkit-lib/test/actions/deploy.test.ts b/packages/@aws-cdk/toolkit-lib/test/actions/deploy.test.ts index f716b8f07..3111bd3ae 100644 --- a/packages/@aws-cdk/toolkit-lib/test/actions/deploy.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/actions/deploy.test.ts @@ -277,7 +277,7 @@ IAM Statement Changes }); // THEN - expect(describeChangeSet).toHaveBeenCalledWith(expect.anything(), 'my-change-set', undefined, deployedRootTemplate); + expect(describeChangeSet).toHaveBeenCalledWith(expect.anything(), 'my-change-set', deployedRootTemplate, undefined); }); test('uses template-only diff with method=direct', async () => { diff --git a/packages/@aws-cdk/toolkit-lib/test/actions/diff.test.ts b/packages/@aws-cdk/toolkit-lib/test/actions/diff.test.ts index 3fcc0633c..f3063cdad 100644 --- a/packages/@aws-cdk/toolkit-lib/test/actions/diff.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/actions/diff.test.ts @@ -809,6 +809,64 @@ describe('diff', () => { })); }); + test('ChangeSet diff shows an added key in JSON in a nested stack that CloudFormation silently drops (issue #2036)', async () => { + // GIVEN - a nested stack whose parameter gains key "b" + const rootChangeSetId = 'arn:aws:cloudformation:us-east-1:123456789012:changeSet/cdk-diff'; + const nestedChangeSetId = 'arn:aws:cloudformation:us-east-1:123456789012:changeSet/cdk-diff-nested/nested'; + const nestedParameter = (value: unknown) => ({ + Resources: { Param: { Type: 'AWS::SSM::Parameter', Properties: { Type: 'String', Value: value } } }, + }); + jest.spyOn(deployments.Deployments.prototype, 'readCurrentTemplateWithNestedStacks').mockImplementation(async (stack) => ({ + deployedRootTemplate: stack.template, + nestedStacks: { + NestedStack: { + deployedTemplate: nestedParameter(jsonWithStackName({ a: '1' })), + generatedTemplate: nestedParameter(jsonWithStackName({ a: '1', b: '2' })), + physicalName: 'Stack1-NestedStack-ABC', + nestedStackTemplates: {}, + }, + }, + })); + givenChangeSetCanBeCreated('Stack1'); + + // The root change set reports the nested stack update... + mockCloudFormationClient.on(DescribeChangeSetCommand, { ChangeSetName: rootChangeSetId }).resolves({ + Status: 'CREATE_COMPLETE', + ExecutionStatus: 'AVAILABLE', + Changes: [{ + Type: 'Resource', + ResourceChange: { + Action: 'Modify', + LogicalResourceId: 'NestedStack', + PhysicalResourceId: 'arn:aws:cloudformation:us-east-1:123456789012:stack/Stack1-NestedStack-ABC/abc', + ResourceType: 'AWS::CloudFormation::Stack', + ChangeSetId: nestedChangeSetId, + }, + }], + }); + // ...and the nested change set leaves the parameter change out of its IncludePropertyValues description + mockCloudFormationClient.on(DescribeChangeSetCommand, { ChangeSetName: nestedChangeSetId, IncludePropertyValues: true }).resolves({ + Status: 'CREATE_COMPLETE', + Changes: [], + }); + mockCloudFormationClient.on(DescribeChangeSetCommand, { ChangeSetName: nestedChangeSetId, IncludePropertyValues: false }).resolves({ + Status: 'CREATE_COMPLETE', + Changes: [parameterValueChange('Param')], + }); + + // WHEN + const cx = await cdkOutFixture(toolkit, 'stack-with-bucket'); + const result = await toolkit.diff(cx, { + stacks: { strategy: StackSelectionStrategy.ALL_STACKS }, + method: DiffMethod.ChangeSet({ fallbackToTemplate: false }), + }); + + // THEN - the nested diff shows the change + const valueChange = result['Stack1-NestedStack-ABC'].resources.get('Param').propertyUpdates.Value; + expect(valueChange.isDifferent).toBe(true); + expect(valueChange.changeImpact).toEqual(ResourceImpact.WILL_UPDATE); + }); + test('ChangeSet diff still hides a rewrite that resolves to the same value, Fn::Sub to Fn::Join', async () => { // GIVEN - the deployed parameter builds the same JSON with Fn::Sub instead of Fn::Join givenDeployedProperty('Param165332EC', 'Value', { 'Fn::Sub': '{"stack":"${AWS::StackName}","a":"1","b":"2"}' }); diff --git a/packages/@aws-cdk/toolkit-lib/test/api/change-sets/change-set-describer.test.ts b/packages/@aws-cdk/toolkit-lib/test/api/change-sets/change-set-describer.test.ts index 11531e564..dcdd4486e 100644 --- a/packages/@aws-cdk/toolkit-lib/test/api/change-sets/change-set-describer.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/api/change-sets/change-set-describer.test.ts @@ -31,6 +31,8 @@ function describer(props: Partial = {}) { ioHelper, stackNameOrArn: 'my-stack', changeSetNameOrArn: 'my-change-set', + // Identical templates, so the template check never asks for a second description. + templates: { deployed: {}, generated: {} }, ...props, }); } diff --git a/packages/@aws-cdk/toolkit-lib/test/api/deployments/deploy-stack.test.ts b/packages/@aws-cdk/toolkit-lib/test/api/deployments/deploy-stack.test.ts index 8e02cb92f..9ceb9bb78 100644 --- a/packages/@aws-cdk/toolkit-lib/test/api/deployments/deploy-stack.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/api/deployments/deploy-stack.test.ts @@ -349,9 +349,7 @@ test('execute-change-set describes and executes an existing change set', async ( test('execute-change-set throws if change set is not ready', async () => { // GIVEN - mockCloudFormationClient.on(DescribeStacksCommand).resolves({ - Stacks: [{ ...baseResponse }], - }); + givenStackExists(); mockCloudFormationClient.on(DescribeChangeSetCommand).resolves({ Status: 'CREATE_IN_PROGRESS', StatusReason: 'doing stuff', @@ -368,9 +366,7 @@ test('execute-change-set throws if change set is not ready', async () => { test('execute-change-set throws if change set failed', async () => { // GIVEN - mockCloudFormationClient.on(DescribeStacksCommand).resolves({ - Stacks: [{ ...baseResponse }], - }); + givenStackExists(); mockCloudFormationClient.on(DescribeChangeSetCommand).resolves({ Status: 'FAILED', StatusReason: 'some reason', @@ -1988,6 +1984,42 @@ describe('execute-change-set deployment method', () => { expect(mockCloudFormationClient).not.toHaveReceivedCommand(ExecuteChangeSetCommand); }); + test('returns replacement-requires-rollback when the description silently leaves the replacement out (#2036)', async () => { + // GIVEN - renaming the queue, with a name resolved at deploy time, replaces it. The change set's + // IncludePropertyValues description leaves that out without a warning. + const queueTemplate = (suffix: string) => ({ + Resources: { + Queue4A7E3555: { Type: 'AWS::SQS::Queue', Properties: { QueueName: { 'Fn::Join': ['', [{ Ref: 'AWS::StackName' }, suffix]] } } }, + }, + }); + const queueReplacement = replacementChange(); + queueReplacement.ResourceChange!.Details![0].Target!.Name = 'QueueName'; + givenStackExists(); + givenTemplateIs(queueTemplate('-a')); + givenChangeSetExists({ + Status: 'CREATE_COMPLETE', + ChangeSetName: 'my-change-set', + Changes: [queueReplacement], + }); + mockCloudFormationClient.on(DescribeChangeSetCommand, { IncludePropertyValues: true }).callsFake(async (input) => ({ + ...await fakeCfn.describeChangeSet(input), + Changes: [], + })); + + // WHEN + const result = await advanceTime( + testDeployStack({ + ...standardDeployStackArguments(testStack({ stackName: 'withouterrors', template: queueTemplate('-b') })), + deploymentMethod: { method: 'execute-change-set', changeSetName: 'my-change-set' }, + rollback: false, + }), + ); + + // THEN + expect(result.type).toEqual('replacement-requires-rollback'); + expect(mockCloudFormationClient).not.toHaveReceivedCommand(ExecuteChangeSetCommand); + }); + test('is never skipped by canSkipDeploy', async () => { // GIVEN - stack exists with identical template (would normally skip) givenStackExists(); diff --git a/packages/@aws-cdk/toolkit-lib/test/api/diagnosing/stack-diagnoser.test.ts b/packages/@aws-cdk/toolkit-lib/test/api/diagnosing/stack-diagnoser.test.ts index a2703eaf4..b3bd1b98a 100644 --- a/packages/@aws-cdk/toolkit-lib/test/api/diagnosing/stack-diagnoser.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/api/diagnosing/stack-diagnoser.test.ts @@ -1,5 +1,7 @@ +import type { Change } from '@aws-sdk/client-cloudformation'; import { ChangeSetStatus, + DescribeChangeSetCommand, DescribeStackResourcesCommand, GetHookResultCommand, ListHookResultsCommand, @@ -294,6 +296,60 @@ describe('CloudFormationStackDiagnoser', () => { expect(fakeTracer.stackCalls.length + fakeTracer.resourceCalls.length).toBeGreaterThan(0); }); + test('finds the failed nested change set on a later page, describing without property values', async () => { + // GIVEN - the parent change set lists the failed nested stack on its second page + fakeCfn.createStackSync({ StackName: 'ParentStack', StackStatus: 'CREATE_COMPLETE' }); + fakeCfn.createStackSync({ StackName: 'NestedStack', StackStatus: 'CREATE_COMPLETE' }); + fakeCfn.createChangeSetSync({ + StackName: 'NestedStack', + ChangeSetName: 'nested-cs', + Status: 'FAILED', + StatusReason: 'Some nested error', + }); + const nestedCsId = fakeCfn.accessStack('NestedStack').changeSets[0].id; + const nestedStackChange: Change = { + Type: 'Resource', + ResourceChange: { + Action: 'Modify', + LogicalResourceId: 'NestedStackResource', + ResourceType: 'AWS::CloudFormation::Stack', + PhysicalResourceId: 'NestedStack', + ChangeSetId: nestedCsId, + }, + }; + fakeCfn.createChangeSetSync({ + StackName: 'ParentStack', + ChangeSetName: 'parent-cs', + Status: 'FAILED', + StatusReason: `Nested change set ${nestedCsId} failed`, + Changes: [nestedStackChange], + }); + const parentCsId = fakeCfn.accessStack('ParentStack').changeSets[0].id; + mockCloudFormationClient.on(DescribeChangeSetCommand).callsFake(async (input) => { + const response = await fakeCfn.describeChangeSet(input); + if (input.ChangeSetName !== parentCsId) { + return response; + } + return input.NextToken + ? { ...response, Changes: [nestedStackChange] } + : { ...response, Changes: [], NextToken: 'page-2' }; + }); + + // WHEN + const result = await makeDiagnoser().diagnoseFromFresh('ParentStack'); + + // THEN + expect(result.result).toMatchObject({ + type: 'problem', + detectedBy: { type: 'change-set' }, + problems: [expect.anything()], + }); + const nestedDescribes = mockCloudFormationClient.commandCalls(DescribeChangeSetCommand) + .filter((call) => call.args[0].input.ChangeSetName === nestedCsId); + expect(nestedDescribes.length).toBeGreaterThan(0); + expect(nestedDescribes.every((call) => call.args[0].input.IncludePropertyValues === false)).toBe(true); + }); + test('diagnoses early validation failure', async () => { fakeCfn.createStackSync({ StackName: 'MyStack', StackStatus: 'CREATE_COMPLETE' }); fakeCfn.createChangeSetSync({ From aec59392248aecc35a96e623f96550942cb95016 Mon Sep 17 00:00:00 2001 From: 9pace Date: Wed, 21 Oct 2026 20:43:38 +0000 Subject: [PATCH 15/16] test: trim the header of the JSON-rewritten-to-the-same-value diff test --- ...does-not-show-json-rewritten-to-the-same-value.integtest.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-does-not-show-json-rewritten-to-the-same-value.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-does-not-show-json-rewritten-to-the-same-value.integtest.ts index 8f8b921ce..a9a4e934d 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-does-not-show-json-rewritten-to-the-same-value.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/diff/cdk-diff---method-change-set-does-not-show-json-rewritten-to-the-same-value.integtest.ts @@ -3,8 +3,7 @@ import '../../../lib/assertions'; /** * The template text changes but the deployed value does not, so CloudFormation leaves it out of the - * change set either way. The recovery for changes CloudFormation leaves out - * (https://github.com/aws/aws-cdk-cli/issues/2036) must not show it. + * change set. */ integTest( 'cdk diff --method=change-set does not show JSON rewritten to the same value', From 6960ecdcf2d48e8ddc165de0a83e4edd24ea555e Mon Sep 17 00:00:00 2001 From: 9pace Date: Wed, 21 Oct 2026 21:06:41 +0000 Subject: [PATCH 16/16] fix(toolkit-lib): validate compares against an empty template for a stack being created or deleted --- packages/@aws-cdk/toolkit-lib/lib/api/deployments/cfn-api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/deployments/cfn-api.ts b/packages/@aws-cdk/toolkit-lib/lib/api/deployments/cfn-api.ts index a6ae50422..c2ca8a629 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/deployments/cfn-api.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/deployments/cfn-api.ts @@ -346,7 +346,7 @@ export async function createValidationChangeSet( ioHelper, stackNameOrArn: changeSet.StackId ?? options.stack.stackName, changeSetNameOrArn: changeSet.Id ?? changeSetName, - templates: { deployed: await deployedStack.template(), generated: options.stack.template }, + templates: { deployed: exists ? await deployedStack.template() : {}, generated: options.stack.template }, }).waitForReport({ diagnoser }); return report;