Skip to content

Commit

Permalink
fix(cli): hide diffs of mangled unicode strings
Browse files Browse the repository at this point in the history
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*
  • Loading branch information
laverdet committed Mar 9, 2023
1 parent 3bd973a commit 513b248
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 3 deletions.
12 changes: 12 additions & 0 deletions packages/@aws-cdk/cloudformation-diff/lib/diff/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,18 @@ export function unionOf(lv: string[] | Set<string>, rv: string[] | Set<string>):
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'
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/cloudformation-diff/lib/index.ts
Original file line number Diff line number Diff line change
@@ -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';
10 changes: 10 additions & 0 deletions packages/@aws-cdk/cloudformation-diff/test/util-test.ts
Original file line number Diff line number Diff line change
@@ -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('?');
});
2 changes: 1 addition & 1 deletion packages/aws-cdk/lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }))
Expand Down
16 changes: 15 additions & 1 deletion packages/aws-cdk/lib/diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
}
Expand Down

0 comments on commit 513b248

Please sign in to comment.