Skip to content

Commit

Permalink
feat(cli): always exit with 0 on cdk diff (under feature flag) (aws#4721
Browse files Browse the repository at this point in the history
)

* feat(cli): add --no-fail option to diff

Use `cdk diff --no-fail` to exit with 0 even when there is a diff.

To avoid a CLI breaking change, the default remains to fail on diff.

Related to aws#4650 and aws#4708

* use feature flag
  • Loading branch information
jogold authored and mergify[bot] committed Nov 28, 2019
1 parent c1b575f commit 3ffd810
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 18 deletions.
9 changes: 8 additions & 1 deletion packages/@aws-cdk/cx-api/lib/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,11 @@ export const DISABLE_METADATA_STACK_TRACE = 'aws:cdk:disable-stack-trace';
* compatibility, but new projects created using `cdk init` will have this
* enabled through the generated `cdk.json`.
*/
export const ENABLE_STACK_NAME_DUPLICATES_CONTEXT = '@aws-cdk/core:enableStackNameDuplicates';
export const ENABLE_STACK_NAME_DUPLICATES_CONTEXT = '@aws-cdk/core:enableStackNameDuplicates';

/**
* IF this is set, `cdk diff` will always exit with 0.
*
* Use `cdk diff --fail` to exit with 1 if there's a diff.
*/
export const ENABLE_DIFF_NO_FAIL = 'aws-cdk:enableDiffNoFail';
7 changes: 4 additions & 3 deletions packages/@aws-cdk/cx-api/lib/future.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ENABLE_STACK_NAME_DUPLICATES_CONTEXT } from "./features";
import { ENABLE_DIFF_NO_FAIL, ENABLE_STACK_NAME_DUPLICATES_CONTEXT } from "./features";

/**
* This map includes context keys and values for feature flags that enable
Expand All @@ -14,5 +14,6 @@ import { ENABLE_STACK_NAME_DUPLICATES_CONTEXT } from "./features";
* Tests must cover the default (disabled) case and the future (enabled) case.
*/
export const FUTURE_FLAGS = {
[ENABLE_STACK_NAME_DUPLICATES_CONTEXT]: 'true'
};
[ENABLE_STACK_NAME_DUPLICATES_CONTEXT]: 'true',
[ENABLE_DIFF_NO_FAIL]: 'true',
};
5 changes: 4 additions & 1 deletion packages/aws-cdk/bin/cdk.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env node
import 'source-map-support/register';

import cxapi = require('@aws-cdk/cx-api');
import colors = require('colors/safe');
import path = require('path');
import yargs = require('yargs');
Expand Down Expand Up @@ -72,6 +73,7 @@ async function parseCommandLineArguments() {
.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('fail', { type: 'boolean', desc: 'Fail with exit code 1 in case of diff', 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 })
Expand Down Expand Up @@ -191,7 +193,8 @@ async function initCommandLine() {
exclusively: args.exclusively,
templatePath: args.template,
strict: args.strict,
contextLines: args.contextLines
contextLines: args.contextLines,
fail: args.fail || !configuration.context.get(cxapi.ENABLE_DIFF_NO_FAIL),
});

case 'bootstrap':
Expand Down
17 changes: 11 additions & 6 deletions packages/aws-cdk/lib/cdk-toolkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class CdkToolkit {
const contextLines = options.contextLines || 3;
const stream = options.stream || process.stderr;

let ret = 0;
let diffs = 0;
if (options.templatePath !== undefined) {
// Compare single stack against fixed template
if (stacks.length !== 1) {
Expand All @@ -62,19 +62,17 @@ export class CdkToolkit {
throw new Error(`There is no file at ${options.templatePath}`);
}
const template = deserializeStructure(await fs.readFile(options.templatePath, { encoding: 'UTF-8' }));
ret = printStackDiff(template, stacks[0], strict, contextLines, options.stream);
diffs = printStackDiff(template, stacks[0], strict, contextLines, stream);
} else {
// Compare N stacks against deployed templates
for (const stack of stacks) {
stream.write(format('Stack %s\n', colors.bold(stack.displayName)));
const currentTemplate = await this.provisioner.readCurrentTemplate(stack);
if (printStackDiff(currentTemplate, stack, !!options.strict, options.contextLines || 3, stream) !== 0) {
ret = 1;
}
diffs = printStackDiff(currentTemplate, stack, strict, contextLines, stream);
}
}

return ret;
return diffs && options.fail ? 1 : 0;
}

public async deploy(options: DeployOptions) {
Expand Down Expand Up @@ -244,6 +242,13 @@ export interface DiffOptions {
* @default stderr
*/
stream?: NodeJS.WritableStream;

/**
* Whether to fail with exit code 1 in case of diff
*
* @default false
*/
fail?: boolean;
}

export interface DeployOptions {
Expand Down
5 changes: 4 additions & 1 deletion packages/aws-cdk/test/integ/cli/app/cdk.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"app": "node app.js",
"versionReporting": false
"versionReporting": false,
"context": {
"aws-cdk:enableDiffNoFail": "true"
}
}
13 changes: 8 additions & 5 deletions packages/aws-cdk/test/integ/cli/test-cdk-diff.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ source ${scriptdir}/common.bash

setup

function cdk_diff() {
cdk diff $1 2>&1 || true
}
cdk diff ${STACK_NAME_PREFIX}-test-1 2>&1 | grep "AWS::SNS::Topic"
cdk diff ${STACK_NAME_PREFIX}-test-2 2>&1 | grep "AWS::SNS::Topic"

cdk_diff ${STACK_NAME_PREFIX}-test-1 | grep "AWS::SNS::Topic"
cdk_diff ${STACK_NAME_PREFIX}-test-2 | grep "AWS::SNS::Topic"
failed=0
cdk diff --fail ${STACK_NAME_PREFIX}-test-1 2>&1 || failed=1

if [ $failed -ne 1 ]; then
fail 'cdk diff with --fail does not fail'
fi

echo "✅ success"
2 changes: 1 addition & 1 deletion packages/aws-cdk/test/integ/cli/test-cdk-iam-diff.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ source ${scriptdir}/common.bash
setup

function nonfailing_diff() {
( cdk diff $1 2>&1 || true ) | strip_color_codes
cdk diff $1 2>&1 | strip_color_codes
}

assert "nonfailing_diff ${STACK_NAME_PREFIX}-iam-test" <<HERE
Expand Down
26 changes: 26 additions & 0 deletions packages/aws-cdk/test/test.diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,32 @@ export = {
test.ok(plainTextOutput.indexOf('Stack A') > -1, `Did not contain "Stack A": ${plainTextOutput}`);
test.ok(plainTextOutput.indexOf('Stack B') > -1, `Did not contain "Stack B": ${plainTextOutput}`);

test.equals(0, exitCode);

test.done();
},

async 'exits with 1 with diffs and fail set to true'(test: Test) {
// GIVEN
const provisioner: IDeploymentTarget = {
async readCurrentTemplate(_stack: cxapi.CloudFormationStackArtifact): Promise<Template> {
return {};
},
async deployStack(_options: DeployStackOptions): Promise<DeployStackResult> {
return { noOp: true, outputs: {}, stackArn: ''};
}
};
const toolkit = new CdkToolkit({ appStacks, provisioner });
const buffer = new StringWritable();

// WHEN
const exitCode = await toolkit.diff({
stackNames: ['A'],
stream: buffer,
fail: true
});

// THEN
test.equals(1, exitCode);

test.done();
Expand Down

0 comments on commit 3ffd810

Please sign in to comment.