Skip to content

Commit 751a922

Browse files
authored
feat(CLI): synth displays "AssertDescription: CDK bootstrap stack version 6 required" (#31092)
### Issue # (if applicable) Closes #17942. ### Reason for this change The CDK CLI shows the stack template, which includes the CFN Rule `CheckBootstrapVersion`. This rule will fail a deployment if the bootstrap is not right. Customers think this rule is an error message. ### Description of changes Obscure this `CheckBootstrapVersion` Rule from the template when we print it, if it exists. If it is the only Rule, remove the `Rules` section entirely. ### Description of how you validated changes Manual testing, unit tests, and CLI integration tests. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent f2babd9 commit 751a922

File tree

6 files changed

+160
-15
lines changed

6 files changed

+160
-15
lines changed

packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts

+6
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ integTest('cdk synth', withDefaultFixture(async (fixture) => {
102102
},
103103
}));
104104

105+
expect(await fixture.cdkSynth({
106+
options: [fixture.fullStackName('test-1')],
107+
})).not.toEqual(expect.stringContaining(`
108+
Rules:
109+
CheckBootstrapVersion:`));
110+
105111
await fixture.cdk(['synth', fixture.fullStackName('test-2')], { verbose: false });
106112
expect(fixture.template('test-2')).toEqual(expect.objectContaining({
107113
Resources: {

packages/aws-cdk/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ $ # Diff against the currently deployed stack with quiet parameter enabled
165165
$ cdk diff --quiet --app='node bin/main.js' MyStackName
166166
```
167167

168+
Note that the CDK::Metadata resource and the `CheckBootstrapVersion` Rule are excluded from `cdk diff` by default. You can force `cdk diff` to display them by passing the `--strict` flag.
169+
168170
The `change-set` flag will make `diff` create a change set and extract resource replacement data from it. This is a bit slower, but will provide no false positives for resource replacement.
169171
The `--no-change-set` mode will consider any change to a property that requires replacement to be a resource replacement,
170172
even if the change is purely cosmetic (like replacing a resource reference with a hardcoded arn).

packages/aws-cdk/lib/cdk-toolkit.ts

+31-6
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export class CdkToolkit {
105105

106106
public async metadata(stackName: string, json: boolean) {
107107
const stacks = await this.selectSingleStackByName(stackName);
108-
data(serializeStructure(stacks.firstStack.manifest.metadata ?? {}, json));
108+
printSerializedObject(stacks.firstStack.manifest.metadata ?? {}, json);
109109
}
110110

111111
public async acknowledge(noticeId: string) {
@@ -632,7 +632,7 @@ export class CdkToolkit {
632632
});
633633

634634
if (options.long && options.showDeps) {
635-
data(serializeStructure(stacks, options.json ?? false));
635+
printSerializedObject(stacks, options.json ?? false);
636636
return 0;
637637
}
638638

@@ -646,7 +646,7 @@ export class CdkToolkit {
646646
});
647647
}
648648

649-
data(serializeStructure(stackDeps, options.json ?? false));
649+
printSerializedObject(stackDeps, options.json ?? false);
650650
return 0;
651651
}
652652

@@ -660,7 +660,7 @@ export class CdkToolkit {
660660
environment: stack.environment,
661661
});
662662
}
663-
data(serializeStructure(long, options.json ?? false));
663+
printSerializedObject(long, options.json ?? false);
664664
return 0;
665665
}
666666

@@ -687,7 +687,7 @@ export class CdkToolkit {
687687
// if we have a single stack, print it to STDOUT
688688
if (stacks.stackCount === 1) {
689689
if (!quiet) {
690-
data(serializeStructure(stacks.firstStack.template, json ?? false));
690+
printSerializedObject(obscureTemplate(stacks.firstStack.template), json ?? false);
691691
}
692692
return undefined;
693693
}
@@ -701,7 +701,7 @@ export class CdkToolkit {
701701
// behind an environment variable.
702702
const isIntegMode = process.env.CDK_INTEG_MODE === '1';
703703
if (isIntegMode) {
704-
data(serializeStructure(stacks.stackArtifacts.map(s => s.template), json ?? false));
704+
printSerializedObject(stacks.stackArtifacts.map(s => obscureTemplate(s.template)), json ?? false);
705705
}
706706

707707
// not outputting template to stdout, let's explain things to the user a little bit...
@@ -1045,6 +1045,13 @@ export class CdkToolkit {
10451045
}
10461046
}
10471047

1048+
/**
1049+
* Print a serialized object (YAML or JSON) to stdout.
1050+
*/
1051+
function printSerializedObject(obj: any, json: boolean) {
1052+
data(serializeStructure(obj, json));
1053+
}
1054+
10481055
export interface DiffOptions {
10491056
/**
10501057
* Stack names to diff
@@ -1526,3 +1533,21 @@ function buildParameterMap(parameters: {
15261533

15271534
return parameterMap;
15281535
}
1536+
1537+
/**
1538+
* Remove any template elements that we don't want to show users.
1539+
*/
1540+
function obscureTemplate(template: any = {}) {
1541+
if (template.Rules) {
1542+
// see https://github.com/aws/aws-cdk/issues/17942
1543+
if (template.Rules.CheckBootstrapVersion) {
1544+
if (Object.keys(template.Rules).length > 1) {
1545+
delete template.Rules.CheckBootstrapVersion;
1546+
} else {
1547+
delete template.Rules;
1548+
}
1549+
}
1550+
}
1551+
1552+
return template;
1553+
}

packages/aws-cdk/lib/cli.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ async function parseCommandLineArguments(args: string[]) {
260260
.option('exclusively', { type: 'boolean', alias: 'e', desc: 'Only diff requested stacks, don\'t include dependencies' })
261261
.option('context-lines', { type: 'number', desc: 'Number of context lines to include in arbitrary JSON diff rendering', default: 3, requiresArg: true })
262262
.option('template', { type: 'string', desc: 'The path to the CloudFormation template to compare with', requiresArg: true })
263-
.option('strict', { type: 'boolean', desc: 'Do not filter out AWS::CDK::Metadata resources or mangled non-ASCII characters', default: false })
263+
.option('strict', { type: 'boolean', desc: 'Do not filter out AWS::CDK::Metadata resources, mangled non-ASCII characters, or the CheckBootstrapVersionRule', default: false })
264264
.option('security-only', { type: 'boolean', desc: 'Only diff for broadened security changes', default: false })
265265
.option('fail', { type: 'boolean', desc: 'Fail with exit code 1 in case of diff' })
266266
.option('processed', { type: 'boolean', desc: 'Whether to compare against the template with Transforms already processed', default: false })

packages/aws-cdk/lib/diff.ts

+31-8
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { print, warning } from './logging';
1919
*
2020
* @param oldTemplate the old/current state of the stack.
2121
* @param newTemplate the new/target state of the stack.
22-
* @param strict do not filter out AWS::CDK::Metadata
22+
* @param strict do not filter out AWS::CDK::Metadata or Rules
2323
* @param context lines of context to use in arbitrary JSON diff
2424
* @param quiet silences \'There were no differences\' messages
2525
*
@@ -50,13 +50,9 @@ export function printStackDiff(
5050
}
5151

5252
// filter out 'AWS::CDK::Metadata' resources from the template
53-
if (diff.resources && !strict) {
54-
diff.resources = diff.resources.filter(change => {
55-
if (!change) { return true; }
56-
if (change.newResourceType === 'AWS::CDK::Metadata') { return false; }
57-
if (change.oldResourceType === 'AWS::CDK::Metadata') { return false; }
58-
return true;
59-
});
53+
// filter out 'CheckBootstrapVersion' rules from the template
54+
if (!strict) {
55+
obscureDiff(diff);
6056
}
6157

6258
let stackDiffCount = 0;
@@ -165,3 +161,30 @@ function logicalIdMapFromTemplate(template: any) {
165161
}
166162
return ret;
167163
}
164+
165+
/**
166+
* Remove any template elements that we don't want to show users.
167+
* This is currently:
168+
* - AWS::CDK::Metadata resource
169+
* - CheckBootstrapVersion Rule
170+
*/
171+
function obscureDiff(diff: TemplateDiff) {
172+
if (diff.unknown) {
173+
// see https://github.com/aws/aws-cdk/issues/17942
174+
diff.unknown = diff.unknown.filter(change => {
175+
if (!change) { return true; }
176+
if (change.newValue?.CheckBootstrapVersion) { return false; }
177+
if (change.oldValue?.CheckBootstrapVersion) { return false; }
178+
return true;
179+
});
180+
}
181+
182+
if (diff.resources) {
183+
diff.resources = diff.resources.filter(change => {
184+
if (!change) { return true; }
185+
if (change.newResourceType === 'AWS::CDK::Metadata') { return false; }
186+
if (change.oldResourceType === 'AWS::CDK::Metadata') { return false; }
187+
return true;
188+
});
189+
}
190+
}

packages/aws-cdk/test/diff.test.ts

+89
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,95 @@ Resources
855855
});
856856
});
857857

858+
describe('--strict', () => {
859+
const templatePath = 'oldTemplate.json';
860+
beforeEach(() => {
861+
const oldTemplate = {};
862+
863+
cloudExecutable = new MockCloudExecutable({
864+
stacks: [{
865+
stackName: 'A',
866+
template: {
867+
Resources: {
868+
MetadataResource: {
869+
Type: 'AWS::CDK::Metadata',
870+
Properties: {
871+
newMeta: 'newData',
872+
},
873+
},
874+
SomeOtherResource: {
875+
Type: 'AWS::Something::Amazing',
876+
},
877+
},
878+
Rules: {
879+
CheckBootstrapVersion: {
880+
newCheck: 'newBootstrapVersion',
881+
},
882+
},
883+
},
884+
}],
885+
});
886+
887+
toolkit = new CdkToolkit({
888+
cloudExecutable,
889+
deployments: cloudFormation,
890+
configuration: cloudExecutable.configuration,
891+
sdkProvider: cloudExecutable.sdkProvider,
892+
});
893+
894+
fs.writeFileSync(templatePath, JSON.stringify(oldTemplate));
895+
});
896+
897+
afterEach(() => fs.rmSync(templatePath));
898+
899+
test('--strict does not obscure CDK::Metadata or CheckBootstrapVersion', async () => {
900+
// GIVEN
901+
const buffer = new StringWritable();
902+
903+
// WHEN
904+
const exitCode = await toolkit.diff({
905+
stackNames: ['A'],
906+
stream: buffer,
907+
strict: true,
908+
});
909+
910+
// THEN
911+
const plainTextOutput = buffer.data.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '');
912+
expect(plainTextOutput.trim()).toEqual(`Stack A
913+
Resources
914+
[+] AWS::CDK::Metadata MetadataResource
915+
[+] AWS::Something::Amazing SomeOtherResource
916+
917+
Other Changes
918+
[+] Unknown Rules: {\"CheckBootstrapVersion\":{\"newCheck\":\"newBootstrapVersion\"}}
919+
920+
921+
✨ Number of stacks with differences: 1`);
922+
expect(exitCode).toBe(0);
923+
});
924+
925+
test('--no-strict obscures CDK::Metadata and CheckBootstrapVersion', async () => {
926+
// GIVEN
927+
const buffer = new StringWritable();
928+
929+
// WHEN
930+
const exitCode = await toolkit.diff({
931+
stackNames: ['A'],
932+
stream: buffer,
933+
});
934+
935+
// THEN
936+
const plainTextOutput = buffer.data.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '');
937+
expect(plainTextOutput.trim()).toEqual(`Stack A
938+
Resources
939+
[+] AWS::Something::Amazing SomeOtherResource
940+
941+
942+
✨ Number of stacks with differences: 1`);
943+
expect(exitCode).toBe(0);
944+
});
945+
});
946+
858947
class StringWritable extends Writable {
859948
public data: string;
860949
private readonly _decoder: StringDecoder;

0 commit comments

Comments
 (0)