From 513b248feb5dcd815106ccf9f2387db055a22821 Mon Sep 17 00:00:00 2001 From: Marcel Laverdet Date: Thu, 9 Mar 2023 15:18:27 -0600 Subject: [PATCH] fix(cli): hide diffs of mangled unicode strings CloudFormation's `GetStackTemplate` irrecoverably mangles any character not in the 7-bit ASCII range. This causes noisy output from `cdk diff` when a template contains non-English languages or emoji. We can detect this case and consider these strings equal. This can be disabled by passing `--strict`. *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../cloudformation-diff/lib/diff/util.ts | 12 ++++++++++++ .../@aws-cdk/cloudformation-diff/lib/index.ts | 2 +- .../cloudformation-diff/test/util-test.ts | 10 ++++++++++ packages/aws-cdk/lib/cli.ts | 2 +- packages/aws-cdk/lib/diff.ts | 16 +++++++++++++++- 5 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 packages/@aws-cdk/cloudformation-diff/test/util-test.ts diff --git a/packages/@aws-cdk/cloudformation-diff/lib/diff/util.ts b/packages/@aws-cdk/cloudformation-diff/lib/diff/util.ts index 1cbd4b1a111d7..8b076dd36221e 100644 --- a/packages/@aws-cdk/cloudformation-diff/lib/diff/util.ts +++ b/packages/@aws-cdk/cloudformation-diff/lib/diff/util.ts @@ -134,6 +134,18 @@ export function unionOf(lv: string[] | Set, rv: string[] | Set): return new Array(...result); } +/** + * GetStackTemplate flattens any codepoint greater than "\u7f" to "?". This is + * true even for codepoints in the supplemental planes which are represented + * in JS as surrogate pairs, all the way up to "\u{10ffff}". + * + * This function implements the same mangling in order to provide diagnostic + * information in `cdk diff`. + */ +export function mangleLikeCloudFormation(payload: string) { + return payload.replace(/[\u{80}-\u{10ffff}]/gu, '?'); +} + /** * A parseFloat implementation that does the right thing for * strings like '0.0.0' diff --git a/packages/@aws-cdk/cloudformation-diff/lib/index.ts b/packages/@aws-cdk/cloudformation-diff/lib/index.ts index 34a07c7559fb7..9d42f22b882b9 100644 --- a/packages/@aws-cdk/cloudformation-diff/lib/index.ts +++ b/packages/@aws-cdk/cloudformation-diff/lib/index.ts @@ -1,4 +1,4 @@ export * from './diff-template'; export * from './format'; export * from './format-table'; -export { deepEqual } from './diff/util'; +export { deepEqual, mangleLikeCloudFormation } from './diff/util'; diff --git a/packages/@aws-cdk/cloudformation-diff/test/util-test.ts b/packages/@aws-cdk/cloudformation-diff/test/util-test.ts new file mode 100644 index 0000000000000..b95f44f48c62f --- /dev/null +++ b/packages/@aws-cdk/cloudformation-diff/test/util-test.ts @@ -0,0 +1,10 @@ +import { mangleLikeCloudFormation } from '../lib/diff/util'; + +test('mangled strings', () => { + expect(mangleLikeCloudFormation('foo')).toEqual('foo'); + expect(mangleLikeCloudFormation('文字化け')).toEqual('????'); + expect(mangleLikeCloudFormation('🤦🏻‍♂️')).toEqual('?????'); + expect(mangleLikeCloudFormation('\u{10ffff}')).toEqual('?'); + expect(mangleLikeCloudFormation('\u007f')).toEqual('\u007f'); + expect(mangleLikeCloudFormation('\u0080')).toEqual('?'); +}); diff --git a/packages/aws-cdk/lib/cli.ts b/packages/aws-cdk/lib/cli.ts index f24f42231beb5..85ac53137a4b7 100644 --- a/packages/aws-cdk/lib/cli.ts +++ b/packages/aws-cdk/lib/cli.ts @@ -257,7 +257,7 @@ async function parseCommandLineArguments(args: string[]) { .option('exclusively', { type: 'boolean', alias: 'e', desc: 'Only diff requested stacks, don\'t include dependencies' }) .option('context-lines', { type: 'number', desc: 'Number of context lines to include in arbitrary JSON diff rendering', default: 3, requiresArg: true }) .option('template', { type: 'string', desc: 'The path to the CloudFormation template to compare with', requiresArg: true }) - .option('strict', { type: 'boolean', desc: 'Do not filter out AWS::CDK::Metadata resources', default: false }) + .option('strict', { type: 'boolean', desc: 'Do not filter out AWS::CDK::Metadata resources or mangled non-ASCII characters', default: false }) .option('security-only', { type: 'boolean', desc: 'Only diff for broadened security changes', default: false }) .option('fail', { type: 'boolean', desc: 'Fail with exit code 1 in case of diff' }) .option('processed', { type: 'boolean', desc: 'Whether to compare against the template with Transforms already processed', default: false })) diff --git a/packages/aws-cdk/lib/diff.ts b/packages/aws-cdk/lib/diff.ts index 8566083efa48a..08a67e7567c8c 100644 --- a/packages/aws-cdk/lib/diff.ts +++ b/packages/aws-cdk/lib/diff.ts @@ -21,7 +21,18 @@ export function printStackDiff( context: number, stream?: cfnDiff.FormatStream): number { - const diff = cfnDiff.diffTemplate(oldTemplate, newTemplate.template); + let diff = cfnDiff.diffTemplate(oldTemplate, newTemplate.template); + + // detect and filter out mangled characters from the diff + let filteredChangesCount = 0; + if (diff.differenceCount && !strict) { + const mangledNewTemplate = JSON.parse(cfnDiff.mangleLikeCloudFormation(JSON.stringify(newTemplate.template))); + const mangledDiff = cfnDiff.diffTemplate(oldTemplate, mangledNewTemplate); + filteredChangesCount = Math.max(0, diff.differenceCount - mangledDiff.differenceCount); + if (filteredChangesCount > 0) { + diff = mangledDiff; + } + } // filter out 'AWS::CDK::Metadata' resources from the template if (diff.resources && !strict) { @@ -41,6 +52,9 @@ export function printStackDiff( } else { print(chalk.green('There were no differences')); } + if (filteredChangesCount > 0) { + print(chalk.yellow(`Omitted ${filteredChangesCount} changes because they are likely mangled non-ASCII characters. Use --strict to print them.`)); + } return diff.differenceCount; }