Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cdk): adding tags parameter option to cdk deploy command #2185

Merged
merged 29 commits into from
Jun 3, 2019
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
98226c1
feat(cdk): adding tags parameter option to cdk deploy command to allo…
IsmaelMartinez Apr 5, 2019
26c8921
feat(cdk) removing duplicated code and adding missing semicolons
IsmaelMartinez Apr 5, 2019
0ee589a
feat(cdk): #2185 Reading the stack metadata to find tags if no tags o…
IsmaelMartinez Apr 8, 2019
6444ba4
Removing print from getTagsFromStackMetadata
IsmaelMartinez Apr 8, 2019
14c08df
feat(cdk): #2185 Changes from PR comments
IsmaelMartinez Apr 11, 2019
71883f2
Changes from PR comments. Fixing build
IsmaelMartinez Apr 11, 2019
9defac8
Correcting copy/paste error and changing the name to TAGS_METADATA_KE…
IsmaelMartinez Apr 11, 2019
e046be6
Correcting copy/paste error and changing the name to TAGS_METADATA_KE…
IsmaelMartinez Apr 11, 2019
caaeff5
WIP with tag aspects
IsmaelMartinez Apr 22, 2019
17addb8
Merge branch 'master' into ismaelmartinez/feat-add-tags-option-cdk-de…
IsmaelMartinez Apr 22, 2019
c504b6f
Merge remote-tracking branch 'upstream/master' into ismaelmartinez/fe…
IsmaelMartinez Apr 24, 2019
ca252b0
WIP
IsmaelMartinez Apr 24, 2019
5e43a64
Adding the tags via properties and the stack.node.apply. WIP
IsmaelMartinez Apr 26, 2019
53f0158
Adding the tags via properties and the stack.node.apply. WIP
IsmaelMartinez Apr 26, 2019
408d7fa
Merge branch 'master' into ismaelmartinez/feat-add-tags-option-cdk-de…
IsmaelMartinez Apr 26, 2019
8eed2c8
changes from comments
IsmaelMartinez May 2, 2019
90fc71f
Only adding the metadata when there are tags
IsmaelMartinez May 3, 2019
d0cc809
Adding some tests for the KeyValue and hasTags in tag-manager
IsmaelMartinez May 3, 2019
bc1bb26
Moving the isTaggable to the Contructor and tidying up after code rev…
IsmaelMartinez May 6, 2019
c1e3e22
Merge remote-tracking branch 'upstream/master' into ismaelmartinez/fe…
IsmaelMartinez May 30, 2019
bbeebd2
Changes to address most of the code review comments
IsmaelMartinez May 31, 2019
196b2ca
Reverting the move of the check for if the resource/stack is taggable…
IsmaelMartinez May 31, 2019
08eb2b3
Fixing the build (alphabeticall order of imports)
IsmaelMartinez May 31, 2019
f0a219e
Moving isTaggable from the constructor to the TagManager
IsmaelMartinez Jun 3, 2019
0e3c101
renaming TAGS_METADATA_KEY to STACK_TAGS_METADATA_KEY and moving it t…
IsmaelMartinez Jun 3, 2019
4d97f7c
Missing fixes from code review changes
IsmaelMartinez Jun 3, 2019
26d1870
Fixing build
IsmaelMartinez Jun 3, 2019
d04c9c1
Fixing an issue with the tags not been populated if pass via the cons…
IsmaelMartinez Jun 3, 2019
bf518e3
Adding docs
IsmaelMartinez Jun 3, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions packages/@aws-cdk/cdk/lib/cfn-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import cxapi = require('@aws-cdk/cx-api');
import { CfnCondition } from './cfn-condition';
import { Construct, IConstruct } from './construct';
import { CreationPolicy, DeletionPolicy, UpdatePolicy } from './resource-policy';
import { TagManager } from './tag-manager';
import { ITaggable } from './tag-manager';
import { capitalizePropertyNames, ignoreEmpty, PostResolveToken } from './util';
// import required to be here, otherwise causes a cycle when running the generated JavaScript
// tslint:disable-next-line:ordered-imports
Expand All @@ -21,12 +21,6 @@ export interface CfnResourceProps {
readonly properties?: any;
}

export interface ITaggable {
/**
* TagManager to set, remove and format tags
*/
readonly tags: TagManager;
}
/**
* Represents a CloudFormation resource.
*/
Expand Down Expand Up @@ -264,6 +258,7 @@ export enum TagType {
Standard = 'StandardTag',
AutoScalingGroup = 'AutoScalingGroupTag',
Map = 'StringToStringMap',
KeyValue = 'KeyValue',
NotTaggable = 'NotTaggable',
}

