Skip to content

Commit

Permalink
feat: embed construct paths in CloudFormation metadata (aws#1183)
Browse files Browse the repository at this point in the history
In order to allow tools that read a CloudFormation template created by
the CDK to be able to present the CDK path of resources in the template,
the CDK now embeds the full path as CloudFormation metadata "aws:cdk:path"
entry for each resource.

To disable this behavior use the switch `--no-path-metadata` or set
`pathMetadata` to `false` in `cdk.json` or `~/.cdk.json`.

The toolkit can control this behavior by setting the
"aws:cdk:enable-path-metadata" context key. It sets it to `true` by
default.

The default behavior of the *Resource class* is _not_ to include
metadata. This is in order to maintain backwards compatibility
for tests. `cdk-integ` also disables this by default.

Fixes aws#1182
Related aws#1121
  • Loading branch information
Elad Ben-Israel authored Nov 15, 2018
1 parent d397dd7 commit 3cc2fff
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 10 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ coverage
.nyc_output
.LAST_BUILD
*.swp
tsconfig.json

# we don't want tsconfig at the root
/tsconfig.json
10 changes: 10 additions & 0 deletions packages/@aws-cdk/cdk/lib/cloudformation/resource.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import cxapi = require('@aws-cdk/cx-api');
import { Construct } from '../core/construct';
import { capitalizePropertyNames, ignoreEmpty } from '../core/util';
import { CloudFormationToken } from './cloudformation-token';
Expand Down Expand Up @@ -86,6 +87,15 @@ export class Resource extends Referenceable {

this.resourceType = props.type;
this.properties = props.properties || { };

// if aws:cdk:enable-path-metadata is set, embed the current construct's
// path in the CloudFormation template, so it will be possible to trace
// back to the actual construct path.
if (this.getContext(cxapi.PATH_METADATA_ENABLE_CONTEXT)) {
this.options.metadata = {
[cxapi.PATH_METADATA_KEY]: this.path
};
}
}

/**
Expand Down
19 changes: 19 additions & 0 deletions packages/@aws-cdk/cdk/test/cloudformation/test.resource.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import cxapi = require('@aws-cdk/cx-api');
import { Test } from 'nodeunit';
import { applyRemovalPolicy, Condition, Construct, DeletionPolicy,
FnEquals, FnNot, HashedAddressingScheme, IDependable,
Expand Down Expand Up @@ -570,6 +571,24 @@ export = {
test.done();
}
}
},

'"aws:cdk:path" metadata is added if "aws:cdk:path-metadata" context is set to true'(test: Test) {
const stack = new Stack();
stack.setContext(cxapi.PATH_METADATA_ENABLE_CONTEXT, true);

const parent = new Construct(stack, 'Parent');

new Resource(parent, 'MyResource', {
type: 'MyResourceType',
});

test.deepEqual(stack.toCloudFormation(), { Resources:
{ ParentMyResource4B1FDBCC:
{ Type: 'MyResourceType',
Metadata: { [cxapi.PATH_METADATA_KEY]: 'Parent/MyResource' } } } });

test.done();
}
};

Expand Down
11 changes: 11 additions & 0 deletions packages/@aws-cdk/cx-api/lib/cxapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,17 @@ export const WARNING_METADATA_KEY = 'aws:cdk:warning';
*/
export const ERROR_METADATA_KEY = 'aws:cdk:error';

/**
* The key used when CDK path is embedded in **CloudFormation template**
* metadata.
*/
export const PATH_METADATA_KEY = 'aws:cdk:path';

/**
* Enables the embedding of the "aws:cdk:path" in CloudFormation template metadata.
*/
export const PATH_METADATA_ENABLE_CONTEXT = 'aws:cdk:enable-path-metadata';

/**
* Separator string that separates the prefix separator from the object key separator.
*
Expand Down
14 changes: 10 additions & 4 deletions packages/aws-cdk/bin/cdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { data, debug, error, highlight, print, setVerbose, success, warning } fr
import { PluginHost } from '../lib/plugin';
import { parseRenames } from '../lib/renames';
import { deserializeStructure, serializeStructure } from '../lib/serialize';
import { loadProjectConfig, loadUserConfig, PER_USER_DEFAULTS, saveProjectConfig, Settings } from '../lib/settings';
import { DEFAULTS, loadProjectConfig, loadUserConfig, PER_USER_DEFAULTS, saveProjectConfig, Settings } from '../lib/settings';
import { VERSION } from '../lib/version';

// tslint:disable-next-line:no-var-requires
Expand Down Expand Up @@ -50,7 +50,8 @@ async function parseCommandLineArguments() {
.option('profile', { type: 'string', desc: 'Use the indicated AWS profile as the default environment' })
.option('proxy', { type: 'string', desc: 'Use the indicated proxy. Will read from HTTPS_PROXY environment variable if not specified.' })
.option('ec2creds', { type: 'boolean', alias: 'i', default: undefined, desc: 'Force trying to fetch EC2 instance credentials. Default: guess EC2 instance status.' })
.option('version-reporting', { type: 'boolean', desc: 'Disable insersion of the CDKMetadata resource in synthesized templates', default: undefined })
.option('version-reporting', { type: 'boolean', desc: 'Include the "AWS::CDK::Metadata" resource in synthesized templates (enabled by default)', default: undefined })
.option('path-metadata', { type: 'boolean', desc: 'Include "aws:cdk:path" CloudFormation metadata for each resource (enabled by default)', default: true })
.option('role-arn', { type: 'string', alias: 'r', desc: 'ARN of Role to use when invoking CloudFormation', default: undefined })
.command([ 'list', 'ls' ], 'Lists all stacks in the app', yargs => yargs
.option('long', { type: 'boolean', default: false, alias: 'l', desc: 'display environment information for each stack' }))
Expand Down Expand Up @@ -137,7 +138,7 @@ async function initCommandLine() {
ec2creds: argv.ec2creds,
});

const defaultConfig = new Settings({ versionReporting: true });
const defaultConfig = new Settings({ versionReporting: true, pathMetadata: true });
const userConfig = await loadUserConfig();
const projectConfig = await loadProjectConfig();
const commandLineArguments = argumentsToSettings();
Expand Down Expand Up @@ -645,7 +646,11 @@ async function initCommandLine() {
function logDefaults() {
if (!userConfig.empty()) {
debug('Defaults loaded from ', PER_USER_DEFAULTS, ':', JSON.stringify(userConfig.settings, undefined, 2));
debug(PER_USER_DEFAULTS + ':', JSON.stringify(userConfig.settings, undefined, 2));
}
if (!projectConfig.empty()) {
debug(DEFAULTS + ':', JSON.stringify(projectConfig.settings, undefined, 2));
}
const combined = userConfig.merge(projectConfig);
Expand Down Expand Up @@ -680,6 +685,7 @@ async function initCommandLine() {
plugin: argv.plugin,
toolkitStackName: argv.toolkitStackName,
versionReporting: argv.versionReporting,
pathMetadata: argv.pathMetadata,
});
}

Expand Down
11 changes: 11 additions & 0 deletions packages/aws-cdk/lib/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ export async function execProgram(aws: SDK, config: Settings): Promise<cxapi.Syn
const context = config.get(['context']);
await populateDefaultEnvironmentIfNeeded(aws, context);

let pathMetadata: boolean = config.get(['pathMetadata']);
if (pathMetadata === undefined) {
pathMetadata = true; // default to true
}

if (pathMetadata) {
context[cxapi.PATH_METADATA_ENABLE_CONTEXT] = true;
}

debug('context:', context);

env[cxapi.CONTEXT_ENV] = JSON.stringify(context);

const app = config.get(['app']);
Expand Down
2 changes: 1 addition & 1 deletion tools/cdk-integ-tools/bin/cdk-integ-assert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ async function main() {
}

const expected = await test.readExpected();
const actual = await test.invoke(['--json', 'synth'], { json: true, context: STATIC_TEST_CONTEXT });
const actual = await test.invoke(['--json', '--no-path-metadata', 'synth'], { json: true, context: STATIC_TEST_CONTEXT });

const diff = diffTemplate(expected, actual);

Expand Down
15 changes: 11 additions & 4 deletions tools/cdk-integ-tools/bin/cdk-integ.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,23 @@ async function main() {
for (const test of tests) {
console.error(`Trying to deploy ${test.name}`);

// injects "--verbose" to the command line of "cdk" if we are in verbose mode
const makeArgs = (...args: string[]) => !argv.verbose ? args : [ '--verbose', ...args ];
const args = new Array<string>();

// inject "--no-path-metadata" so aws:cdk:path entries are not added to CFN metadata
args.push('--no-path-metadata');

// inject "--verbose" to the command line of "cdk" if we are in verbose mode
if (argv.verbose) {
args.push('--verbose');
}

try {
await test.invoke(makeArgs('deploy'), { verbose: argv.verbose }); // Note: no context, so use default user settings!
await test.invoke([ ...args, 'deploy' ], { verbose: argv.verbose }); // Note: no context, so use default user settings!

console.error(`Success! Writing out reference synth.`);

// If this all worked, write the new expectation file
const actual = await test.invoke(makeArgs('--json', 'synth'), {
const actual = await test.invoke([ ...args, '--json', 'synth' ], {
json: true,
context: STATIC_TEST_CONTEXT,
verbose: argv.verbose
Expand Down

0 comments on commit 3cc2fff

Please sign in to comment.