Skip to content

Commit

Permalink
feat(cdk): aspect framework and tag implementation (#1451)
Browse files Browse the repository at this point in the history
A generalized aspect framework is added. Aspects can be used to affect the construct tree without modifying the class hierarchy. This framework is designed to be generic for future use cases. Tagging is the first implementation.

Tagging has been reimplemented to leverage the aspect framework and simplify the original tag design. Tag Manager still exists, but is no longer intended for use by L2 construct authors. There are two new classes `cdk.Tag` and `cdk.RemoveTag`. As new resources are added tag support will be automatic as long as they implement one of the existing tag specifications. All L2 constructs have removed the TagManager.

Code generation has been modified to add tag support to any CloudFormation Resource with a matching specification, by creating a Tag Manager for that resource as a `tags` attribute. The schema code now includes the ability to detect 3 forms of tags which include the current CloudFormation Resources.

BREAKING CHANGE: if you are using TagManager the API for this object has completely changed. You should no longer use TagManager directly, but instead replace this with Tag Aspects. `cdk.Tag` has been renamed to `cdk.CfnTag` to enable `cdk.Tag` to be the Tag Aspect.

Fixes #1136
Fixes #1497 
Related #360
  • Loading branch information
moofish32 authored and Elad Ben-Israel committed Feb 6, 2019
1 parent 36cfca8 commit f7c8531
Show file tree
Hide file tree
Showing 36 changed files with 1,711 additions and 1,473 deletions.
3 changes: 3 additions & 0 deletions examples/cdk-examples-typescript/hello-cdk-ecs-tags/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"app": "node index"
}
53 changes: 53 additions & 0 deletions examples/cdk-examples-typescript/hello-cdk-ecs-tags/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import ec2 = require('@aws-cdk/aws-ec2');
import ecs = require('@aws-cdk/aws-ecs');
import cdk = require('@aws-cdk/cdk');

const COST_CENTER_KEY = 'CostCenter';

class MarketingDepartmentStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);

// Create VPC and Fargate Cluster
// NOTE: Limit AZs to avoid reaching resource quotas
const vpc = new ec2.VpcNetwork(this, 'MyVpc', { maxAZs: 3 });

// override cost center to platform
vpc.apply(new cdk.Tag(COST_CENTER_KEY, 'Platform'));

const cluster = new ecs.Cluster(this, 'Cluster', { vpc });

// Create a load-balanced Fargate service and make it public
const b2b = new ecs.LoadBalancedFargateService(this, 'B2BService', {
cluster, // Required
cpu: '512', // Default is 256
desiredCount: 6, // Default is 1
image: ecs.ContainerImage.fromDockerHub('amazon/amazon-ecs-sample'), // Required
memoryMiB: '2048', // Default is 512
publicLoadBalancer: true // Default is false
});

// Create a load-balanced Fargate service and make it public
const b2c = new ecs.LoadBalancedFargateService(this, 'B2CService', {
cluster, // Required
cpu: '512', // Default is 256
desiredCount: 6, // Default is 1
image: ecs.ContainerImage.fromDockerHub('amazon/amazon-ecs-sample'), // Required
memoryMiB: '2048', // Default is 512
publicLoadBalancer: true // Default is false
});

// Output the DNS where you can access your service
new cdk.Output(this, 'B2BLoadBalancerDNS', { value: b2b.loadBalancer.dnsName });
new cdk.Output(this, 'B2CLoadBalancerDNS', { value: b2c.loadBalancer.dnsName });
}
}

const app = new cdk.App();

// by default bill everything to marketing overrides are in the stack
app.apply(new cdk.Tag(COST_CENTER_KEY, 'Marketing'));

new MarketingDepartmentStack(app, 'Bonjour');