Expand Down
16 changes: 14 additions & 2 deletions packages/@aws-cdk/cdk/lib/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { Environment } from './environment';
import { HashedAddressingScheme, IAddressingScheme, LogicalIDs } from './logical-id';
import { ISynthesisSession } from './synthesis';
import { makeUniqueId } from './uniqueid';

export interface StackProps {
/**
* The AWS environment (account/region) where this stack will be deployed.
Expand Down Expand Up @@ -40,6 +39,8 @@ export interface StackProps {
* @default true
*/
readonly autoDeploy?: boolean;

readonly tags?: { [key: string]: string };
}

const STACK_SYMBOL = Symbol.for('@aws-cdk/cdk.Stack');
Expand All @@ -48,6 +49,7 @@ const STACK_SYMBOL = Symbol.for('@aws-cdk/cdk.Stack');
* A root construct which represents a single CloudFormation stack.
*/
export class Stack extends Construct {

/**
* Adds a metadata annotation "aws:cdk:physical-name" to the construct if physicalName
* is non-null. This can be used later by tools and aspects to determine if resources
Expand All @@ -72,6 +74,8 @@ export class Stack extends Construct {

private static readonly VALID_STACK_NAME_REGEX = /^[A-Za-z][A-Za-z0-9-]*$/;

public readonly tags: TagManager;

/**
* Lists all missing contextual information.
* This is returned when the stack is synthesized under the 'missing' attribute
Expand Down Expand Up @@ -153,6 +157,8 @@ export class Stack extends Construct {
this.logicalIds = new LogicalIDs(props && props.namingScheme ? props.namingScheme : new HashedAddressingScheme());
this.name = props.stackName !== undefined ? props.stackName : this.calculateStackName();
this.autoDeploy = props && props.autoDeploy === false ? false : true;
const tags = props === undefined ? undefined : props.tags;
this.tags = new TagManager(TagType.KeyValue, "AWS:Cloudformation::Stack", tags);
}

/**
Expand Down Expand Up @@ -190,6 +196,9 @@ export class Stack extends Construct {
public _toCloudFormation() {
// before we begin synthesis, we shall lock this stack, so children cannot be added
this.node.lock();
if (this.tags.hasTags()) {
this.node.addMetadata(cxapi.TAGS_METADATA_KEY, this.tags.renderTags());
}

try {
const template: any = {
Expand Down Expand Up @@ -548,13 +557,15 @@ export class Stack extends Construct {
visit(this);

const app = this.parentApp();

if (app && app.node.metadata.length > 0) {
output[PATH_SEP] = app.node.metadata;
}

return output;

function visit(node: IConstruct) {

if (node.node.metadata.length > 0) {
// Make the path absolute
output[PATH_SEP + node.node.path] = node.node.metadata.map(md => node.node.resolve(md) as cxapi.MetadataEntry);
Expand Down Expand Up @@ -660,8 +671,9 @@ function cfnElements(node: IConstruct, into: CfnElement[] = []): CfnElement[] {
import { ArnComponents, arnFromComponents, parseArn } from './arn';
import { CfnElement } from './cfn-element';
import { CfnReference } from './cfn-reference';
import { CfnResource } from './cfn-resource';
import { CfnResource, TagType } from './cfn-resource';
import { Aws, ScopedAws } from './pseudo';
import { TagManager } from './tag-manager';

/**
* Find all resources in a set of constructs
Expand Down
17 changes: 11 additions & 6 deletions packages/@aws-cdk/cdk/lib/tag-aspect.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// import cxapi = require('@aws-cdk/cx-api');
import { IAspect } from './aspect';
import { CfnResource, ITaggable } from './cfn-resource';
import { CfnResource} from './cfn-resource';
import { IConstruct } from './construct';
import { Stack } from './stack';
import { ITaggable } from './tag-manager';

/**
* Properties for a tag
Expand Down Expand Up @@ -71,12 +74,14 @@ export abstract class TagBase implements IAspect {
}

public visit(construct: IConstruct): void {
if (!CfnResource.isCfnResource(construct)) {
const isCfnResource = CfnResource.isCfnResource(construct);
const isStack = Stack.isStack(construct);
if (!isCfnResource && !isStack) {
return;
}
const resource = construct as CfnResource;
const resource = isCfnResource ? construct as CfnResource : construct as Stack;
if (CfnResource.isTaggable(resource)) {
this.applyTag(resource);
this.applyTag(resource);
}
}

Expand Down Expand Up @@ -104,7 +109,7 @@ export class Tag extends TagBase {
}

protected applyTag(resource: ITaggable) {
if (resource.tags.applyTagAspectHere(this.props.includeResourceTypes, this.props.excludeResourceTypes)) {
if (resource.tags.applyTagAspectHere(this.props.includeResourceTypes, this.props.excludeResourceTypes) || Stack.isStack(resource)) {
resource.tags.setTag(
this.key,
this.value,
Expand All @@ -127,7 +132,7 @@ export class RemoveTag extends TagBase {
}

protected applyTag(resource: ITaggable): void {
if (resource.tags.applyTagAspectHere(this.props.includeResourceTypes, this.props.excludeResourceTypes)) {
if (resource.tags.applyTagAspectHere(this.props.includeResourceTypes, this.props.excludeResourceTypes) || Stack.isStack(resource)) {
resource.tags.removeTag(this.key, this.props.priority !== undefined ? this.props.priority : this.defaultPriority);
}
}
Expand Down
43 changes: 43 additions & 0 deletions packages/@aws-cdk/cdk/lib/tag-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ interface CfnAsgTag {
propagateAtLaunch: boolean;
}

interface StackTag {
Key: string;
Value: string;
}
/**
* Interface for converter between CloudFormation and internal tag representations
*/
Expand Down Expand Up @@ -142,6 +146,33 @@ class MapFormatter implements ITagFormatter {
}
}

