From ef0017acc224cef0ce88428eccd4403433964a82 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Sun, 18 Nov 2018 14:11:42 +0200 Subject: [PATCH] feat(toolkit): by default hide AWS::CDK::Metadata from "cdk diff" (#1186) `DifferenceCollection#filter` returns a new collection with changes filtered. Changed `DifferenceCollection#count` to do a lazy calculation since now `changes` is mutable. The toolkit switch `cdk diff --strict` disables this behavior. Fixes #465 --- .../cloudformation-diff/lib/diff/types.ts | 78 ++++++++++++------- packages/aws-cdk/bin/cdk.ts | 9 ++- packages/aws-cdk/lib/diff.ts | 13 +++- 3 files changed, 66 insertions(+), 34 deletions(-) diff --git a/packages/@aws-cdk/cloudformation-diff/lib/diff/types.ts b/packages/@aws-cdk/cloudformation-diff/lib/diff/types.ts index f921531e39919..1a6c54f8ac6d9 100644 --- a/packages/@aws-cdk/cloudformation-diff/lib/diff/types.ts +++ b/packages/@aws-cdk/cloudformation-diff/lib/diff/types.ts @@ -3,57 +3,60 @@ import { deepEqual } from './util'; /** Semantic differences between two CloudFormation templates. */ export class TemplateDiff implements ITemplateDiff { - public readonly awsTemplateFormatVersion?: Difference; - public readonly description?: Difference; - public readonly transform?: Difference; - public readonly conditions: DifferenceCollection; - public readonly mappings: DifferenceCollection; - public readonly metadata: DifferenceCollection; - public readonly outputs: DifferenceCollection; - public readonly parameters: DifferenceCollection; - public readonly resources: DifferenceCollection; + public awsTemplateFormatVersion?: Difference; + public description?: Difference; + public transform?: Difference; + public conditions: DifferenceCollection; + public mappings: DifferenceCollection; + public metadata: DifferenceCollection; + public outputs: DifferenceCollection; + public parameters: DifferenceCollection; + public resources: DifferenceCollection; /** The differences in unknown/unexpected parts of the template */ - public readonly unknown: DifferenceCollection>; - - public readonly count: number; + public unknown: DifferenceCollection>; constructor(args: ITemplateDiff) { - let count = 0; if (args.awsTemplateFormatVersion !== undefined) { this.awsTemplateFormatVersion = args.awsTemplateFormatVersion; - count += 1; } if (args.description !== undefined) { this.description = args.description; - count += 1; } if (args.transform !== undefined) { this.transform = args.transform; - count += 1; } this.conditions = args.conditions || new DifferenceCollection({}); - count += this.conditions.count; - this.mappings = args.mappings || new DifferenceCollection({}); - count += this.mappings.count; - this.metadata = args.metadata || new DifferenceCollection({}); - count += this.metadata.count; - this.outputs = args.outputs || new DifferenceCollection({}); - count += this.outputs.count; - this.parameters = args.parameters || new DifferenceCollection({}); - count += this.parameters.count; - this.resources = args.resources || new DifferenceCollection({}); - count += this.resources.count; - this.unknown = args.unknown || new DifferenceCollection({}); + } + + public get count() { + let count = 0; + + if (this.awsTemplateFormatVersion !== undefined) { + count += 1; + } + if (this.description !== undefined) { + count += 1; + } + if (this.transform !== undefined) { + count += 1; + } + + count += this.conditions.count; + count += this.mappings.count; + count += this.metadata.count; + count += this.outputs.count; + count += this.parameters.count; + count += this.resources.count; count += this.unknown.count; - this.count = count; + return count; } public get isEmpty(): boolean { @@ -117,6 +120,23 @@ export class DifferenceCollection> { return Object.keys(this.changes); } + /** + * Returns a new TemplateDiff which only contains changes for which `predicate` + * returns `true`. + */ + public filter(predicate: (diff: T | undefined) => boolean): DifferenceCollection { + const newChanges: { [logicalId: string]: T | undefined } = { }; + for (const id of Object.keys(this.changes)) { + const diff = this.changes[id]; + + if (predicate(diff)) { + newChanges[id] = diff; + } + } + + return new DifferenceCollection(newChanges); + } + public forEach(cb: (logicalId: string, change: T) => any): void { for (const logicalId of this.logicalIds) { cb(logicalId, this.changes[logicalId]!); diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index 5844934a47c74..2a26c19fa7a83 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -57,7 +57,8 @@ async function parseCommandLineArguments() { .command('destroy [STACKS..]', 'Destroy the stack(s) named STACKS', yargs => yargs .option('force', { type: 'boolean', alias: 'f', desc: 'Do not ask for confirmation before destroying the stacks' })) .command('diff [STACK]', 'Compares the specified stack with the deployed stack or a local template file', yargs => yargs - .option('template', { type: 'string', desc: 'the path to the CloudFormation template to compare with' })) + .option('template', { type: 'string', desc: 'the path to the CloudFormation template to compare with' }) + .option('strict', { type: 'boolean', desc: 'do not filter out AWS::CDK::Metadata resources', default: false })) .command('metadata [STACK]', 'Returns all metadata associated with this stack') .command('init [TEMPLATE]', 'Create a new, empty CDK project from a template. Invoked without TEMPLATE, the app template will be used.', yargs => yargs .option('language', { type: 'string', alias: 'l', desc: 'the language to be used for the new project (default can be configured in ~/.cdk.json)', choices: initTemplateLanuages }) @@ -187,7 +188,7 @@ async function initCommandLine() { return await cliList({ long: args.long }); case 'diff': - return await diffStack(await findStack(args.STACK), args.template); + return await diffStack(await findStack(args.STACK), args.template, args.strict); case 'bootstrap': return await cliBootstrap(args.ENVIRONMENTS, toolkitStackName, args.roleArn); @@ -394,10 +395,10 @@ async function initCommandLine() { } } - async function diffStack(stackName: string, templatePath?: string): Promise { + async function diffStack(stackName: string, templatePath: string | undefined, strict: boolean): Promise { const stack = await appStacks.synthesizeStack(stackName); const currentTemplate = await readCurrentTemplate(stack, templatePath); - if (printStackDiff(currentTemplate, stack) === 0) { + if (printStackDiff(currentTemplate, stack, strict) === 0) { return 0; } else { return 1; diff --git a/packages/aws-cdk/lib/diff.ts b/packages/aws-cdk/lib/diff.ts index b952790916f7f..bf823da2f97b4 100644 --- a/packages/aws-cdk/lib/diff.ts +++ b/packages/aws-cdk/lib/diff.ts @@ -11,8 +11,19 @@ import { print } from './logging'; * * @returns the count of differences that were rendered. */ -export function printStackDiff(oldTemplate: any, newTemplate: cxapi.SynthesizedStack): number { +export function printStackDiff(oldTemplate: any, newTemplate: cxapi.SynthesizedStack, strict: boolean): number { const diff = cfnDiff.diffTemplate(oldTemplate, newTemplate.template); + + // filter out 'AWS::CDK::Metadata' resources from the template + if (diff.resources && !strict) { + diff.resources = diff.resources.filter(change => { + if (!change) { return true; } + if (change.newResourceType === 'AWS::CDK::Metadata') { return false; } + if (change.oldResourceType === 'AWS::CDK::Metadata') { return false; } + return true; + }); + } + if (!diff.isEmpty) { cfnDiff.formatDifferences(process.stderr, diff); } else {