From 3d48eb28dee1b15bd0884f7e9d318dc71080a071 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Thu, 27 Sep 2018 03:11:02 -0700 Subject: [PATCH] feat: Tagging support for AutoScaling/SecurityGroup (#766) Add support for tagging of AutoScalingGroups and Security Groups. BREAKING CHANGE: constructor signature of `TagManager` has changed. `initialTags` is now passed inside a props object. --- .../aws-autoscaling/lib/auto-scaling-group.ts | 30 ++- ...g.asg-w-classic-loadbalancer.expected.json | 14 ++ .../test/integ.asg-w-elbv2.expected.json | 14 ++ .../test/test.auto-scaling-group.ts | 230 +++++++----------- .../@aws-cdk/aws-ec2/lib/security-group.ts | 16 +- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 4 +- packages/@aws-cdk/aws-ec2/test/test.vpc.ts | 68 +++--- .../test/integ.elb.expected.json | 1 + .../test/integ.alb.expected.json | 1 + .../test/integ.vpc-lambda.expected.json | 1 + .../aws-rds/test/integ.cluster.expected.json | 56 ++++- .../@aws-cdk/aws-rds/test/integ.cluster.ts | 3 + packages/@aws-cdk/cdk/lib/core/tag-manager.ts | 72 +++++- .../cdk/test/core/test.tag-manager.ts | 2 +- 14 files changed, 329 insertions(+), 183 deletions(-) diff --git a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts index 62b2e924a31db..8d55ff8a5582f 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -7,6 +7,11 @@ import cdk = require('@aws-cdk/cdk'); import { cloudformation } from './autoscaling.generated'; +/** + * Name tag constant + */ +const NAME_TAG: string = 'Name'; + /** * Properties of a Fleet */ @@ -124,6 +129,11 @@ export interface AutoScalingGroupProps { * @default 300 (5 minutes) */ resourceSignalTimeoutSec?: number; + + /** + * The AWS resource tags to associate with the ASG. + */ + tags?: cdk.Tags; } /** @@ -137,7 +147,7 @@ export interface AutoScalingGroupProps { * * The ASG spans all availability zones. */ -export class AutoScalingGroup extends cdk.Construct implements elb.ILoadBalancerTarget, ec2.IConnectable, +export class AutoScalingGroup extends cdk.Construct implements cdk.ITaggable, elb.ILoadBalancerTarget, ec2.IConnectable, elbv2.IApplicationLoadBalancerTarget, elbv2.INetworkLoadBalancerTarget { /** * The type of OS instances of this fleet are running. @@ -154,6 +164,11 @@ export class AutoScalingGroup extends cdk.Construct implements elb.ILoadBalancer */ public readonly role: iam.Role; + /** + * Manage tags for this construct and children + */ + public readonly tags: cdk.TagManager; + private readonly userDataLines = new Array(); private readonly autoScalingGroup: cloudformation.AutoScalingGroupResource; private readonly securityGroup: ec2.SecurityGroupRef; @@ -167,6 +182,8 @@ export class AutoScalingGroup extends cdk.Construct implements elb.ILoadBalancer this.securityGroup = new ec2.SecurityGroup(this, 'InstanceSecurityGroup', { vpc: props.vpc }); this.connections = new ec2.Connections({ securityGroup: this.securityGroup }); this.securityGroups.push(this.securityGroup); + this.tags = new TagManager(this, {initialTags: props.tags}); + this.tags.setTag(NAME_TAG, this.path, { overwrite: false }); if (props.allowAllOutbound !== false) { this.connections.allowTo(new ec2.AnyIPv4(), new ec2.AllConnections(), 'Outbound traffic allowed by default'); @@ -211,6 +228,7 @@ export class AutoScalingGroup extends cdk.Construct implements elb.ILoadBalancer 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) { @@ -472,6 +490,16 @@ 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. */ diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json index c92cd2c79f071..11f6f40f5d196 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json @@ -453,6 +453,12 @@ } ], "SecurityGroupIngress": [], + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-integ/Fleet" + } + ], "VpcId": { "Ref": "VPCB9E5F0B4" } @@ -544,6 +550,13 @@ "Ref": "LB8A12904C" } ], + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "aws-cdk-ec2-integ/Fleet" + } + ], "VPCZoneIdentifier": [ { "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" @@ -576,6 +589,7 @@ "ToPort": 80 } ], + "Tags": [], "VpcId": { "Ref": "VPCB9E5F0B4" } diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json index 7e8084da884f9..e7f59cf158a9a 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json @@ -319,6 +319,12 @@ } ], "SecurityGroupIngress": [], + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-integ/Fleet" + } + ], "VpcId": { "Ref": "VPCB9E5F0B4" } @@ -405,6 +411,13 @@ "LaunchConfigurationName": { "Ref": "FleetLaunchConfig59F79D36" }, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "aws-cdk-ec2-integ/Fleet" + } + ], "TargetGroupARNs": [ { "Ref": "LBListenerTargetGroupF04FCF6D" @@ -463,6 +476,7 @@ "ToPort": 80 } ], + "Tags": [], "VpcId": { "Ref": "VPCB9E5F0B4" } diff --git a/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts index 124ef0e85386d..c7c31f3558712 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts @@ -19,28 +19,35 @@ export = { expect(stack).toMatch({ "Resources": { - "MyFleetInstanceSecurityGroup774E8234": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "GroupDescription": "MyFleet/InstanceSecurityGroup", - "SecurityGroupEgress": [ - { - "CidrIp": "0.0.0.0/0", - "Description": "Outbound traffic allowed by default", - "FromPort": -1, - "IpProtocol": "-1", - "ToPort": -1 + "MyFleetInstanceSecurityGroup774E8234": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "MyFleet/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Outbound traffic allowed by default", + "FromPort": -1, + "IpProtocol": "-1", + "ToPort": -1 + } + ], + "SecurityGroupIngress": [], + "Tags": [ + { + "Key": "Name", + "Value": "MyFleet" + } + ], + + "VpcId": "my-vpc" } - ], - "SecurityGroupIngress": [], - "VpcId": "my-vpc" - } - }, - "MyFleetInstanceRole25A84AB8": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ + }, + "MyFleetInstanceRole25A84AB8": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ { "Action": "sts:AssumeRole", "Effect": "Allow", @@ -99,11 +106,19 @@ export = { "LaunchConfigurationName": { "Ref": "MyFleetLaunchConfig5D7F9801" }, - "MaxSize": "1", - "MinSize": "1", - "VPCZoneIdentifier": [ - "pri1" - ] + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "MyFleet" + } + ], + + "MaxSize": "1", + "MinSize": "1", + "VPCZoneIdentifier": [ + "pri1" + ] } } } @@ -123,121 +138,21 @@ export = { }); fleet.addToRolePolicy(new cdk.PolicyStatement() - .addAction('*') + .addAction('test:SpecialName') .addAllResources()); - expect(stack).toMatch({ - "Resources": { - "MyFleetInstanceSecurityGroup774E8234": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "GroupDescription": "MyFleet/InstanceSecurityGroup", - "SecurityGroupEgress": [ - { - "CidrIp": "0.0.0.0/0", - "Description": "Outbound traffic allowed by default", - "FromPort": -1, - "IpProtocol": "-1", - "ToPort": -1 - } - ], - "SecurityGroupIngress": [], - "VpcId": "my-vpc" - } - }, - MyFleetInstanceRole25A84AB8: { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ec2.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - } - } - }, - MyFleetInstanceRoleDefaultPolicy7B0197E7: { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Effect": "Allow", - "Resource": "*" - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "MyFleetInstanceRoleDefaultPolicy7B0197E7", - "Roles": [ + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ { - "Ref": "MyFleetInstanceRole25A84AB8" - } - ] - } - }, - MyFleetInstanceProfile70A58496: { - "Type": "AWS::IAM::InstanceProfile", - "Properties": { - "Roles": [ - { - "Ref": "MyFleetInstanceRole25A84AB8" + Action: "test:SpecialName", + Effect: "Allow", + Resource: "*" } - ] - } - }, - MyFleetLaunchConfig5D7F9801: { - Type: "AWS::AutoScaling::LaunchConfiguration", - Properties: { - "IamInstanceProfile": { - "Ref": "MyFleetInstanceProfile70A58496" - }, - "ImageId": "dummy", - "InstanceType": "m4.micro", - "SecurityGroups": [ - { - "Fn::GetAtt": [ - "MyFleetInstanceSecurityGroup774E8234", - "GroupId" - ] - } - ], - "UserData": { - "Fn::Base64": "#!/bin/bash\n" - } - }, - DependsOn: [ - "MyFleetInstanceRole25A84AB8", - "MyFleetInstanceRoleDefaultPolicy7B0197E7" - ] - }, - MyFleetASG88E55886: { - Type: "AWS::AutoScaling::AutoScalingGroup", - UpdatePolicy: { - AutoScalingScheduledAction: { IgnoreUnmodifiedGroupSizeProperties: true } - }, - Properties: { - DesiredCapacity: "1", - LaunchConfigurationName: { - Ref: "MyFleetLaunchConfig5D7F9801" - }, - MaxSize: "1", - MinSize: "1", - VPCZoneIdentifier: [ - "pri1" - ] - } - } - } - }); - + ], + Version: "2012-10-17" + }, + })); test.done(); }, @@ -354,6 +269,47 @@ export = { })); test.done(); }, + 'can set tags'(test: Test) { + // GIVEN + const stack = new cdk.Stack(undefined, 'MyStack', { env: { region: 'us-east-1', account: '1234' }}); + const vpc = mockVpc(stack); + + // WHEN + const asg = new autoscaling.AutoScalingGroup(stack, 'MyFleet', { + instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + machineImage: new ec2.AmazonLinuxImage(), + vpc, + updateType: autoscaling.UpdateType.RollingUpdate, + rollingUpdateConfiguration: { + minSuccessfulInstancesPercent: 50, + pauseTimeSec: 345 + }, + tags: {superfood: 'acai'}, + }); + asg.tags.setTag('notsuper', 'caramel', {propagate: false}); + + // THEN + expect(stack).to(haveResource("AWS::AutoScaling::AutoScalingGroup", { + Tags: [ + { + Key: 'superfood', + Value: 'acai', + PropagateAtLaunch: true, + }, + { + Key: 'Name', + Value: 'MyFleet', + PropagateAtLaunch: true, + }, + { + Key: 'notsuper', + Value: 'caramel', + PropagateAtLaunch: false, + }, + ] + })); + test.done(); + }, }; function mockVpc(stack: cdk.Stack) { diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group.ts b/packages/@aws-cdk/aws-ec2/lib/security-group.ts index b4df710d1eda5..4f48e59b74991 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group.ts @@ -1,4 +1,4 @@ -import { Construct, Output, Token } from '@aws-cdk/cdk'; +import { Construct, ITaggable, Output, TagManager, Tags, Token } from '@aws-cdk/cdk'; import { Connections, IConnectable } from './connections'; import { cloudformation } from './ec2.generated'; import { IPortRange, ISecurityGroupRule } from './security-group-rule'; @@ -106,6 +106,11 @@ export interface SecurityGroupProps { */ description?: string; + /** + * The AWS resource tags to associate with the security group. + */ + tags?: Tags; + /** * The VPC in which to create the security group. */ @@ -119,7 +124,7 @@ export interface SecurityGroupProps { * inline ingress and egress rule (which saves on the total number of resources inside * the template). */ -export class SecurityGroup extends SecurityGroupRef { +export class SecurityGroup extends SecurityGroupRef implements ITaggable { /** * An attribute that represents the security group name. */ @@ -135,6 +140,11 @@ export class SecurityGroup extends SecurityGroupRef { */ public readonly securityGroupId: string; + /** + * Manage tags for this construct and children + */ + public readonly tags: TagManager; + private readonly securityGroup: cloudformation.SecurityGroupResource; private readonly directIngressRules: cloudformation.SecurityGroupResource.IngressProperty[] = []; private readonly directEgressRules: cloudformation.SecurityGroupResource.EgressProperty[] = []; @@ -142,6 +152,7 @@ export class SecurityGroup extends SecurityGroupRef { constructor(parent: Construct, name: string, props: SecurityGroupProps) { super(parent, name); + this.tags = new TagManager(this, { initialTags: props.tags}); const groupDescription = props.description || this.path; this.securityGroup = new cloudformation.SecurityGroupResource(this, 'Resource', { groupName: props.groupName, @@ -149,6 +160,7 @@ export class SecurityGroup extends SecurityGroupRef { securityGroupIngress: new Token(() => this.directIngressRules), securityGroupEgress: new Token(() => this.directEgressRules), vpcId: props.vpc.vpcId, + tags: this.tags, }); this.securityGroupId = this.securityGroup.securityGroupId; diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index e273c34967e4f..4a4f75225f68f 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -272,7 +272,7 @@ export class VpcNetwork extends VpcNetworkRef implements cdk.ITaggable { throw new Error('To use DNS Hostnames, DNS Support must be enabled, however, it was explicitly disabled.'); } - this.tags = new cdk.TagManager(this, props.tags); + this.tags = new cdk.TagManager(this, { initialTags: props.tags}); this.tags.setTag(NAME_TAG, this.path, { overwrite: false }); const cidrBlock = ifUndefined(props.cidr, VpcNetwork.DEFAULT_CIDR_RANGE); @@ -467,7 +467,7 @@ export class VpcSubnet extends VpcSubnetRef implements cdk.ITaggable { constructor(parent: cdk.Construct, name: string, props: VpcSubnetProps) { super(parent, name); - this.tags = new cdk.TagManager(this, props.tags); + this.tags = new cdk.TagManager(this, {initialTags: props.tags}); this.tags.setTag(NAME_TAG, this.path, {overwrite: false}); this.availabilityZone = props.availabilityZone; diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts index 459ab504462a5..b9a52dfad6d50 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts @@ -128,6 +128,10 @@ export = { cidrMask: 24, name: 'ingress', subnetType: SubnetType.Public, + tags: { + type: 'Public', + init: 'No', + }, }, { cidrMask: 24, @@ -155,44 +159,50 @@ export = { CidrBlock: `10.0.6.${i * 16}/28` })); } + expect(stack).to(haveResource("AWS::EC2::Subnet", hasTags( + [ + { Key: 'type', Value: 'Public'}, + { Key: 'init', Value: 'No'}, + ], + ))); test.done(); }, "with custom subents and natGateways = 2 there should be only two NATGW"(test: Test) { const stack = getTestStack(); new VpcNetwork(stack, 'TheVPC', { - cidr: '10.0.0.0/21', - natGateways: 2, - subnetConfiguration: [ - { - cidrMask: 24, - name: 'ingress', - subnetType: SubnetType.Public, - }, - { - cidrMask: 24, - name: 'application', - subnetType: SubnetType.Private, - }, - { - cidrMask: 28, - name: 'rds', - subnetType: SubnetType.Isolated, - } - ], - maxAZs: 3 + cidr: '10.0.0.0/21', + natGateways: 2, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'ingress', + subnetType: SubnetType.Public, + }, + { + cidrMask: 24, + name: 'application', + subnetType: SubnetType.Private, + }, + { + cidrMask: 28, + name: 'rds', + subnetType: SubnetType.Isolated, + } + ], + maxAZs: 3 }); expect(stack).to(countResources("AWS::EC2::InternetGateway", 1)); expect(stack).to(countResources("AWS::EC2::NatGateway", 2)); expect(stack).to(countResources("AWS::EC2::Subnet", 9)); for (let i = 0; i < 6; i++) { - expect(stack).to(haveResource("AWS::EC2::Subnet", { - CidrBlock: `10.0.${i}.0/24` - })); + expect(stack).to(haveResource("AWS::EC2::Subnet", { + CidrBlock: `10.0.${i}.0/24` + })); } for (let i = 0; i < 3; i++) { - expect(stack).to(haveResource("AWS::EC2::Subnet", { - CidrBlock: `10.0.6.${i * 16}/28` - })); + expect(stack).to(haveResource("AWS::EC2::Subnet", { + CidrBlock: `10.0.6.${i * 16}/28` + })); } test.done(); }, @@ -229,9 +239,9 @@ export = { expect(stack).to(countResources("AWS::EC2::Subnet", 4)); expect(stack).to(countResources("AWS::EC2::Route", 4)); for (let i = 0; i < 4; i++) { - expect(stack).to(haveResource("AWS::EC2::Subnet", { - CidrBlock: `10.0.${i * 64}.0/18` - })); + expect(stack).to(haveResource("AWS::EC2::Subnet", { + CidrBlock: `10.0.${i * 64}.0/18` + })); } expect(stack).to(haveResource("AWS::EC2::Route", { DestinationCidrBlock: '0.0.0.0/0', diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/test/integ.elb.expected.json b/packages/@aws-cdk/aws-elasticloadbalancing/test/integ.elb.expected.json index 4b05529da2869..0203dcbaccd44 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/test/integ.elb.expected.json +++ b/packages/@aws-cdk/aws-elasticloadbalancing/test/integ.elb.expected.json @@ -185,6 +185,7 @@ "ToPort": 80 } ], + "Tags": [], "VpcId": { "Ref": "VPCB9E5F0B4" } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.expected.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.expected.json index 07eeab7b440b4..5d337cebcb360 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.expected.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.expected.json @@ -343,6 +343,7 @@ "ToPort": 80 } ], + "Tags": [], "VpcId": { "Ref": "VPCB9E5F0B4" } diff --git a/packages/@aws-cdk/aws-lambda/test/integ.vpc-lambda.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.vpc-lambda.expected.json index 396988864f19f..93e729c8fe111 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.vpc-lambda.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.vpc-lambda.expected.json @@ -382,6 +382,7 @@ } ], "SecurityGroupIngress": [], + "Tags": [], "VpcId": { "Ref": "VPCB9E5F0B4" } diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json b/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json index c4e75bcba0011..0da9a7177f3b8 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json @@ -315,6 +315,53 @@ } } }, + "DbSecurity381C2C15": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "DeletionPolicy": "Retain" + }, "DatabaseSubnets56F17B9A": { "Type": "AWS::RDS::DBSubnetGroup", "Properties": { @@ -335,6 +382,7 @@ "GroupDescription": "RDS security group", "SecurityGroupEgress": [], "SecurityGroupIngress": [], + "Tags": [], "VpcId": { "Ref": "VPCB9E5F0B4" } @@ -376,9 +424,15 @@ "DBSubnetGroupName": { "Ref": "DatabaseSubnets56F17B9A" }, + "KmsKeyId": { + "Fn::GetAtt": [ + "DbSecurity381C2C15", + "Arn" + ] + }, "MasterUsername": "admin", "MasterUserPassword": "7959866cacc02c2d243ecfe177464fe6", - "StorageEncrypted": false, + "StorageEncrypted": true, "VpcSecurityGroupIds": [ { "Fn::GetAtt": [ diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts index a5d21a14e6b63..d59dc9913fea3 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts @@ -1,4 +1,5 @@ import ec2 = require('@aws-cdk/aws-ec2'); +import kms = require('@aws-cdk/aws-kms'); import cdk = require('@aws-cdk/cdk'); import { DatabaseCluster, DatabaseClusterEngine } from '../lib'; import { ClusterParameterGroup } from '../lib/cluster-parameter-group'; @@ -14,6 +15,7 @@ const params = new ClusterParameterGroup(stack, 'Params', { }); params.setParameter('character_set_database', 'utf8mb4'); +const kmsKey = new kms.EncryptionKey(stack, 'DbSecurity'); const cluster = new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.Aurora, masterUser: { @@ -26,6 +28,7 @@ const cluster = new DatabaseCluster(stack, 'Database', { vpc }, parameterGroup: params, + kmsKeyArn: kmsKey.keyArn, }); cluster.connections.allowDefaultPortFromAnyIpv4('Open to the world'); diff --git a/packages/@aws-cdk/cdk/lib/core/tag-manager.ts b/packages/@aws-cdk/cdk/lib/core/tag-manager.ts index d73cd472513f2..d8763158cedd6 100644 --- a/packages/@aws-cdk/cdk/lib/core/tag-manager.ts +++ b/packages/@aws-cdk/cdk/lib/core/tag-manager.ts @@ -48,6 +48,31 @@ export interface TagProps { overwrite?: boolean; } +/** + * This is the interface for arguments to `tagFormatResolve` to enable extensions + */ +export interface TagGroups { + /** + * Tags that overwrite ancestor tags + */ + stickyTags: Tags; + + /** + * Tags that are overwritten by ancestor tags + */ + nonStickyTags: Tags; + + /** + * Tags with propagate true not from an ancestor + */ + propagateTags: Tags; + + /** + * Tags that are propagated from ancestors + */ + ancestorTags: Tags; +} + /** * Properties for removing tags */ @@ -60,6 +85,16 @@ export interface RemoveProps { blockPropagate?: boolean; } +/** + * Properties for Tag Manager + */ +export interface TagManagerProps { + /** + * Initial tags to set on the tag manager using TAG_DEFAULTS + */ + initialTags?: Tags; +} + /** * TagManager facilitates a common implementation of tagging for Constructs. * @@ -120,8 +155,10 @@ export class TagManager extends Token { */ private readonly blockedTags: string[] = []; - constructor(private readonly parent: Construct, initialTags: Tags = {}) { + constructor(private readonly parent: Construct, props: TagManagerProps = {}) { super(); + + const initialTags = props.initialTags || {}; for (const key of Object.keys(initialTags)) { const tag = { value: initialTags[key], @@ -135,6 +172,8 @@ export class TagManager extends Token { * Converts the `tags` to a Token for use in lazy evaluation */ public resolve(): any { + // need this for scoping + const blockedTags = this.blockedTags; function filterTags(_tags: FullTags, filter: TagProps = {}): Tags { const filteredTags: Tags = {}; Object.keys(_tags).map( key => { @@ -154,6 +193,7 @@ export class TagManager extends Token { filteredTags[key] = _tags[key].value; } }); + for (const key of blockedTags) { delete filteredTags[key]; } return filteredTags; } @@ -165,17 +205,21 @@ export class TagManager extends Token { Object.assign(parentTags, tagsFrom); } } + for (const key of blockedTags) { delete parentTags[key]; } return parentTags; } - const propOverwrite = filterTags(this._tags, {sticky: false}); - const nonOverwrite = filterTags(this._tags, {sticky: true}); + const nonStickyTags = filterTags(this._tags, {sticky: false}); + const stickyTags = filterTags(this._tags, {sticky: true}); const ancestors = this.parent.ancestors(); - ancestors.push(this.parent); - const tags = {...propOverwrite, ...propagatedTags(ancestors), ...nonOverwrite}; - for (const key of this.blockedTags) { delete tags[key]; } - - return Object.keys(tags).map( key => ({key, value: tags[key]})); + const ancestorTags = propagatedTags(ancestors); + const propagateTags = filterTags(this._tags, {propagate: true}); + return this.tagFormatResolve( { + ancestorTags, + nonStickyTags, + stickyTags, + propagateTags, + }); } /** @@ -202,6 +246,7 @@ export class TagManager extends Token { * Removes the specified tag from the array if it exists * * @param key The key of the tag to remove + * @param props The `RemoveProps` for the tag */ public removeTag(key: string, props: RemoveProps = {blockPropagate: true}): void { if (props.blockPropagate) { @@ -211,8 +256,15 @@ export class TagManager extends Token { } /** - * Retrieve all propagated tags from all ancestors + * Handles returning the tags in the desired format * - * This retrieves tags from parents but not local tags + * This function can be overridden to support another tag format. This was + * specifically designed to enable AutoScalingGroup Tags that have an + * additional CloudFormation key for `PropagateAtLaunch` */ + protected tagFormatResolve(tagGroups: TagGroups): any { + const tags = {...tagGroups.nonStickyTags, ...tagGroups.ancestorTags, ...tagGroups.stickyTags}; + for (const key of this.blockedTags) { delete tags[key]; } + return Object.keys(tags).map( key => ({key, value: tags[key]})); + } } diff --git a/packages/@aws-cdk/cdk/test/core/test.tag-manager.ts b/packages/@aws-cdk/cdk/test/core/test.tag-manager.ts index 03d32660a6ee7..ba855531add82 100644 --- a/packages/@aws-cdk/cdk/test/core/test.tag-manager.ts +++ b/packages/@aws-cdk/cdk/test/core/test.tag-manager.ts @@ -9,6 +9,7 @@ class ChildTagger extends Construct implements ITaggable { this.tags = new TagManager(parent); } } + class Child extends Construct { constructor(parent: Construct, name: string) { super(parent, name); @@ -16,7 +17,6 @@ class Child extends Construct { } export = { - 'TagManger handles tags for a Contruct Tree': { 'setTag by default propagates to children'(test: Test) { const root = new Root();