class KeyValueFormatter implements ITagFormatter {
public parseTags(keyValueTags: any, priority: number): Tag[] {
const tags: Tag[] = [];
for (const key in keyValueTags.tags) {
if (keyValueTags.tags.hasOwnProperty(key)) {
const value = keyValueTags.tags[key];
tags.push({
key,
value,
priority
});
}
}
return tags;
}
public formatTags(unformattedTags: Tag[]): any {
const tags: StackTag[] = [];
unformattedTags.forEach(tag => {
tags.push({
Key: tag.key,
Value: tag.value
});
});
return tags;
}
}

class NoFormat implements ITagFormatter {
public parseTags(_cfnPropertyTags: any): Tag[] {
return [];
Expand All @@ -155,9 +186,17 @@ const TAG_FORMATTERS: {[key: string]: ITagFormatter} = {
[TagType.AutoScalingGroup]: new AsgFormatter(),
[TagType.Standard]: new StandardFormatter(),
[TagType.Map]: new MapFormatter(),
[TagType.KeyValue]: new KeyValueFormatter(),
[TagType.NotTaggable]: new NoFormat(),
};

export interface ITaggable {
/**
* TagManager to set, remove and format tags
*/
readonly tags: TagManager;
}

/**
* TagManager facilitates a common implementation of tagging for Constructs.
*/
Expand Down Expand Up @@ -217,6 +256,10 @@ export class TagManager {
return true;
}

public hasTags(): boolean {
return this.tags.size > 0;
}

private _setTag(...tags: Tag[]) {
for (const tag of tags) {
if (tag.priority >= (this.priorities.get(tag.key) || 0)) {
Expand Down
20 changes: 19 additions & 1 deletion packages/@aws-cdk/cdk/test/test.tag-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,21 @@ export = {
test.deepEqual(mgr.renderTags(), undefined );
test.done();
},
'#hasTags() returns false'(test: Test) {
const mgr = new TagManager(TagType.Standard, 'AWS::Resource::Type');
test.equal(mgr.hasTags(), false );
test.done();
}
},
'#renderTags() handles standard, map, and ASG tag formats'(test: Test) {
'#renderTags() handles standard, map, keyValue, and ASG tag formats'(test: Test) {
const tagged: TagManager[] = [];
const standard = new TagManager(TagType.Standard, 'AWS::Resource::Type');
const asg = new TagManager(TagType.AutoScalingGroup, 'AWS::Resource::Type');
const keyValue = new TagManager(TagType.KeyValue, 'AWS::Resource::Type');
const mapper = new TagManager(TagType.Map, 'AWS::Resource::Type');
tagged.push(standard);
tagged.push(asg);
tagged.push(keyValue);
tagged.push(mapper);
for (const res of tagged) {
res.setTag('foo', 'bar');
Expand All @@ -65,12 +72,23 @@ export = {
{key: 'foo', value: 'bar', propagateAtLaunch: true},
{key: 'asg', value: 'only', propagateAtLaunch: false},
]);
test.deepEqual(keyValue.renderTags(), [
{ Key: 'foo', Value : 'bar' },
{ Key: 'asg', Value : 'only' }
]);
test.deepEqual(mapper.renderTags(), {
foo: 'bar',
asg: 'only',
});
test.done();
},
'when there are tags it hasTags returns true'(test: Test) {
const mgr = new TagManager(TagType.Standard, 'AWS::Resource::Type');
mgr.setTag('key', 'myVal', 2);
mgr.setTag('key', 'newVal', 1);
test.equal(mgr.hasTags(), true);
test.done();
},
'tags with higher or equal priority always take precedence'(test: Test) {
const mgr = new TagManager(TagType.Standard, 'AWS::Resource::Type');
mgr.setTag('key', 'myVal', 2);
Expand Down
5 changes: 5 additions & 0 deletions packages/@aws-cdk/cx-api/lib/cxapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ export const OUTFILE_NAME = 'cdk.out';
*/
export const DISABLE_VERSION_REPORTING = 'aws:cdk:disable-version-reporting';

/**
* Tag metadata key.
*/
export const TAGS_METADATA_KEY = 'aws:cdk:tags';

/**
* If this context key is set, the CDK will stage assets under the specified
* directory. Otherwise, assets will not be staged.
Expand Down
5 changes: 3 additions & 2 deletions packages/aws-cdk/bin/cdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ async function parseCommandLineArguments() {
.option('exclusively', { type: 'boolean', alias: 'e', desc: 'only deploy requested stacks, don\'t include dependencies' })
.option('require-approval', { type: 'string', choices: [RequireApproval.Never, RequireApproval.AnyChange, RequireApproval.Broadening], desc: 'what security-sensitive changes need manual approval' }))
.option('ci', { type: 'boolean', desc: 'Force CI detection. Use --no-ci to disable CI autodetection.', default: process.env.CI !== undefined })
.option('tags', { type: 'array', alias: 't', desc: 'tags to add to the stack (KEY=VALUE)', nargs: 1, requiresArg: true })
.command('destroy [STACKS..]', 'Destroy the stack(s) named STACKS', yargs => yargs
.option('exclusively', { type: 'boolean', alias: 'x', desc: 'only deploy requested stacks, don\'t include dependees' })
.option('force', { type: 'boolean', alias: 'f', desc: 'Do not ask for confirmation before destroying the stacks' }))
Expand Down Expand Up @@ -104,7 +105,6 @@ async function initCommandLine() {
proxyAddress: argv.proxy,
ec2creds: argv.ec2creds,
});

const configuration = new Configuration(argv);
await configuration.load();

Expand Down Expand Up @@ -199,7 +199,8 @@ async function initCommandLine() {
roleArn: args.roleArn,
requireApproval: configuration.settings.get(['requireApproval']),
ci: args.ci,
reuseAssets: args['build-exclude']
reuseAssets: args['build-exclude'],
tags: configuration.settings.get(['tags'])
});

case 'destroy':
Expand Down
18 changes: 18 additions & 0 deletions packages/aws-cdk/lib/api/cxapp/stacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export interface AppStacksProps {
* In a class because it shares some global state
*/
export class AppStacks {

/**
* Since app execution basically always synthesizes all the stacks,
* we can invoke it once and cache the response for subsequent calls.
Expand Down Expand Up @@ -227,6 +228,18 @@ export class AppStacks {
}
}

public getTagsFromStackMetadata(stack: SelectedStack): Tag[] | undefined {
for (const id of Object.keys(stack.metadata)) {
const metadata = stack.metadata[id];
for (const entry of metadata) {
if (entry.type === cxapi.TAGS_METADATA_KEY) {
return entry.data;
}
}
}
return;
}

/**
* Extracts 'aws:cdk:warning|info|error' metadata entries from the stack synthesis
*/
Expand Down Expand Up @@ -375,3 +388,8 @@ export interface SelectedStack extends cxapi.SynthesizedStack {
*/
originalName: string;
}

export interface Tag {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rix0rrr -- we are still separating interfaces across the cxapi right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rix0rrr , any updates on this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this as a public API?

readonly Key: string;
readonly Value: string;
}
5 changes: 4 additions & 1 deletion packages/aws-cdk/lib/api/deploy-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import cxapi = require('@aws-cdk/cx-api');
import aws = require('aws-sdk');
import colors = require('colors/safe');
import uuid = require('uuid');
import { Tag } from "../api/cxapp/stacks";
import { prepareAssets } from '../assets';
import { debug, error, print } from '../logging';
import { toYAML } from '../serialize';
Expand Down Expand Up @@ -32,6 +33,7 @@ export interface DeployStackOptions {
quiet?: boolean;
ci?: boolean;
reuseAssets?: string[];
tags?: Tag[];
}

const LARGE_TEMPLATE_SIZE_KB = 50;
Expand Down Expand Up @@ -73,7 +75,8 @@ export async function deployStack(options: DeployStackOptions): Promise<DeploySt
TemplateURL: bodyParameter.TemplateURL,
Parameters: params,
RoleARN: options.roleArn,
Capabilities: [ 'CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND' ]
Capabilities: [ 'CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND' ],
Tags: options.tags
}).promise();
debug('Initiated creation of changeset: %s; waiting for it to finish creating...', changeSet.Id);
const changeSetDescription = await waitForChangeSet(cfn, deployName, changeSetName);
Expand Down
Loading