Skip to content

Commit

Permalink
feat(cdk): aspect framework and tag implementation aws#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 aws#1136.
  • Loading branch information
moofish32 committed Feb 5, 2019
1 parent a91a4f1 commit 5970c0b
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 19 deletions.
4 changes: 2 additions & 2 deletions examples/cdk-examples-typescript/hello-cdk-ecs-tags/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import cdk = require('@aws-cdk/cdk');

const COST_CENTER_KEY = 'CostCenter';

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

Expand Down Expand Up @@ -48,6 +48,6 @@ 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 MarketingFargate(app, 'Bonjour');
new MarketingDepartmentStack(app, 'Bonjour');

app.run();
60 changes: 44 additions & 16 deletions packages/@aws-cdk/cdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,30 @@ the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) (AWS CDK).
## Aspects

Aspects are a mechanism to extend the CDK without having to directly impact the
class hierarchy. We have implemented Aspects using the [Visitor
class hierarchy. We have implemented aspects using the [Visitor
Pattern](https://en.wikipedia.org/wiki/Visitor_pattern).

An aspect in the CDK is defined by this [interface](lib/aspects/aspect.ts)

Aspects can be applied to any construct. During the tree
preparation phase the aspect will visit each construct in the tree one
time. Aspects are invoked in the order they were added to the construct,
starting at the `App` (root of the tree) and progressing in order to the leaf
nodes (most commonly the CloudFormation Resource). Aspect authors will implement
the `visit(IConstruct)` function and can inspect the `Construct` for specific characteristics.
Such as, is this construct a CloudFormation Resource?
"prepare" phase the aspect will visit each construct in the tree once.
Aspects are invoked in the order they were added to the construct. They
traverse the construct tree in a breadth first order starting at the `App`
ending at the leaf nodes (most commonly the CloudFormation Resource). Aspect
authors implement the `visit(IConstruct)` function and can inspect the
`Construct` for specific characteristics. Such as, is this construct a
CloudFormation Resource?

## Tagging

Tags are implemented using Aspects.
Tags are implemented using aspects.

Tags can be applied to any construct. Tags are inherited, based on the scope. If
you tag construct A, and A contains construct B, construct B inherits the tag.
The Tag API supports:

* Add (apply) a tag, either to specific resources or all but specific resources
* Remove a tag, again either from specific resources or all but specific resources
* `Tag` add (apply) a tag, either to specific resources or all but specific resources
* `RemoveTag` remove a tag, again either from specific resources or all but specific resources

A simple example, if you create a stack and want anything in the stack to receive a
tag:
Expand All @@ -43,11 +44,11 @@ theBestStack.apply(new cdk.Tag('StackType', 'TheBest'));
// any resources added that support tags will get them
```

The goal was to enable the ability to define tags in one place and have them
applied consistently for all resources that support tagging. In addition
the developer should not have to know if the resource supports tags. The
developer defines the tagging intents for all resources within a path.
If the resources support tags they are added, else no action is taken.
> The goal was to enable the ability to define tags in one place and have them
> applied consistently for all resources that support tagging. In addition
> the developer should not have to know if the resource supports tags. The
> developer defines the tagging intents for all resources within a path.
> If the resources support tags they are added, else no action is taken.
### Tag Example with ECS

Expand Down Expand Up @@ -118,7 +119,7 @@ has a few features that are covered later to explain how this works.

In order to enable additional controls a Tags can specifically include or
exclude a CloudFormation Resource Type, propagate tags for an autoscaling group,
and use priority to override the default precedence. See the `TagAspectProps`
and use priority to override the default precedence. See the `TagProps`
interface for more details.

#### applyToLaunchedInstances
Expand All @@ -127,13 +128,27 @@ This property is a boolean that defaults to `true`. When `true` and the aspect
visits an AutoScalingGroup resource the `PropagateAtLaunch` property is set to
true. If false the property is set accordingly.

```ts
// ... snip
const vpc = new ec2.VpcNetwork(this, 'MyVpc', { ... });
vpc.apply(new cdk.Tag('MyKey', 'MyValue', { applyToLaunchedInstances: false }));
// ... snip
```

#### includeResourceTypes

Include is an array property that contains strings of CloudFormation Resource
Types. As the aspect visits nodes it only takes action if node is one of the
resource types in the array. By default the array is empty and an empty array is
interpreted as apply to any resource type.

```ts
// ... snip
const vpc = new ec2.VpcNetwork(this, 'MyVpc', { ... });
vpc.apply(new cdk.Tag('MyKey', 'MyValue', { includeResourceTypes: ['AWS::EC2::Subnet']}));
// ... snip
```

#### excludeResourceTypes

Exclude is the inverse of include. Exclude is also an array of CloudFormation
Expand All @@ -142,10 +157,23 @@ one of the resource types in the array. By default the array is empty and an
empty array is interpreted to match no resource type. Exclude takes precedence
over include in the event of a collision.

```ts
// ... snip
const vpc = new ec2.VpcNetwork(this, 'MyVpc', { ... });
vpc.apply(new cdk.Tag('MyKey', 'MyValue', { exludeResourceTypes: ['AWS::EC2::Subnet']}));
// ... snip
```

#### priority

Priority is used to control precedence when the default pattern does not work.
In general users should try to avoid using priority, but in some situations it
is required. In the example above, this is how `RemoveTag` works. The default
setting for removing tags uses a higher priority than the standard tag.

```ts
// ... snip
const vpc = new ec2.VpcNetwork(this, 'MyVpc', { ... });
vpc.apply(new cdk.Tag('MyKey', 'MyValue', { priority: 2 }));
// ... snip
```
2 changes: 1 addition & 1 deletion packages/@aws-cdk/cdk/lib/cloudformation/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class Resource extends Referenceable {
/**
* Check whether the given construct is Taggable
*/
public static isTaggable(construct: IConstruct): construct is ITaggable {
public static isTaggable(construct: any): construct is ITaggable {
return (construct as any).tags !== undefined;
}

Expand Down

0 comments on commit 5970c0b

Please sign in to comment.