app.run();
26 changes: 2 additions & 24 deletions packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,6 @@ export interface AutoScalingGroupProps {
*/
resourceSignalTimeoutSec?: number;

/**
* The AWS resource tags to associate with the ASG.
*/
tags?: cdk.Tags;

/**
* Default scaling cooldown for this AutoScalingGroup
*
Expand Down Expand Up @@ -169,7 +164,7 @@ export interface AutoScalingGroupProps {
*
* The ASG spans all availability zones.
*/
export class AutoScalingGroup extends cdk.Construct implements IAutoScalingGroup, cdk.ITaggable, elb.ILoadBalancerTarget, ec2.IConnectable,
export class AutoScalingGroup extends cdk.Construct implements IAutoScalingGroup, elb.ILoadBalancerTarget, ec2.IConnectable,
elbv2.IApplicationLoadBalancerTarget, elbv2.INetworkLoadBalancerTarget {
/**
* The type of OS instances of this fleet are running.
Expand All @@ -186,11 +181,6 @@ export class AutoScalingGroup extends cdk.Construct implements IAutoScalingGroup
*/
public readonly role: iam.Role;

/**
* Manage tags for this construct and children
*/
public readonly tags: cdk.TagManager;

/**
* Name of the AutoScalingGroup
*/
Expand All @@ -217,8 +207,7 @@ export class AutoScalingGroup extends cdk.Construct implements IAutoScalingGroup
});
this.connections = new ec2.Connections({ securityGroups: [this.securityGroup] });
this.securityGroups.push(this.securityGroup);
this.tags = new TagManager(this, {initialTags: props.tags});
this.tags.setTag(NAME_TAG, this.node.path, { overwrite: false });
this.apply(new cdk.Tag(NAME_TAG, this.node.path));

this.role = new iam.Role(this, 'InstanceRole', {
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com')
Expand Down Expand Up @@ -264,7 +253,6 @@ export class AutoScalingGroup extends cdk.Construct implements IAutoScalingGroup
launchConfigurationName: launchConfig.ref,
loadBalancerNames: new cdk.Token(() => this.loadBalancerNames.length > 0 ? this.loadBalancerNames : undefined),
targetGroupArns: new cdk.Token(() => this.targetGroupArns.length > 0 ? this.targetGroupArns : undefined),
tags: this.tags,
};

if (props.notificationsTopic) {
Expand Down Expand Up @@ -623,16 +611,6 @@ function renderRollingUpdateConfig(config: RollingUpdateConfiguration = {}): cdk
};
}

class TagManager extends cdk.TagManager {
protected tagFormatResolve(tagGroups: cdk.TagGroups): any {
const tags = {...tagGroups.nonStickyTags, ...tagGroups.ancestorTags, ...tagGroups.stickyTags};
return Object.keys(tags).map( (key) => {
const propagateAtLaunch = !!tagGroups.propagateTags[key] || !!tagGroups.ancestorTags[key];
return {key, value: tags[key], propagateAtLaunch};
});
}
}

/**
* Render a number of seconds to a PTnX string.
*/
Expand Down
23 changes: 14 additions & 9 deletions packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import autoscaling = require('../lib');

