Skip to content

Commit

Permalink
feat: embed construct paths in CloudFormation metadata
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 #1182
Related #1121
  • Loading branch information
Elad Ben-Israel committed Nov 15, 2018
1 parent 4694baa commit b1c1c94
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ coverage
.nyc_output
.LAST_BUILD
*.swp

# 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
12 changes: 9 additions & 3 deletions packages/aws-cdk/bin/cdk.ts
Original file line number Diff line number Diff line change
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 @@ -121,7 +122,7 @@ async function initCommandLine() {
'hosted-zone': new contextplugins.HostedZoneContextProviderPlugin(aws),
};

const defaultConfig = new Settings({ versionReporting: true });
const defaultConfig = new Settings({ versionReporting: true, pathMetadata: true });
const userConfig = await new Settings().load(PER_USER_DEFAULTS);
const projectConfig = await new Settings().load(DEFAULTS);
const commandLineArguments = argumentsToSettings();
Expand Down Expand Up @@ -629,7 +630,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 @@ -664,6 +669,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
3 changes: 2 additions & 1 deletion tools/cdk-integ-tools/bin/cdk-integ.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ async function main() {
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 ];
// inject "--no-path-metadata" so aws:cdk:path entries are not added to CFN metadata
const makeArgs = (...args: string[]) => !argv.verbose ? args : [ '--verbose', '--no-path-metadata', ...args ];

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

0 comments on commit b1c1c94

Please sign in to comment.