-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
Feature Add Ability to Tag Subnets #458
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -181,6 +181,13 @@ export interface SubnetConfiguration { | |
* availability zone. | ||
*/ | ||
name: string; | ||
|
||
/** | ||
* The AWS resource tags to associate with the Subnet. | ||
* | ||
*/ | ||
tags?: cdk.Tag[]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Everywhere I exposed to the customer I tried to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't it make more sense for this to just be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So should I never use
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should deprecate There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @eladb and the second question here about |
||
|
||
} | ||
|
||
/** | ||
|
@@ -400,7 +407,8 @@ export class VpcNetwork extends VpcNetworkRef { | |
vpcId: this.vpcId, | ||
cidrBlock: this.networkBuilder.addSubnet(cidrMask), | ||
mapPublicIpOnLaunch: (subnetConfig.subnetType === SubnetType.Public), | ||
}; | ||
tags: subnetConfig.tags, | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indentation? |
||
|
||
switch (subnetConfig.subnetType) { | ||
case SubnetType.Public: | ||
|
@@ -452,6 +460,11 @@ export interface VpcSubnetProps { | |
* Defaults to true in Subnet.Public, false in Subnet.Private or Subnet.Isolated. | ||
*/ | ||
mapPublicIpOnLaunch?: boolean; | ||
|
||
/** | ||
* The AWS resource tags to associate with the Subnet. | ||
*/ | ||
tags?: cdk.Tag[]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. map There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. map == |
||
} | ||
|
||
/** | ||
|
@@ -468,6 +481,11 @@ export class VpcSubnet extends VpcSubnetRef { | |
*/ | ||
public readonly subnetId: VpcSubnetId; | ||
|
||
/** | ||
* The tags for this subnet | ||
*/ | ||
private readonly tags: cdk.Token[] = []; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's no need to store these as this.tags = {
Name: this.path,
...(this.props.tags || {}) // if props.tags contains "Name" it will be overridden by the splat
}; Then use a token to "lazy render" to CloudFormation: const subnet = new cloudformation.SubnetResource(this, 'Subnet', {
// ...
tags: new Token(() => Object.keys(this.tags).map(key => ({ key, value: this.tags[key] })))
}; (you can also create a helper function if you think it's more readable) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have this in my work for #91 - thinking it might be better just to MVP that solution and only enable VPC first. I need to pivot away from |
||
|
||
/** | ||
* The routeTableId attached to this subnet. | ||
*/ | ||
|
@@ -476,11 +494,20 @@ export class VpcSubnet extends VpcSubnetRef { | |
constructor(parent: cdk.Construct, name: string, props: VpcSubnetProps) { | ||
super(parent, name); | ||
this.availabilityZone = props.availabilityZone; | ||
if (props.tags !== undefined) { | ||
this.tags = props.tags.map( tag => new cdk.Token(tag) ); | ||
if (props.tags.filter( tag => tag.key === 'Name' ).length !== 1) { | ||
this.tags.push(new cdk.Token({key: 'Name', value: name})); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
} | ||
} else { | ||
this.tags = [new cdk.Token({key: 'Name', value: name})]; | ||
} | ||
const subnet = new cloudformation.SubnetResource(this, 'Subnet', { | ||
vpcId: props.vpcId, | ||
cidrBlock: props.cidrBlock, | ||
availabilityZone: props.availabilityZone, | ||
mapPublicIpOnLaunch: props.mapPublicIpOnLaunch, | ||
tags: this.tags, | ||
}); | ||
this.subnetId = subnet.ref; | ||
const table = new cloudformation.RouteTableResource(this, 'RouteTable', { | ||
|
@@ -497,6 +524,10 @@ export class VpcSubnet extends VpcSubnetRef { | |
this.dependencyElements.push(subnet, table, routeAssoc); | ||
} | ||
|
||
public addTag(tag: cdk.Tag): void { | ||
this.tags.push(new cdk.Token(tag)); | ||
} | ||
|
||
protected addDefaultRouteToNAT(natGatewayId: cdk.Token) { | ||
new cloudformation.RouteResource(this, `DefaultRoute`, { | ||
routeTableId: this.routeTableId, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
import { countResources, expect, haveResource } from '@aws-cdk/assert'; | ||
import { countResources, expect, haveResource, isSuperObject} from '@aws-cdk/assert'; | ||
import { AvailabilityZoneProvider, Stack } from '@aws-cdk/cdk'; | ||
import { Test } from 'nodeunit'; | ||
import { DefaultInstanceTenancy, SubnetType, VpcNetwork } from '../lib'; | ||
import { DefaultInstanceTenancy, SubnetType, VpcNetwork, VpcSubnet } from '../lib'; | ||
|
||
export = { | ||
|
||
|
@@ -60,6 +60,12 @@ export = { | |
test.equal(vpc.publicSubnets.length, zones); | ||
test.equal(vpc.privateSubnets.length, zones); | ||
test.deepEqual(vpc.vpcId.resolve(), { Ref: 'TheVPC92636AB0' }); | ||
expect(stack).to(haveResource("AWS::EC2::Subnet", { | ||
Tags: [ {Key: "Name", Value: "PrivateSubnet2"} ], | ||
})); | ||
expect(stack).to(haveResource("AWS::EC2::Subnet", { | ||
Tags: [ {Key: "Name", Value: "PublicSubnet1"} ], | ||
})); | ||
test.done(); | ||
}, | ||
|
||
|
@@ -78,6 +84,9 @@ export = { | |
expect(stack).to(haveResource("AWS::EC2::Subnet", { | ||
MapPublicIpOnLaunch: false | ||
})); | ||
expect(stack).to(haveResource("AWS::EC2::Subnet", { | ||
Tags: [ {Key: "Name", Value: "IsolatedSubnet3"} ], | ||
})); | ||
test.done(); | ||
}, | ||
|
||
|
@@ -119,23 +128,46 @@ export = { | |
cidrMask: 24, | ||
name: 'ingress', | ||
subnetType: SubnetType.Public, | ||
tags: [{key: 'SubnetType', value: 'Public'}], | ||
}, | ||
{ | ||
cidrMask: 24, | ||
name: 'application', | ||
subnetType: SubnetType.Private, | ||
tags: [{key: 'Compliance', value: 'PCI'}], | ||
}, | ||
{ | ||
cidrMask: 28, | ||
name: 'rds', | ||
subnetType: SubnetType.Isolated, | ||
tags: [{key: 'Billing', value: 'DatabaseTeam'}], | ||
} | ||
], | ||
maxAZs: 3 | ||
}); | ||
expect(stack).to(countResources("AWS::EC2::InternetGateway", 1)); | ||
expect(stack).to(countResources("AWS::EC2::NatGateway", zones)); | ||
expect(stack).to(countResources("AWS::EC2::Subnet", 9)); | ||
for (let i = 1; i < 4; i++) { | ||
expect(stack).to(haveResource('AWS::EC2::Subnet', | ||
hasTags([ | ||
{ Key: 'Name', Value: `ingressSubnet${i}` }, | ||
{ Key: 'SubnetType', Value: 'Public' }, | ||
]) | ||
)); | ||
expect(stack).to(haveResource('AWS::EC2::Subnet', | ||
hasTags([ | ||
{ Key: 'Name', Value: `applicationSubnet${i}` }, | ||
{ Key: 'Compliance', Value: 'PCI' }, | ||
]) | ||
)); | ||
expect(stack).to(haveResource('AWS::EC2::Subnet', | ||
hasTags([ | ||
{ Key: 'Name', Value: `rdsSubnet${i}` }, | ||
{ Key: 'Billing', Value: 'DatabaseTeam' }, | ||
]) | ||
)); | ||
} | ||
for (let i = 0; i < 6; i++) { | ||
expect(stack).to(haveResource("AWS::EC2::Subnet", { | ||
CidrBlock: `10.0.${i}.0/24` | ||
|
@@ -216,7 +248,7 @@ export = { | |
}, | ||
"with maxAZs set to 2"(test: Test) { | ||
const stack = getTestStack(); | ||
new VpcNetwork(stack, 'VPC', { maxAZs: 2 }); | ||
const myVpc = new VpcNetwork(stack, 'VPC', { maxAZs: 2 }); | ||
expect(stack).to(countResources("AWS::EC2::Subnet", 4)); | ||
expect(stack).to(countResources("AWS::EC2::Route", 4)); | ||
for (let i = 0; i < 4; i++) { | ||
|
@@ -228,6 +260,20 @@ export = { | |
DestinationCidrBlock: '0.0.0.0/0', | ||
NatGatewayId: { }, | ||
})); | ||
expect(stack).notTo(haveResource("AWS::EC2::Subnet", | ||
hasTags([ | ||
{ Key: 'AddedTag', Value: 'NewThing' }, | ||
]) | ||
)); | ||
(myVpc.publicSubnets as VpcSubnet[]).forEach((subnet) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wanted to change There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There's a little bit of documentation about imports under the AWS Construct Library topic in our docs. We should probably improve it... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. RE: Imports I was under the impression (from reading the Importing section of https://awslabs.github.io/aws-cdk/refs/_aws-cdk_aws-certificatemanager.html) that you had to export a construct before you could import it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
const importedBucket = Bucket.import(this, 'MyImportedBucket', { bucketName: 'my_bucket' }); Would return a Other resources may have different heuristics for imports (for example, VPCs need much more information in order to be usable). When you const exportedFoo = fooBucket.export();
// then in another stack:
const importedFoo = Bucket.import(this, 'ImportedFoo', exportedFoo); Should we expand on the this topic? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll take a stab at updating the topic. Watch for a new PR soon. |
||
subnet.addTag({key: "AddedTag", value: "NewThing"}); | ||
}); | ||
expect(stack).to(haveResource('AWS::EC2::Subnet', | ||
hasTags([ | ||
{ Key: 'AddedTag', Value: 'NewThing' }, | ||
]) | ||
)); | ||
|
||
test.done(); | ||
}, | ||
"with natGateway set to 1"(test: Test) { | ||
|
@@ -262,3 +308,27 @@ export = { | |
function getTestStack(): Stack { | ||
return new Stack(undefined, 'TestStack', { env: { account: '123456789012', region: 'us-east-1' } }); | ||
} | ||
|
||
function hasTags(expectedTags: Array<{Key: string, Value: string}>): (props: any) => boolean { | ||
return (props: any) => { | ||
try { | ||
const tags = props.Tags; | ||
const actualTags = tags.filter( (tag: {Key: string, Value: string}) => { | ||
for (const expectedTag of expectedTags) { | ||
if (isSuperObject(expectedTag, tag)) { | ||
return true; | ||
} else { | ||
continue; | ||
} | ||
} | ||
// no values in array so expecting empty | ||
return false; | ||
}); | ||
return actualTags.length === expectedTags.length; | ||
} catch (e) { | ||
// tslint:disable-next-line:no-console | ||
console.error('Invalid Tags array in ', props); | ||
throw e; | ||
} | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant newline