export = {
'default fleet'(test: Test) {
const stack = new cdk.Stack(undefined, 'MyStack', { env: { region: 'us-east-1', account: '1234' }});
const stack = getTestStack();
const vpc = mockVpc(stack);

new autoscaling.AutoScalingGroup(stack, 'MyFleet', {
Expand Down Expand Up @@ -365,7 +365,8 @@ export = {
},
'can set tags'(test: Test) {
// GIVEN
const stack = new cdk.Stack(undefined, 'MyStack', { env: { region: 'us-east-1', account: '1234' }});
const stack = getTestStack();
// new cdk.Stack(undefined, 'MyStack', { env: { region: 'us-east-1', account: '1234' }});
const vpc = mockVpc(stack);

// WHEN
Expand All @@ -378,27 +379,27 @@ export = {
minSuccessfulInstancesPercent: 50,
pauseTimeSec: 345
},
tags: {superfood: 'acai'},
});
asg.tags.setTag('notsuper', 'caramel', {propagate: false});
asg.apply( new cdk.Tag('superfood', 'acai'));
asg.apply( new cdk.Tag('notsuper', 'caramel', { applyToLaunchedInstances: false }));

// THEN
expect(stack).to(haveResource("AWS::AutoScaling::AutoScalingGroup", {
Tags: [
{
Key: 'superfood',
Value: 'acai',
Key: 'Name',
PropagateAtLaunch: true,
Value: 'MyFleet',
},
{
Key: 'Name',
Value: 'MyFleet',
Key: 'superfood',
PropagateAtLaunch: true,
Value: 'acai',
},
{
Key: 'notsuper',
Value: 'caramel',
PropagateAtLaunch: false,
Value: 'caramel',
},
]
}));
Expand Down Expand Up @@ -494,3 +495,7 @@ function mockSecurityGroup(stack: cdk.Stack) {
securityGroupId: 'most-secure',
});
}

function getTestStack(): cdk.Stack {
return new cdk.Stack(undefined, 'TestStack', { env: { account: '1234', region: 'us-east-1' } });
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect, haveResource, MatchStyle } from '@aws-cdk/assert';
import { expect, haveResource, MatchStyle, } from '@aws-cdk/assert';
import ec2 = require('@aws-cdk/aws-ec2');
import cdk = require('@aws-cdk/cdk');
import { Test } from 'nodeunit';
Expand Down Expand Up @@ -111,4 +111,4 @@ function makeAutoScalingGroup(scope: cdk.Construct) {
machineImage: new ec2.AmazonLinuxImage(),
updateType: autoscaling.UpdateType.RollingUpdate,
});
}
}
9 changes: 1 addition & 8 deletions packages/@aws-cdk/aws-dynamodb/lib/table.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import appscaling = require('@aws-cdk/aws-applicationautoscaling');
import iam = require('@aws-cdk/aws-iam');
import cdk = require('@aws-cdk/cdk');
import { Construct, TagManager, Tags, Token } from '@aws-cdk/cdk';
import { Construct, Token } from '@aws-cdk/cdk';
import { CfnTable } from './dynamodb.generated';
import { EnableScalingProps, IScalableTableAttribute } from './scalable-attribute-api';
import { ScalableTableAttribute } from './scalable-table-attribute';
Expand Down Expand Up @@ -94,12 +94,6 @@ export interface TableProps {
*/
streamSpecification?: StreamViewType;

/**
* The AWS resource tags to associate with the table.
* @default undefined
*/
tags?: Tags;

/**
* The name of TTL attribute.
* @default undefined, TTL is disabled
Expand Down Expand Up @@ -234,7 +228,6 @@ export class Table extends Construct {
},
sseSpecification: props.sseEnabled ? { sseEnabled: props.sseEnabled } : undefined,
streamSpecification: props.streamSpecification ? { streamViewType: props.streamSpecification } : undefined,
tags: new TagManager(this, { initialTags: props.tags }),
timeToLiveSpecification: props.ttlAttributeName ? { attributeName: props.ttlAttributeName, enabled: true } : undefined
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { App, Stack } from '@aws-cdk/cdk';
import { App, Stack, Tag } from '@aws-cdk/cdk';
import { Attribute, AttributeType, BillingMode, ProjectionType, StreamViewType, Table } from '../lib';

// CDK parameters
Expand Down Expand Up @@ -48,11 +48,12 @@ const tableWithGlobalAndLocalSecondaryIndex = new Table(stack, TABLE_WITH_GLOBAL
pitrEnabled: true,
sseEnabled: true,
streamSpecification: StreamViewType.KeysOnly,
tags: { Environment: 'Production' },
billingMode: BillingMode.PayPerRequest,
ttlAttributeName: 'timeToLive'
});

tableWithGlobalAndLocalSecondaryIndex.apply(new Tag('Environment', 'Production'));

tableWithGlobalAndLocalSecondaryIndex.addPartitionKey(TABLE_PARTITION_KEY);
tableWithGlobalAndLocalSecondaryIndex.addSortKey(TABLE_SORT_KEY);
tableWithGlobalAndLocalSecondaryIndex.addGlobalSecondaryIndex({
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import iam = require('@aws-cdk/aws-iam');
import { App, Stack } from '@aws-cdk/cdk';
import { App, Stack, Tag } from '@aws-cdk/cdk';
import { Attribute, AttributeType, ProjectionType, StreamViewType, Table } from '../lib';

// CDK parameters
Expand Down Expand Up @@ -48,10 +48,10 @@ const tableWithGlobalAndLocalSecondaryIndex = new Table(stack, TABLE_WITH_GLOBAL
pitrEnabled: true,
sseEnabled: true,
streamSpecification: StreamViewType.KeysOnly,
tags: { Environment: 'Production' },
ttlAttributeName: 'timeToLive'
});

tableWithGlobalAndLocalSecondaryIndex.apply(new Tag('Environment', 'Production'));
tableWithGlobalAndLocalSecondaryIndex.addPartitionKey(TABLE_PARTITION_KEY);
tableWithGlobalAndLocalSecondaryIndex.addSortKey(TABLE_SORT_KEY);
tableWithGlobalAndLocalSecondaryIndex.addGlobalSecondaryIndex({
Expand Down
Loading

0 comments on commit f7c8531

Please sign in to comment.