From e8380fae95bfbf8a7abe8f0d15a47de118894090 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Thu, 15 Nov 2018 15:41:36 +0100 Subject: [PATCH] feat(aws-ec2): add VPC context provider (#1168) Add a context provider for looking up existing VPCs in an account. This is useful if the VPC is defined outside of your CDK app, such as in a different CDK app, by hand or in a CloudFormation template. Addresses some of the needs in #1095. --- docs/src/context.rst | 13 ++ .../test/integ.amazonlinux2.expected.json | 32 ++++ ...g.asg-w-classic-loadbalancer.expected.json | 140 +++++++++----- .../test/integ.asg-w-elbv2.expected.json | 98 ++++++---- .../test/integ.deployment-group.expected.json | 84 ++++++-- packages/@aws-cdk/aws-ec2/README.md | 12 ++ packages/@aws-cdk/aws-ec2/lib/index.ts | 1 + .../aws-ec2/lib/vpc-network-provider.ts | 85 +++++++++ packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts | 8 + packages/@aws-cdk/aws-ec2/lib/vpc.ts | 22 +++ packages/@aws-cdk/aws-ec2/package.json | 2 + .../aws-ec2/test/example.share-vpcs.lit.ts | 55 ++++++ .../integ.import-default-vpc.expected.json | 33 ++++ ...integ.import-default-vpc.lit.expected.json | 33 ++++ .../test/integ.import-default-vpc.lit.ts | 24 +++ .../aws-ec2/test/integ.vpc.expected.json | 138 +++++++++----- .../test/ec2/integ.lb-awsvpc-nw.expected.json | 32 ++++ .../test/ec2/integ.lb-bridge-nw.expected.json | 32 ++++ .../fargate/integ.asset-image.expected.json | 32 ++++ .../fargate/integ.lb-awsvpc-nw.expected.json | 32 ++++ .../test/integ.elb.expected.json | 58 ++++-- .../test/integ.alb-alias-target.expected.json | 34 +++- .../test/integ.alb.expected.json | 104 ++++++---- .../test/integ.nlb.expected.json | 92 ++++++--- .../test/integ.vpc-lambda.expected.json | 34 +++- .../aws-rds/test/integ.cluster.expected.json | 100 ++++++---- .../aws-route53/lib/hosted-zone-provider.ts | 54 +++--- packages/@aws-cdk/aws-route53/package.json | 3 +- .../test/integ.route53.expected.json | 138 +++++++++----- packages/@aws-cdk/cdk/lib/context.ts | 29 ++- packages/@aws-cdk/cdk/test/test.context.ts | 19 ++ .../cx-api/lib/context/availability-zones.ts | 22 +++ .../cx-api/lib/context/hosted-zone.ts | 57 ++++++ .../cx-api/lib/context/ssm-parameter.ts | 23 +++ packages/@aws-cdk/cx-api/lib/context/vpc.ts | 83 ++++++++ packages/@aws-cdk/cx-api/lib/index.ts | 4 + packages/aws-cdk/bin/cdk.ts | 10 +- .../context-providers/availability-zones.ts | 22 +++ .../lib/context-providers/hosted-zones.ts | 70 +++++++ .../aws-cdk/lib/context-providers/index.ts | 43 +++++ .../aws-cdk/lib/context-providers/provider.ts | 3 + .../lib/context-providers/ssm-parameters.ts | 28 +++ .../aws-cdk/lib/context-providers/vpcs.ts | 180 ++++++++++++++++++ packages/aws-cdk/lib/contextplugins.ts | 156 --------------- tools/cdk-integ-tools/lib/integ-helpers.ts | 6 + 45 files changed, 1757 insertions(+), 523 deletions(-) create mode 100644 packages/@aws-cdk/aws-ec2/lib/vpc-network-provider.ts create mode 100644 packages/@aws-cdk/aws-ec2/test/example.share-vpcs.lit.ts create mode 100644 packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.expected.json create mode 100644 packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.lit.expected.json create mode 100644 packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.lit.ts create mode 100644 packages/@aws-cdk/cx-api/lib/context/availability-zones.ts create mode 100644 packages/@aws-cdk/cx-api/lib/context/hosted-zone.ts create mode 100644 packages/@aws-cdk/cx-api/lib/context/ssm-parameter.ts create mode 100644 packages/@aws-cdk/cx-api/lib/context/vpc.ts create mode 100644 packages/aws-cdk/lib/context-providers/availability-zones.ts create mode 100644 packages/aws-cdk/lib/context-providers/hosted-zones.ts create mode 100644 packages/aws-cdk/lib/context-providers/index.ts create mode 100644 packages/aws-cdk/lib/context-providers/provider.ts create mode 100644 packages/aws-cdk/lib/context-providers/ssm-parameters.ts create mode 100644 packages/aws-cdk/lib/context-providers/vpcs.ts delete mode 100644 packages/aws-cdk/lib/contextplugins.ts diff --git a/docs/src/context.rst b/docs/src/context.rst index 41af9651a7b81..63f994bbe3ae5 100644 --- a/docs/src/context.rst +++ b/docs/src/context.rst @@ -56,3 +56,16 @@ The |cdk| currently supports the following context providers. const zone: HostedZoneRef = new HostedZoneProvider(this, { domainName: 'test.com' }).findAndImport(this, 'HostedZone'); + +:py:class:`VpcNetworkProvider <@aws-cdk/aws-ec2.VpcNetworkProvider>` + Use this provider to look up and reference existing VPC in your accounts. + For example, the follow code imports a VPC by tag name: + +.. code:: js + + const provider = new VpcNetworkProvider(this, { + tags: { + Purpose: 'WebServices' + } + }); + const vpc = VpcNetworkRef.import(this, 'VPC', provider.vpcProps); diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.amazonlinux2.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.amazonlinux2.expected.json index 75177d33e0f5a..45d5e9f6bac02 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.amazonlinux2.expected.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.amazonlinux2.expected.json @@ -28,6 +28,14 @@ { "Key": "Name", "Value": "aws-cdk-autoscaling-integ/VPC/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -111,6 +119,14 @@ { "Key": "Name", "Value": "aws-cdk-autoscaling-integ/VPC/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -194,6 +210,14 @@ { "Key": "Name", "Value": "aws-cdk-autoscaling-integ/VPC/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -248,6 +272,14 @@ { "Key": "Name", "Value": "aws-cdk-autoscaling-integ/VPC/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } 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 9a0f6425887fe..c0779a7e746a0 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 @@ -28,6 +28,14 @@ { "Key": "Name", "Value": "aws-cdk-ec2-integ/VPC/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -57,6 +65,21 @@ } } }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "VPCVPCGW99B986DC" + ], + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, "VPCPublicSubnet1EIP6AD938E8": { "Type": "AWS::EC2::EIP", "Properties": { @@ -83,21 +106,6 @@ ] } }, - "VPCPublicSubnet1DefaultRoute91CEF279": { - "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], - "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet1RouteTableFEE4B781" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - } - } - }, "VPCPublicSubnet2Subnet74179F39": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -111,6 +119,14 @@ { "Key": "Name", "Value": "aws-cdk-ec2-integ/VPC/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -140,6 +156,21 @@ } } }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "VPCVPCGW99B986DC" + ], + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, "VPCPublicSubnet2EIP4947BC00": { "Type": "AWS::EC2::EIP", "Properties": { @@ -166,21 +197,6 @@ ] } }, - "VPCPublicSubnet2DefaultRouteB7481BBA": { - "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], - "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - } - } - }, "VPCPublicSubnet3Subnet631C5E25": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -194,6 +210,14 @@ { "Key": "Name", "Value": "aws-cdk-ec2-integ/VPC/PublicSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -223,6 +247,21 @@ } } }, + "VPCPublicSubnet3DefaultRouteA0D29D46": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "VPCVPCGW99B986DC" + ], + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, "VPCPublicSubnet3EIPAD4BC883": { "Type": "AWS::EC2::EIP", "Properties": { @@ -249,21 +288,6 @@ ] } }, - "VPCPublicSubnet3DefaultRouteA0D29D46": { - "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], - "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet3RouteTable98AE0E14" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - } - } - }, "VPCPrivateSubnet1Subnet8BCA10E0": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -277,6 +301,14 @@ { "Key": "Name", "Value": "aws-cdk-ec2-integ/VPC/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -331,6 +363,14 @@ { "Key": "Name", "Value": "aws-cdk-ec2-integ/VPC/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -385,6 +425,14 @@ { "Key": "Name", "Value": "aws-cdk-ec2-integ/VPC/PrivateSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -663,4 +711,4 @@ } } } -} +} \ No newline at end of file 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 d229da238d349..1dcf8e409e300 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 @@ -28,6 +28,14 @@ { "Key": "Name", "Value": "aws-cdk-ec2-integ/VPC/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -57,6 +65,21 @@ } } }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "VPCVPCGW99B986DC" + ], + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, "VPCPublicSubnet1EIP6AD938E8": { "Type": "AWS::EC2::EIP", "Properties": { @@ -83,21 +106,6 @@ ] } }, - "VPCPublicSubnet1DefaultRoute91CEF279": { - "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], - "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet1RouteTableFEE4B781" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - } - } - }, "VPCPublicSubnet2Subnet74179F39": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -111,6 +119,14 @@ { "Key": "Name", "Value": "aws-cdk-ec2-integ/VPC/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -140,6 +156,21 @@ } } }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "VPCVPCGW99B986DC" + ], + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, "VPCPublicSubnet2EIP4947BC00": { "Type": "AWS::EC2::EIP", "Properties": { @@ -166,21 +197,6 @@ ] } }, - "VPCPublicSubnet2DefaultRouteB7481BBA": { - "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], - "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - } - } - }, "VPCPrivateSubnet1Subnet8BCA10E0": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -194,6 +210,14 @@ { "Key": "Name", "Value": "aws-cdk-ec2-integ/VPC/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -248,6 +272,14 @@ { "Key": "Name", "Value": "aws-cdk-ec2-integ/VPC/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -530,13 +562,13 @@ "Properties": { "Port": 80, "Protocol": "HTTP", - "TargetType": "instance", "VpcId": { "Ref": "VPCB9E5F0B4" }, "TargetGroupAttributes": [], - "Targets": [] + "Targets": [], + "TargetType": "instance" } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codedeploy/test/integ.deployment-group.expected.json b/packages/@aws-cdk/aws-codedeploy/test/integ.deployment-group.expected.json index 14423dcc00eb4..d8f996ed7cfe8 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/integ.deployment-group.expected.json +++ b/packages/@aws-cdk/aws-codedeploy/test/integ.deployment-group.expected.json @@ -28,6 +28,14 @@ { "Key": "Name", "Value": "aws-cdk-codedeploy-server-dg/VPC/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -59,9 +67,6 @@ }, "VPCPublicSubnet1DefaultRoute91CEF279": { "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], "Properties": { "RouteTableId": { "Ref": "VPCPublicSubnet1RouteTableFEE4B781" @@ -70,7 +75,10 @@ "GatewayId": { "Ref": "VPCIGWB7E252D3" } - } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] }, "VPCPublicSubnet1EIP6AD938E8": { "Type": "AWS::EC2::EIP", @@ -111,6 +119,14 @@ { "Key": "Name", "Value": "aws-cdk-codedeploy-server-dg/VPC/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -142,9 +158,6 @@ }, "VPCPublicSubnet2DefaultRouteB7481BBA": { "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], "Properties": { "RouteTableId": { "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" @@ -153,7 +166,10 @@ "GatewayId": { "Ref": "VPCIGWB7E252D3" } - } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] }, "VPCPublicSubnet2EIP4947BC00": { "Type": "AWS::EC2::EIP", @@ -194,6 +210,14 @@ { "Key": "Name", "Value": "aws-cdk-codedeploy-server-dg/VPC/PublicSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -225,9 +249,6 @@ }, "VPCPublicSubnet3DefaultRouteA0D29D46": { "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], "Properties": { "RouteTableId": { "Ref": "VPCPublicSubnet3RouteTable98AE0E14" @@ -236,7 +257,10 @@ "GatewayId": { "Ref": "VPCIGWB7E252D3" } - } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] }, "VPCPublicSubnet3EIPAD4BC883": { "Type": "AWS::EC2::EIP", @@ -277,6 +301,14 @@ { "Key": "Name", "Value": "aws-cdk-codedeploy-server-dg/VPC/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -331,6 +363,14 @@ { "Key": "Name", "Value": "aws-cdk-codedeploy-server-dg/VPC/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -385,6 +425,14 @@ { "Key": "Name", "Value": "aws-cdk-codedeploy-server-dg/VPC/PrivateSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -635,11 +683,11 @@ "GroupDescription": "aws-cdk-codedeploy-server-dg/ELB/SecurityGroup", "SecurityGroupEgress": [ { - "CidrIp":"255.255.255.255/32", - "Description":"Disallow all traffic", - "FromPort":252, - "IpProtocol":"icmp", - "ToPort":86 + "CidrIp": "255.255.255.255/32", + "Description": "Disallow all traffic", + "FromPort": 252, + "IpProtocol": "icmp", + "ToPort": 86 } ], "SecurityGroupIngress": [ @@ -770,4 +818,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index bbb3b6fd7a59e..7633dd72d859c 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -172,6 +172,18 @@ The `VpcNetwork` above will have the exact same subnet definitions as listed above. However, this time the VPC will have only 1 NAT Gateway and all Application subnets will route to the NAT Gateway. +#### Sharing VPCs across stacks + +If you are creating multiple `Stack`s inside the same CDK application, you +can reuse a VPC defined in one Stack in another by using `export()` and +`import()`: + +[sharing VPCs between stacks](test/example.share-vpcs.lit.ts) + +If your VPC is created outside your CDK app, you can use `importFromContext()`: + +[importing existing VPCs](test/integ.import-default-vpc.lit.ts) + ### Allowing Connections In AWS, all network traffic in and out of **Elastic Network Interfaces** (ENIs) diff --git a/packages/@aws-cdk/aws-ec2/lib/index.ts b/packages/@aws-cdk/aws-ec2/lib/index.ts index 9e6e38d27bba0..bade088217cae 100644 --- a/packages/@aws-cdk/aws-ec2/lib/index.ts +++ b/packages/@aws-cdk/aws-ec2/lib/index.ts @@ -5,6 +5,7 @@ export * from './security-group'; export * from './security-group-rule'; export * from './vpc'; export * from './vpc-ref'; +export * from './vpc-network-provider'; // AWS::EC2 CloudFormation Resources: export * from './ec2.generated'; diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-network-provider.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-network-provider.ts new file mode 100644 index 0000000000000..4312daa66bebb --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-network-provider.ts @@ -0,0 +1,85 @@ +import cdk = require('@aws-cdk/cdk'); +import cxapi = require('@aws-cdk/cx-api'); +import { VpcNetworkRefProps } from './vpc-ref'; + +/** + * Properties for looking up an existing VPC. + * + * The combination of properties must specify filter down to exactly one + * non-default VPC, otherwise an error is raised. + */ +export interface VpcNetworkProviderProps { + /** + * The ID of the VPC + * + * If given, will import exactly this VPC. + * + * @default Don't filter on vpcId + */ + vpcId?: string; + + /** + * The name of the VPC + * + * If given, will import the VPC with this name. + * + * @default Don't filter on vpcName + */ + vpcName?: string; + + /** + * Tags on the VPC + * + * The VPC must have all of these tags + * + * @default Don't filter on tags + */ + tags?: {[key: string]: string}; + + /** + * Whether to match the default VPC + * + * @default Don't care whether we return the default VPC + */ + isDefault?: boolean; +} + +/** + * Context provider to discover and import existing VPCs + */ +export class VpcNetworkProvider { + private provider: cdk.ContextProvider; + + constructor(context: cdk.Construct, props: VpcNetworkProviderProps) { + const filter: {[key: string]: string} = props.tags || {}; + + // We give special treatment to some tags + if (props.vpcId) { filter['vpc-id'] = props.vpcId; } + if (props.vpcName) { filter['tag:Name'] = props.vpcName; } + if (props.isDefault !== undefined) { + filter.isDefault = props.isDefault ? 'true' : 'false'; + } + + this.provider = new cdk.ContextProvider(context, cxapi.VPC_PROVIDER, { filter } as cxapi.VpcContextQuery); + } + + /** + * Return the VPC import props matching the filter + */ + public get vpcProps(): VpcNetworkRefProps { + const ret: cxapi.VpcContextResponse = this.provider.getValue(DUMMY_VPC_PROPS); + return ret; + } +} + +/** + * There are returned when the provider has not supplied props yet + * + * It's only used for testing and on the first run-through. + */ +const DUMMY_VPC_PROPS: cxapi.VpcContextResponse = { + availabilityZones: ['dummy-1a', 'dummy-1b'], + vpcId: 'vpc-12345', + publicSubnetIds: ['s-12345', 's-67890'], + privateSubnetIds: ['p-12345', 'p-67890'], +}; diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts index 59d0381c92f39..9833283d2c7a4 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts @@ -1,5 +1,6 @@ import { Construct, IDependable, Output } from "@aws-cdk/cdk"; import { ExportSubnetGroup, ImportSubnetGroup, subnetName } from './util'; +import { VpcNetworkProvider, VpcNetworkProviderProps } from './vpc-network-provider'; /** * The type of Subnet @@ -81,6 +82,13 @@ export abstract class VpcNetworkRef extends Construct implements IDependable { return new ImportedVpcNetwork(parent, name, props); } + /** + * Import an existing VPC from context + */ + public static importFromContext(parent: Construct, name: string, props: VpcNetworkProviderProps): VpcNetworkRef { + return VpcNetworkRef.import(parent, name, new VpcNetworkProvider(parent, props).vpcProps); + } + /** * Identifier for this VPC */ diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index dc09030a08006..0d3d236c8ccff 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -407,24 +407,46 @@ export class VpcNetwork extends VpcNetworkRef implements cdk.ITaggable { tags: subnetConfig.tags, }; + let subnet: VpcSubnet; switch (subnetConfig.subnetType) { case SubnetType.Public: const publicSubnet = new VpcPublicSubnet(this, name, subnetProps); this.publicSubnets.push(publicSubnet); + subnet = publicSubnet; break; case SubnetType.Private: const privateSubnet = new VpcPrivateSubnet(this, name, subnetProps); this.privateSubnets.push(privateSubnet); + subnet = privateSubnet; break; case SubnetType.Isolated: const isolatedSubnet = new VpcPrivateSubnet(this, name, subnetProps); + isolatedSubnet.tags.setTag(SUBNETTYPE_TAG, subnetTypeTagValue(subnetConfig.subnetType)); this.isolatedSubnets.push(isolatedSubnet); + subnet = isolatedSubnet; break; + default: + throw new Error(`Unrecognized subnet type: ${subnetConfig.subnetType}`); } + + // These values will be used to recover the config upon provider import + subnet.tags.setTag(SUBNETNAME_TAG, subnetConfig.name, { propagate: false }); + subnet.tags.setTag(SUBNETTYPE_TAG, subnetTypeTagValue(subnetConfig.subnetType), { propagate: false }); }); } } +const SUBNETTYPE_TAG = 'aws-cdk:subnet-type'; +const SUBNETNAME_TAG = 'aws-cdk:subnet-name'; + +function subnetTypeTagValue(type: SubnetType) { + switch (type) { + case SubnetType.Public: return 'Public'; + case SubnetType.Private: return 'Private'; + case SubnetType.Isolated: return 'Isolated'; + } +} + /** * Specify configuration parameters for a VPC subnet */ diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index d6e0ed527cd3c..4b7ce3bb1c070 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -60,10 +60,12 @@ }, "dependencies": { "@aws-cdk/aws-iam": "^0.17.0", + "@aws-cdk/cx-api": "^0.17.0", "@aws-cdk/cdk": "^0.17.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { + "@aws-cdk/cx-api": "^0.17.0", "@aws-cdk/cdk": "^0.17.0" } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/example.share-vpcs.lit.ts b/packages/@aws-cdk/aws-ec2/test/example.share-vpcs.lit.ts new file mode 100644 index 0000000000000..6a6f4e997193c --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/example.share-vpcs.lit.ts @@ -0,0 +1,55 @@ +import cdk = require('@aws-cdk/cdk'); +import ec2 = require("../lib"); + +const app = new cdk.App(); + +interface ConstructThatTakesAVpcProps { + vpc: ec2.VpcNetworkRef; +} + +class ConstructThatTakesAVpc extends cdk.Construct { + constructor(parent: cdk.Construct, id: string, _props: ConstructThatTakesAVpcProps) { + super(parent, id); + } +} + +/// !show +class Stack1 extends cdk.Stack { + public readonly vpcProps: ec2.VpcNetworkRefProps; + + constructor(parent: cdk.App, id: string, props?: cdk.StackProps) { + super(parent, id, props); + + const vpc = new ec2.VpcNetwork(this, 'VPC'); + + // Export the VPC to a set of properties + this.vpcProps = vpc.export(); + } +} + +interface Stack2Props extends cdk.StackProps { + vpcProps: ec2.VpcNetworkRefProps; +} + +class Stack2 extends cdk.Stack { + constructor(parent: cdk.App, id: string, props: Stack2Props) { + super(parent, id, props); + + // Import the VPC from a set of properties + const vpc = ec2.VpcNetworkRef.import(this, 'VPC', props.vpcProps); + + new ConstructThatTakesAVpc(this, 'Construct', { + vpc + }); + } +} + +const stack1 = new Stack1(app, 'Stack1'); +const stack2 = new Stack2(app, 'Stack2', { + vpcProps: stack1.vpcProps +}); +/// !hide + +Array.isArray(stack2); + +app.run(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.expected.json b/packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.expected.json new file mode 100644 index 0000000000000..37edfef83075b --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.expected.json @@ -0,0 +1,33 @@ +{ + "Resources": { + "SecurityGroupDD263621": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-cdk-ec2-import/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [], + "VpcId": "vpc-60900905" + } + } + }, + "Outputs": { + "PublicSubnets": { + "Value": "ids:subnet-e19455ca,subnet-e0c24797,subnet-ccd77395", + "Export": { + "Name": "aws-cdk-ec2-import:PublicSubnets" + } + }, + "PrivateSubnets": { + "Value": "ids:", + "Export": { + "Name": "aws-cdk-ec2-import:PrivateSubnets" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.lit.expected.json b/packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.lit.expected.json new file mode 100644 index 0000000000000..37edfef83075b --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.lit.expected.json @@ -0,0 +1,33 @@ +{ + "Resources": { + "SecurityGroupDD263621": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-cdk-ec2-import/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [], + "VpcId": "vpc-60900905" + } + } + }, + "Outputs": { + "PublicSubnets": { + "Value": "ids:subnet-e19455ca,subnet-e0c24797,subnet-ccd77395", + "Export": { + "Name": "aws-cdk-ec2-import:PublicSubnets" + } + }, + "PrivateSubnets": { + "Value": "ids:", + "Export": { + "Name": "aws-cdk-ec2-import:PrivateSubnets" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.lit.ts b/packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.lit.ts new file mode 100644 index 0000000000000..2a9fb8921c65f --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.lit.ts @@ -0,0 +1,24 @@ +import cdk = require('@aws-cdk/cdk'); +import ec2 = require("../lib"); + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-cdk-ec2-import'); + +/// !show +const vpc = ec2.VpcNetworkRef.importFromContext(stack, 'VPC', { + // This imports the default VPC but you can also + // specify a 'vpcName' or 'tags'. + isDefault: true +}); +/// !hide + +// The only thing in this library that takes a VPC as an argument :) +new ec2.SecurityGroup(stack, 'SecurityGroup', { + vpc +}); + +// Try subnet selection +new cdk.Output(stack, 'PublicSubnets', { value: 'ids:' + vpc.subnets({ subnetsToUse: ec2.SubnetType.Public }).map(s => s.subnetId).join(',') }); +new cdk.Output(stack, 'PrivateSubnets', { value: 'ids:' + vpc.subnets({ subnetsToUse: ec2.SubnetType.Private }).map(s => s.subnetId).join(',') }); + +app.run(); diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpc.expected.json b/packages/@aws-cdk/aws-ec2/test/integ.vpc.expected.json index 6699c85efbb48..7ac8bc19f1ffb 100644 --- a/packages/@aws-cdk/aws-ec2/test/integ.vpc.expected.json +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpc.expected.json @@ -28,6 +28,14 @@ { "Key": "Name", "Value": "aws-cdk-ec2-vpc/MyVpc/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -57,6 +65,21 @@ } } }, + "MyVpcPublicSubnet1DefaultRoute95FDF9EB": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "MyVpcVPCGW488ACE0D" + ], + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + } + }, "MyVpcPublicSubnet1EIP096967CB": { "Type": "AWS::EC2::EIP", "Properties": { @@ -83,21 +106,6 @@ ] } }, - "MyVpcPublicSubnet1DefaultRoute95FDF9EB": { - "Type": "AWS::EC2::Route", - "DependsOn": [ - "MyVpcVPCGW488ACE0D" - ], - "Properties": { - "RouteTableId": { - "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "MyVpcIGW5C4A4F63" - } - } - }, "MyVpcPublicSubnet2Subnet492B6BFB": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -111,6 +119,14 @@ { "Key": "Name", "Value": "aws-cdk-ec2-vpc/MyVpc/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -140,6 +156,21 @@ } } }, + "MyVpcPublicSubnet2DefaultRoute052936F6": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "MyVpcVPCGW488ACE0D" + ], + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + } + }, "MyVpcPublicSubnet2EIP8CCBA239": { "Type": "AWS::EC2::EIP", "Properties": { @@ -166,21 +197,6 @@ ] } }, - "MyVpcPublicSubnet2DefaultRoute052936F6": { - "Type": "AWS::EC2::Route", - "DependsOn": [ - "MyVpcVPCGW488ACE0D" - ], - "Properties": { - "RouteTableId": { - "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "MyVpcIGW5C4A4F63" - } - } - }, "MyVpcPublicSubnet3Subnet57EEE236": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -194,6 +210,14 @@ { "Key": "Name", "Value": "aws-cdk-ec2-vpc/MyVpc/PublicSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -223,6 +247,21 @@ } } }, + "MyVpcPublicSubnet3DefaultRoute3A83AB36": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "MyVpcVPCGW488ACE0D" + ], + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet3RouteTable15028F08" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + } + }, "MyVpcPublicSubnet3EIPC5ACADAB": { "Type": "AWS::EC2::EIP", "Properties": { @@ -249,21 +288,6 @@ ] } }, - "MyVpcPublicSubnet3DefaultRoute3A83AB36": { - "Type": "AWS::EC2::Route", - "DependsOn": [ - "MyVpcVPCGW488ACE0D" - ], - "Properties": { - "RouteTableId": { - "Ref": "MyVpcPublicSubnet3RouteTable15028F08" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "MyVpcIGW5C4A4F63" - } - } - }, "MyVpcPrivateSubnet1Subnet5057CF7E": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -277,6 +301,14 @@ { "Key": "Name", "Value": "aws-cdk-ec2-vpc/MyVpc/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -331,6 +363,14 @@ { "Key": "Name", "Value": "aws-cdk-ec2-vpc/MyVpc/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -385,6 +425,14 @@ { "Key": "Name", "Value": "aws-cdk-ec2-vpc/MyVpc/PrivateSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json index 056c03d04281d..b048e70e261b4 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json @@ -28,6 +28,14 @@ { "Key": "Name", "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -111,6 +119,14 @@ { "Key": "Name", "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -194,6 +210,14 @@ { "Key": "Name", "Value": "aws-ecs-integ/Vpc/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -248,6 +272,14 @@ { "Key": "Name", "Value": "aws-ecs-integ/Vpc/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json index a49def3f4b680..5bdb77145f970 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json @@ -28,6 +28,14 @@ { "Key": "Name", "Value": "aws-ecs-integ-ecs/Vpc/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -111,6 +119,14 @@ { "Key": "Name", "Value": "aws-ecs-integ-ecs/Vpc/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -194,6 +210,14 @@ { "Key": "Name", "Value": "aws-ecs-integ-ecs/Vpc/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -248,6 +272,14 @@ { "Key": "Name", "Value": "aws-ecs-integ-ecs/Vpc/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.asset-image.expected.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.asset-image.expected.json index fb0ae46ef8f3d..8dc76ed4166a5 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.asset-image.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.asset-image.expected.json @@ -28,6 +28,14 @@ { "Key": "Name", "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -111,6 +119,14 @@ { "Key": "Name", "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -194,6 +210,14 @@ { "Key": "Name", "Value": "aws-ecs-integ/Vpc/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -248,6 +272,14 @@ { "Key": "Name", "Value": "aws-ecs-integ/Vpc/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.expected.json index 039f526419214..690bee1da284b 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.expected.json @@ -28,6 +28,14 @@ { "Key": "Name", "Value": "aws-ecs-integ/Vpc/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -111,6 +119,14 @@ { "Key": "Name", "Value": "aws-ecs-integ/Vpc/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -194,6 +210,14 @@ { "Key": "Name", "Value": "aws-ecs-integ/Vpc/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -248,6 +272,14 @@ { "Key": "Name", "Value": "aws-ecs-integ/Vpc/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } 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 9155d42d526b6..c61fcd74337c6 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/test/integ.elb.expected.json +++ b/packages/@aws-cdk/aws-elasticloadbalancing/test/integ.elb.expected.json @@ -28,6 +28,14 @@ { "Key": "Name", "Value": "aws-cdk-elb-integ/VPC/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -57,6 +65,21 @@ } } }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "VPCVPCGW99B986DC" + ], + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, "VPCPublicSubnet1EIP6AD938E8": { "Type": "AWS::EC2::EIP", "Properties": { @@ -83,21 +106,6 @@ ] } }, - "VPCPublicSubnet1DefaultRoute91CEF279": { - "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], - "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet1RouteTableFEE4B781" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - } - } - }, "VPCPrivateSubnet1Subnet8BCA10E0": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -111,6 +119,14 @@ { "Key": "Name", "Value": "aws-cdk-elb-integ/VPC/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -180,11 +196,11 @@ "GroupDescription": "aws-cdk-elb-integ/LB/SecurityGroup", "SecurityGroupEgress": [ { - "CidrIp":"255.255.255.255/32", - "Description":"Disallow all traffic", - "FromPort":252, - "IpProtocol":"icmp", - "ToPort":86 + "CidrIp": "255.255.255.255/32", + "Description": "Disallow all traffic", + "FromPort": 252, + "IpProtocol": "icmp", + "ToPort": 86 } ], "SecurityGroupIngress": [ @@ -236,4 +252,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb-alias-target.expected.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb-alias-target.expected.json index e1cab64dac4c1..2a2d938388c3f 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb-alias-target.expected.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb-alias-target.expected.json @@ -28,6 +28,14 @@ { "Key": "Name", "Value": "aws-cdk-elbv2-integ/VPC/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -111,6 +119,14 @@ { "Key": "Name", "Value": "aws-cdk-elbv2-integ/VPC/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -194,6 +210,14 @@ { "Key": "Name", "Value": "aws-cdk-elbv2-integ/VPC/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -248,6 +272,14 @@ { "Key": "Name", "Value": "aws-cdk-elbv2-integ/VPC/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -385,4 +417,4 @@ } } } -} +} \ No newline at end of file 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 a920725179f4a..9473d122acc7e 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.expected.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.expected.json @@ -28,6 +28,14 @@ { "Key": "Name", "Value": "aws-cdk-elbv2-integ/VPC/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -57,6 +65,21 @@ } } }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "VPCVPCGW99B986DC" + ], + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, "VPCPublicSubnet1EIP6AD938E8": { "Type": "AWS::EC2::EIP", "Properties": { @@ -83,21 +106,6 @@ ] } }, - "VPCPublicSubnet1DefaultRoute91CEF279": { - "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], - "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet1RouteTableFEE4B781" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - } - } - }, "VPCPublicSubnet2Subnet74179F39": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -111,6 +119,14 @@ { "Key": "Name", "Value": "aws-cdk-elbv2-integ/VPC/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -140,6 +156,21 @@ } } }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "VPCVPCGW99B986DC" + ], + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, "VPCPublicSubnet2EIP4947BC00": { "Type": "AWS::EC2::EIP", "Properties": { @@ -166,21 +197,6 @@ ] } }, - "VPCPublicSubnet2DefaultRouteB7481BBA": { - "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], - "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - } - } - }, "VPCPrivateSubnet1Subnet8BCA10E0": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -194,6 +210,14 @@ { "Key": "Name", "Value": "aws-cdk-elbv2-integ/VPC/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -248,6 +272,14 @@ { "Key": "Name", "Value": "aws-cdk-elbv2-integ/VPC/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -341,11 +373,11 @@ "GroupDescription": "Automatically created Security Group for ELB awscdkelbv2integLB9950B1E4", "SecurityGroupEgress": [ { - "CidrIp":"255.255.255.255/32", - "Description":"Disallow all traffic", - "FromPort":252, - "IpProtocol":"icmp", - "ToPort":86 + "CidrIp": "255.255.255.255/32", + "Description": "Disallow all traffic", + "FromPort": 252, + "IpProtocol": "icmp", + "ToPort": 86 } ], "SecurityGroupIngress": [ @@ -441,4 +473,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.expected.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.expected.json index 8371578b42a29..38a08693cf2fc 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.expected.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.expected.json @@ -28,6 +28,14 @@ { "Key": "Name", "Value": "aws-cdk-elbv2-integ/VPC/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -57,6 +65,21 @@ } } }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "VPCVPCGW99B986DC" + ], + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, "VPCPublicSubnet1EIP6AD938E8": { "Type": "AWS::EC2::EIP", "Properties": { @@ -83,21 +106,6 @@ ] } }, - "VPCPublicSubnet1DefaultRoute91CEF279": { - "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], - "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet1RouteTableFEE4B781" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - } - } - }, "VPCPublicSubnet2Subnet74179F39": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -111,6 +119,14 @@ { "Key": "Name", "Value": "aws-cdk-elbv2-integ/VPC/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -140,6 +156,21 @@ } } }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "VPCVPCGW99B986DC" + ], + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, "VPCPublicSubnet2EIP4947BC00": { "Type": "AWS::EC2::EIP", "Properties": { @@ -166,21 +197,6 @@ ] } }, - "VPCPublicSubnet2DefaultRouteB7481BBA": { - "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], - "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - } - } - }, "VPCPrivateSubnet1Subnet8BCA10E0": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -194,6 +210,14 @@ { "Key": "Name", "Value": "aws-cdk-elbv2-integ/VPC/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -248,6 +272,14 @@ { "Key": "Name", "Value": "aws-cdk-elbv2-integ/VPC/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } 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 c062ac71ba8b4..3325043c8414e 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 @@ -28,6 +28,14 @@ { "Key": "Name", "Value": "aws-cdk-vpc-lambda/VPC/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -111,6 +119,14 @@ { "Key": "Name", "Value": "aws-cdk-vpc-lambda/VPC/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -194,6 +210,14 @@ { "Key": "Name", "Value": "aws-cdk-vpc-lambda/VPC/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -248,6 +272,14 @@ { "Key": "Name", "Value": "aws-cdk-vpc-lambda/VPC/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -409,4 +441,4 @@ ] } } -} +} \ No newline at end of file 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 d880c015af506..eb14dcf760fbc 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json @@ -28,6 +28,14 @@ { "Key": "Name", "Value": "aws-cdk-rds-integ/VPC/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -57,6 +65,21 @@ } } }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "VPCVPCGW99B986DC" + ], + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, "VPCPublicSubnet1EIP6AD938E8": { "Type": "AWS::EC2::EIP", "Properties": { @@ -83,21 +106,6 @@ ] } }, - "VPCPublicSubnet1DefaultRoute91CEF279": { - "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], - "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet1RouteTableFEE4B781" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - } - } - }, "VPCPublicSubnet2Subnet74179F39": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -111,6 +119,14 @@ { "Key": "Name", "Value": "aws-cdk-rds-integ/VPC/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -140,6 +156,21 @@ } } }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "VPCVPCGW99B986DC" + ], + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, "VPCPublicSubnet2EIP4947BC00": { "Type": "AWS::EC2::EIP", "Properties": { @@ -166,21 +197,6 @@ ] } }, - "VPCPublicSubnet2DefaultRouteB7481BBA": { - "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], - "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - } - } - }, "VPCPrivateSubnet1Subnet8BCA10E0": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -194,6 +210,14 @@ { "Key": "Name", "Value": "aws-cdk-rds-integ/VPC/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -248,6 +272,14 @@ { "Key": "Name", "Value": "aws-cdk-rds-integ/VPC/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -388,9 +420,9 @@ "GroupDescription": "RDS security group", "SecurityGroupEgress": [ { - "CidrIp":"0.0.0.0/0", - "Description":"Allow all outbound traffic by default", - "IpProtocol":"-1" + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" } ], "SecurityGroupIngress": [], @@ -493,4 +525,4 @@ ] } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts b/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts index f1bf7205a9d9b..fc3d01bf88e2f 100644 --- a/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts +++ b/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts @@ -1,4 +1,5 @@ import cdk = require('@aws-cdk/cdk'); +import cxapi = require('@aws-cdk/cx-api'); import { HostedZoneRef, HostedZoneRefProps } from './hosted-zone-ref'; /** @@ -21,25 +22,18 @@ export interface HostedZoneProviderProps { vpcId?: string; } -const HOSTED_ZONE_PROVIDER = 'hosted-zone'; - -const DEFAULT_HOSTED_ZONE: HostedZoneRefProps = { - hostedZoneId: '/hostedzone/DUMMY', - zoneName: 'example.com', +const DEFAULT_HOSTED_ZONE: HostedZoneContextResponse = { + Id: '/hostedzone/DUMMY', + Name: 'example.com', }; -interface AwsHostedZone { - Id: string; - Name: string; -} - /** * Context provider that will lookup the Hosted Zone ID for the given arguments */ export class HostedZoneProvider { private provider: cdk.ContextProvider; constructor(context: cdk.Construct, props: HostedZoneProviderProps) { - this.provider = new cdk.ContextProvider(context, HOSTED_ZONE_PROVIDER, props); + this.provider = new cdk.ContextProvider(context, cxapi.HOSTED_ZONE_PROVIDER, props); } /** @@ -52,27 +46,23 @@ export class HostedZoneProvider { * Return the hosted zone meeting the filter */ public findHostedZone(): HostedZoneRefProps { - const zone = this.provider.getValue(DEFAULT_HOSTED_ZONE); - if (zone === DEFAULT_HOSTED_ZONE) { - return zone; - } - if (!this.isAwsHostedZone(zone)) { - throw new Error(`Expected an AWS Hosted Zone received ${JSON.stringify(zone)}`); - } else { - const actualZone = zone as AwsHostedZone; - // CDK handles the '.' at the end, so remove it here - if (actualZone.Name.endsWith('.')) { - actualZone.Name = actualZone.Name.substring(0, actualZone.Name.length - 1); - } - return { - hostedZoneId: actualZone.Id, - zoneName: actualZone.Name, - }; + const zone = this.provider.getValue(DEFAULT_HOSTED_ZONE) as HostedZoneContextResponse; + // CDK handles the '.' at the end, so remove it here + if (zone.Name.endsWith('.')) { + zone.Name = zone.Name.substring(0, zone.Name.length - 1); } - } - - private isAwsHostedZone(zone: AwsHostedZone | any): zone is AwsHostedZone { - const candidateZone = zone as AwsHostedZone; - return candidateZone.Name !== undefined && candidateZone.Id !== undefined; + return { + hostedZoneId: zone.Id, + zoneName: zone.Name, + }; } } + +/** + * A mirror of the definition in cxapi, but can use the capital letters + * since it doesn't need to be published via JSII. + */ +interface HostedZoneContextResponse { + Id: string; + Name: string; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index 6d81f59c74c25..192f9742ba988 100644 --- a/packages/@aws-cdk/aws-route53/package.json +++ b/packages/@aws-cdk/aws-route53/package.json @@ -62,7 +62,8 @@ "dependencies": { "@aws-cdk/aws-ec2": "^0.17.0", "@aws-cdk/aws-logs": "^0.17.0", - "@aws-cdk/cdk": "^0.17.0" + "@aws-cdk/cdk": "^0.17.0", + "@aws-cdk/cx-api": "^0.17.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { diff --git a/packages/@aws-cdk/aws-route53/test/integ.route53.expected.json b/packages/@aws-cdk/aws-route53/test/integ.route53.expected.json index 1d888b6f9eb1f..a35b1b55fcfd2 100644 --- a/packages/@aws-cdk/aws-route53/test/integ.route53.expected.json +++ b/packages/@aws-cdk/aws-route53/test/integ.route53.expected.json @@ -28,6 +28,14 @@ { "Key": "Name", "Value": "aws-cdk-route53-integ/VPC/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -57,6 +65,21 @@ } } }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "VPCVPCGW99B986DC" + ], + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, "VPCPublicSubnet1EIP6AD938E8": { "Type": "AWS::EC2::EIP", "Properties": { @@ -83,21 +106,6 @@ ] } }, - "VPCPublicSubnet1DefaultRoute91CEF279": { - "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], - "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet1RouteTableFEE4B781" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - } - } - }, "VPCPublicSubnet2Subnet74179F39": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -111,6 +119,14 @@ { "Key": "Name", "Value": "aws-cdk-route53-integ/VPC/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -140,6 +156,21 @@ } } }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "VPCVPCGW99B986DC" + ], + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, "VPCPublicSubnet2EIP4947BC00": { "Type": "AWS::EC2::EIP", "Properties": { @@ -166,21 +197,6 @@ ] } }, - "VPCPublicSubnet2DefaultRouteB7481BBA": { - "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], - "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - } - } - }, "VPCPublicSubnet3Subnet631C5E25": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -194,6 +210,14 @@ { "Key": "Name", "Value": "aws-cdk-route53-integ/VPC/PublicSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" } ] } @@ -223,6 +247,21 @@ } } }, + "VPCPublicSubnet3DefaultRouteA0D29D46": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "VPCVPCGW99B986DC" + ], + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, "VPCPublicSubnet3EIPAD4BC883": { "Type": "AWS::EC2::EIP", "Properties": { @@ -249,21 +288,6 @@ ] } }, - "VPCPublicSubnet3DefaultRouteA0D29D46": { - "Type": "AWS::EC2::Route", - "DependsOn": [ - "VPCVPCGW99B986DC" - ], - "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet3RouteTable98AE0E14" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - } - } - }, "VPCPrivateSubnet1Subnet8BCA10E0": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -277,6 +301,14 @@ { "Key": "Name", "Value": "aws-cdk-route53-integ/VPC/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -331,6 +363,14 @@ { "Key": "Name", "Value": "aws-cdk-route53-integ/VPC/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } @@ -385,6 +425,14 @@ { "Key": "Name", "Value": "aws-cdk-route53-integ/VPC/PrivateSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" } ] } diff --git a/packages/@aws-cdk/cdk/lib/context.ts b/packages/@aws-cdk/cdk/lib/context.ts index 49fef72abbd72..b215e20193ee2 100644 --- a/packages/@aws-cdk/cdk/lib/context.ts +++ b/packages/@aws-cdk/cdk/lib/context.ts @@ -1,9 +1,7 @@ +import cxapi = require('@aws-cdk/cx-api'); import { Stack } from './cloudformation/stack'; import { Construct } from './core/construct'; -const AVAILABILITY_ZONES_PROVIDER = 'availability-zones'; -const SSM_PARAMETER_PROVIDER = 'ssm'; - type ContextProviderProps = {[key: string]: any}; /** * Base class for the model side of context providers @@ -136,7 +134,7 @@ export class AvailabilityZoneProvider { private provider: ContextProvider; constructor(context: Construct) { - this.provider = new ContextProvider(context, AVAILABILITY_ZONES_PROVIDER); + this.provider = new ContextProvider(context, cxapi.AVAILABILITY_ZONE_PROVIDER); } /** @@ -161,7 +159,7 @@ export class SSMParameterProvider { private provider: ContextProvider; constructor(context: Construct, props: SSMParameterProviderProps) { - this.provider = new ContextProvider(context, SSM_PARAMETER_PROVIDER, props); + this.provider = new ContextProvider(context, cxapi.SSM_PARAMETER_PROVIDER, props); } /** @@ -181,27 +179,26 @@ function formatMissingScopeError(provider: string, props: {[key: string]: string return s; } -function propsToArray(props: {[key: string]: any}): string[] { - const propArray: string[] = []; - const keys = Object.keys(props); - keys.sort(); - for (const key of keys) { +function propsToArray(props: {[key: string]: any}, keyPrefix = ''): string[] { + const ret: string[] = []; + + for (const key of Object.keys(props)) { switch (typeof props[key]) { case 'object': { - const childObjStrs = propsToArray(props[key]); - const qualifiedChildStr = childObjStrs.map( child => (`${key}.${child}`)).join(':'); - propArray.push(qualifiedChildStr); + ret.push(...propsToArray(props[key], `${keyPrefix}${key}.`)); break; } case 'string': { - propArray.push(`${key}=${colonQuote(props[key])}`); + ret.push(`${keyPrefix}${key}=${colonQuote(props[key])}`); break; } default: { - propArray.push(`${key}=${JSON.stringify(props[key])}`); + ret.push(`${keyPrefix}${key}=${JSON.stringify(props[key])}`); break; } } } - return propArray; + + ret.sort(); + return ret; } diff --git a/packages/@aws-cdk/cdk/test/test.context.ts b/packages/@aws-cdk/cdk/test/test.context.ts index 4a9b67b559abb..f061a16c4b443 100644 --- a/packages/@aws-cdk/cdk/test/test.context.ts +++ b/packages/@aws-cdk/cdk/test/test.context.ts @@ -59,6 +59,25 @@ export = { 'vpc:account=12345:cidrBlock=192.168.0.16:igw=false:region=us-east-1:tags.Env=Preprod:tags.Name=MyVPC'); test.done(); }, + + 'Key generation can contain arbitrarily deep structures'(test: Test) { + // GIVEN + const stack = new Stack(undefined, 'TestStack', { env: { account: '12345', region: 'us-east-1' } }); + + // WHEN + const provider = new ContextProvider(stack, 'provider', { + list: [ + { key: 'key1', value: 'value1' }, + { key: 'key2', value: 'value2' }, + ], + }); + + // THEN + test.equals(provider.key, 'provider:account=12345:list.0.key=key1:list.0.value=value1:list.1.key=key2:list.1.value=value2:region=us-east-1'); + + test.done(); + }, + 'SSM parameter provider will return context values if available'(test: Test) { const stack = new Stack(undefined, 'TestStack', { env: { account: '12345', region: 'us-east-1' } }); new SSMParameterProvider(stack, {parameterName: 'test'}).parameterValue(); diff --git a/packages/@aws-cdk/cx-api/lib/context/availability-zones.ts b/packages/@aws-cdk/cx-api/lib/context/availability-zones.ts new file mode 100644 index 0000000000000..6850c2e593fc1 --- /dev/null +++ b/packages/@aws-cdk/cx-api/lib/context/availability-zones.ts @@ -0,0 +1,22 @@ +export const AVAILABILITY_ZONE_PROVIDER = 'availability-zones'; + +/** + * Query to hosted zone context provider + */ +export interface AvailabilityZonesContextQuery { + /** + * Query account + */ + account?: string; + + /** + * Query region + */ + region?: string; + +} + +/** + * Response of the AZ provider looks like this + */ +export type AvailabilityZonesContextResponse = string[]; \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/lib/context/hosted-zone.ts b/packages/@aws-cdk/cx-api/lib/context/hosted-zone.ts new file mode 100644 index 0000000000000..7862b0affac82 --- /dev/null +++ b/packages/@aws-cdk/cx-api/lib/context/hosted-zone.ts @@ -0,0 +1,57 @@ +export const HOSTED_ZONE_PROVIDER = 'hosted-zone'; + +/** + * Query to hosted zone context provider + */ +export interface HostedZoneContextQuery { + /** + * Query account + */ + account?: string; + + /** + * Query region + */ + region?: string; + + /** + * The domain name e.g. example.com to lookup + */ + domainName: string; + + /** + * True if the zone you want to find is a private hosted zone + */ + privateZone?: boolean; + + /** + * The VPC ID to that the private zone must be associated with + * + * If you provide VPC ID and privateZone is false, this will return no results + * and raise an error. + */ + vpcId?: string; +} + +/** + * Hosted zone context + * + * This definition is for human reference. It is not machine-checked as the + * naming conventions used in it are not JSII compatible, and changing those + * introduces a backwards incompatibility. + */ +// export interface HostedZoneContextResponse { +// /** +// * The ID that Amazon Route 53 assigned to the hosted zone when you created +// * it. +// */ +// Id: string; + +// /** +// * The name of the domain. For public hosted zones, this is the name that you +// * have registered with your DNS registrar. For information about how to +// * specify characters other than a-z, 0-9, and - (hyphen) and how to specify +// * internationalized domain names, see CreateHostedZone. +// */ +// Name: string; +// } \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/lib/context/ssm-parameter.ts b/packages/@aws-cdk/cx-api/lib/context/ssm-parameter.ts new file mode 100644 index 0000000000000..caf807aca1ceb --- /dev/null +++ b/packages/@aws-cdk/cx-api/lib/context/ssm-parameter.ts @@ -0,0 +1,23 @@ +export const SSM_PARAMETER_PROVIDER = 'ssm'; + +/** + * Query to hosted zone context provider + */ +export interface SSMParameterContextQuery { + /** + * Query account + */ + account?: string; + + /** + * Query region + */ + region?: string; + + /** + * Parameter name to query + */ + parameterName?: string; +} + +// Response is a string \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/lib/context/vpc.ts b/packages/@aws-cdk/cx-api/lib/context/vpc.ts new file mode 100644 index 0000000000000..a4513b2567a99 --- /dev/null +++ b/packages/@aws-cdk/cx-api/lib/context/vpc.ts @@ -0,0 +1,83 @@ +export const VPC_PROVIDER = 'vpc-provider'; + +/** + * Query input for looking up a VPC + */ +export interface VpcContextQuery { + /** + * Query account + */ + account?: string; + + /** + * Query region + */ + region?: string; + + /** + * Filters to apply to the VPC + * + * Filter parameters are the same as passed to DescribeVpcs. + * + * @see https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeVpcs.html + */ + filter: {[key: string]: string}; +} + +/** + * Properties of a discovered VPC + */ +export interface VpcContextResponse { + + /** + * VPC id + */ + vpcId: string; + + /** + * AZs + */ + availabilityZones: string[]; + + /** + * IDs of all public subnets + * + * Element count: #(availabilityZones) · #(publicGroups) + */ + publicSubnetIds?: string[]; + + /** + * Name of public subnet groups + * + * Element count: #(publicGroups) + */ + publicSubnetNames?: string[]; + + /** + * IDs of all private subnets + * + * Element count: #(availabilityZones) · #(privateGroups) + */ + privateSubnetIds?: string[]; + + /** + * Name of private subnet groups + * + * Element count: #(privateGroups) + */ + privateSubnetNames?: string[]; + + /** + * IDs of all isolated subnets + * + * Element count: #(availabilityZones) · #(isolatedGroups) + */ + isolatedSubnetIds?: string[]; + + /** + * Name of isolated subnet groups + * + * Element count: #(isolatedGroups) + */ + isolatedSubnetNames?: string[]; +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/lib/index.ts b/packages/@aws-cdk/cx-api/lib/index.ts index 6077a01aa9581..7856e10f5b787 100644 --- a/packages/@aws-cdk/cx-api/lib/index.ts +++ b/packages/@aws-cdk/cx-api/lib/index.ts @@ -1,2 +1,6 @@ export * from './cxapi'; export * from './environment'; +export * from './context/hosted-zone'; +export * from './context/vpc'; +export * from './context/ssm-parameter'; +export * from './context/availability-zones'; diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index 12e7355c3877e..558eb10a4ea8f 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -10,7 +10,7 @@ import yargs = require('yargs'); import cdkUtil = require('../lib/util'); import { bootstrapEnvironment, deployStack, destroyStack, loadToolkitInfo, Mode, SDK } from '../lib'; -import contextplugins = require('../lib/contextplugins'); +import contextproviders = require('../lib/context-providers/index'); import { printStackDiff } from '../lib/diff'; import { execProgram } from '../lib/exec'; import { availableInitLanguages, cliInit, printAvailableTemplates } from '../lib/init'; @@ -115,12 +115,6 @@ async function initCommandLine() { ec2creds: argv.ec2creds, }); - const availableContextProviders: contextplugins.ProviderMap = { - 'availability-zones': new contextplugins.AZContextProviderPlugin(aws), - 'ssm': new contextplugins.SSMContextProviderPlugin(aws), - 'hosted-zone': new contextplugins.HostedZoneContextProviderPlugin(aws), - }; - const defaultConfig = new Settings({ versionReporting: true }); const userConfig = await new Settings().load(PER_USER_DEFAULTS); const projectConfig = await new Settings().load(DEFAULTS); @@ -384,7 +378,7 @@ async function initCommandLine() { if (!cdkUtil.isEmpty(allMissing)) { debug(`Some context information is missing. Fetching...`); - await contextplugins.provideContextValues(allMissing, projectConfig, availableContextProviders); + await contextproviders.provideContextValues(allMissing, projectConfig, aws); // Cache the new context to disk await projectConfig.save(DEFAULTS); diff --git a/packages/aws-cdk/lib/context-providers/availability-zones.ts b/packages/aws-cdk/lib/context-providers/availability-zones.ts new file mode 100644 index 0000000000000..f6f80c60150c3 --- /dev/null +++ b/packages/aws-cdk/lib/context-providers/availability-zones.ts @@ -0,0 +1,22 @@ +import { Mode, SDK } from '../api'; +import { debug } from '../logging'; +import { ContextProviderPlugin } from './provider'; + +/** + * Plugin to retrieve the Availability Zones for the current account + */ +export class AZContextProviderPlugin implements ContextProviderPlugin { + constructor(private readonly aws: SDK) { + } + + public async getValue(args: {[key: string]: any}) { + const region = args.region; + const account = args.account; + debug(`Reading AZs for ${account}:${region}`); + const ec2 = await this.aws.ec2(account, region, Mode.ForReading); + const response = await ec2.describeAvailabilityZones().promise(); + if (!response.AvailabilityZones) { return []; } + const azs = response.AvailabilityZones.filter(zone => zone.State === 'available').map(zone => zone.ZoneName); + return azs; + } +} \ No newline at end of file diff --git a/packages/aws-cdk/lib/context-providers/hosted-zones.ts b/packages/aws-cdk/lib/context-providers/hosted-zones.ts new file mode 100644 index 0000000000000..28773aefbd3a6 --- /dev/null +++ b/packages/aws-cdk/lib/context-providers/hosted-zones.ts @@ -0,0 +1,70 @@ +import cxapi = require('@aws-cdk/cx-api'); +import { Mode, SDK } from '../api'; +import { debug } from '../logging'; +import { ContextProviderPlugin } from './provider'; + +export class HostedZoneContextProviderPlugin implements ContextProviderPlugin { + + constructor(private readonly aws: SDK) { + } + + public async getValue(args: {[key: string]: any}): Promise { + const account = args.account; + const region = args.region; + if (!this.isHostedZoneQuery(args)) { + throw new Error(`HostedZoneProvider requires domainName property to be set in ${args}`); + } + const domainName = args.domainName; + debug(`Reading hosted zone ${account}:${region}:${domainName}`); + const r53 = await this.aws.route53(account, region, Mode.ForReading); + const response = await r53.listHostedZonesByName({ DNSName: domainName }).promise(); + if (!response.HostedZones) { + throw new Error(`Hosted Zone not found in account ${account}, region ${region}: ${domainName}`); + } + const candidateZones = await this.filterZones(r53, response.HostedZones, args); + if (candidateZones.length !== 1) { + const filteProps = `dns:${domainName}, privateZone:${args.privateZone}, vpcId:${args.vpcId}`; + throw new Error(`Found zones: ${JSON.stringify(candidateZones)} for ${filteProps}, but wanted exactly 1 zone`); + } + + return { + Id: candidateZones[0].Id, + Name: candidateZones[0].Name, + }; + } + + private async filterZones( + r53: AWS.Route53, zones: AWS.Route53.HostedZone[], + props: cxapi.HostedZoneContextQuery): Promise { + + let candidates: AWS.Route53.HostedZone[] = []; + const domainName = props.domainName.endsWith('.') ? props.domainName : `${props.domainName}.`; + debug(`Found the following zones ${JSON.stringify(zones)}`); + candidates = zones.filter( zone => zone.Name === domainName); + debug(`Found the following matched name zones ${JSON.stringify(candidates)}`); + if (props.privateZone) { + candidates = candidates.filter(zone => zone.Config && zone.Config.PrivateZone); + } else { + candidates = candidates.filter(zone => !zone.Config || !zone.Config.PrivateZone); + } + if (props.vpcId) { + const vpcZones: AWS.Route53.HostedZone[] = []; + for (const zone of candidates) { + const data = await r53.getHostedZone({ Id: zone. Id }).promise(); + if (!data.VPCs) { + debug(`Expected VPC for private zone but no VPC found ${zone.Id}`); + continue; + } + if (data.VPCs.map(vpc => vpc.VPCId).includes(props.vpcId)) { + vpcZones.push(zone); + } + } + return vpcZones; + } + return candidates; + } + + private isHostedZoneQuery(props: cxapi.HostedZoneContextQuery | any): props is cxapi.HostedZoneContextQuery { + return (props as cxapi.HostedZoneContextQuery).domainName !== undefined; + } +} \ No newline at end of file diff --git a/packages/aws-cdk/lib/context-providers/index.ts b/packages/aws-cdk/lib/context-providers/index.ts new file mode 100644 index 0000000000000..7d73feddb88fe --- /dev/null +++ b/packages/aws-cdk/lib/context-providers/index.ts @@ -0,0 +1,43 @@ +import cxapi = require('@aws-cdk/cx-api'); +import { SDK } from '../api/util/sdk'; +import { debug } from '../logging'; +import { Settings } from '../settings'; +import { AZContextProviderPlugin } from './availability-zones'; +import { HostedZoneContextProviderPlugin } from './hosted-zones'; +import { ContextProviderPlugin } from './provider'; +import { SSMContextProviderPlugin } from './ssm-parameters'; +import { VpcNetworkContextProviderPlugin } from './vpcs'; + +type ProviderConstructor = (new (sdk: SDK) => ContextProviderPlugin); +export type ProviderMap = {[name: string]: ProviderConstructor}; + +/** + * Iterate over the list of missing context values and invoke the appropriate providers from the map to retrieve them + */ +export async function provideContextValues( + missingValues: { [key: string]: cxapi.MissingContext }, + projectConfig: Settings, + sdk: SDK) { + for (const key of Object.keys(missingValues)) { + const missingContext = missingValues[key]; + + const constructor = availableContextProviders[missingContext.provider]; + if (!constructor) { + // tslint:disable-next-line:max-line-length + throw new Error(`Unrecognized context provider name: ${missingContext.provider}. You might need to update the toolkit to match the version of the construct library.`); + } + + const provider = new constructor(sdk); + + const value = await provider.getValue(missingContext.props); + projectConfig.set(['context', key], value); + debug(`Setting "${key}" context to ${JSON.stringify(value)}`); + } +} + +const availableContextProviders: ProviderMap = { + [cxapi.AVAILABILITY_ZONE_PROVIDER]: AZContextProviderPlugin, + [cxapi.SSM_PARAMETER_PROVIDER]: SSMContextProviderPlugin, + [cxapi.HOSTED_ZONE_PROVIDER]: HostedZoneContextProviderPlugin, + [cxapi.VPC_PROVIDER]: VpcNetworkContextProviderPlugin, +}; diff --git a/packages/aws-cdk/lib/context-providers/provider.ts b/packages/aws-cdk/lib/context-providers/provider.ts new file mode 100644 index 0000000000000..3d8a59938e9ef --- /dev/null +++ b/packages/aws-cdk/lib/context-providers/provider.ts @@ -0,0 +1,3 @@ +export interface ContextProviderPlugin { + getValue(args: {[key: string]: any}): Promise; +} diff --git a/packages/aws-cdk/lib/context-providers/ssm-parameters.ts b/packages/aws-cdk/lib/context-providers/ssm-parameters.ts new file mode 100644 index 0000000000000..be561fcef3415 --- /dev/null +++ b/packages/aws-cdk/lib/context-providers/ssm-parameters.ts @@ -0,0 +1,28 @@ +import { Mode, SDK } from '../api'; +import { debug } from '../logging'; +import { ContextProviderPlugin } from './provider'; + +/** + * Plugin to read arbitrary SSM parameter names + */ +export class SSMContextProviderPlugin implements ContextProviderPlugin { + constructor(private readonly aws: SDK) { + } + + public async getValue(args: {[key: string]: any}) { + const region = args.region; + const account = args.account; + if (!('parameterName' in args)) { + throw new Error('parameterName must be provided in props for SSMContextProviderPlugin'); + } + const parameterName = args.parameterName; + debug(`Reading SSM parameter ${account}:${region}:${parameterName}`); + + const ssm = await this.aws.ssm(account, region, Mode.ForReading); + const response = await ssm.getParameter({ Name: parameterName }).promise(); + if (!response.Parameter || response.Parameter.Value === undefined) { + throw new Error(`SSM parameter not available in account ${account}, region ${region}: ${parameterName}`); + } + return response.Parameter.Value; + } +} diff --git a/packages/aws-cdk/lib/context-providers/vpcs.ts b/packages/aws-cdk/lib/context-providers/vpcs.ts new file mode 100644 index 0000000000000..442b55d577a7e --- /dev/null +++ b/packages/aws-cdk/lib/context-providers/vpcs.ts @@ -0,0 +1,180 @@ +import cxapi = require('@aws-cdk/cx-api'); +import AWS = require('aws-sdk'); +import { Mode, SDK } from '../api'; +import { debug } from '../logging'; +import { ContextProviderPlugin } from './provider'; + +export class VpcNetworkContextProviderPlugin implements ContextProviderPlugin { + + constructor(private readonly aws: SDK) { + } + + public async getValue(args: cxapi.VpcContextQuery) { + const account: string = args.account!; + const region: string = args.region!; + + const ec2 = await this.aws.ec2(account, region, Mode.ForReading); + + const vpcId = await this.findVpc(ec2, args); + + return await this.readVpcProps(ec2, vpcId); + } + + private async findVpc(ec2: AWS.EC2, args: cxapi.VpcContextQuery): Promise { + // Build request filter (map { Name -> Value } to list of [{ Name, Values }]) + const filters: AWS.EC2.Filter[] = Object.entries(args.filter).map(x => ({ Name: x[0], Values: [x[1]] })); + + debug(`Listing VPCs in ${args.account}:${args.region}`); + const response = await ec2.describeVpcs({ Filters: filters }).promise(); + + const vpcs = response.Vpcs || []; + if (vpcs.length === 0) { + throw new Error(`Could not find any VPCs matching ${JSON.stringify(args)}`); + } + if (vpcs.length > 1) { + throw new Error(`Found ${vpcs.length} VPCs matching ${JSON.stringify(args)}; please narrow the search criteria`); + } + + return vpcs[0].VpcId!; + } + + private async readVpcProps(ec2: AWS.EC2, vpcId: string): Promise { + debug(`Describing VPC ${vpcId}`); + + const response = await ec2.describeSubnets({ Filters: [{ Name: 'vpc-id', Values: [vpcId] }] }).promise(); + const listedSubnets = response.Subnets || []; + + // Now comes our job to separate these subnets out into AZs and subnet groups (Public, Private, Isolated) + // We have the following attributes to go on: + // - Type tag, we tag subnets with their type. In absence of this tag, we + // fall back to MapPublicIpOnLaunch => must be a Public subnet, anything + // else is considered Priate. + // - Name tag, we tag subnets with their subnet group name. In absence of this tag, + // we use the type as the name. + + const azs = Array.from(new Set(listedSubnets.map(s => s.AvailabilityZone!))); + azs.sort(); + + const subnets: Subnet[] = listedSubnets.map(subnet => { + let type = getTag('aws-cdk:subnet-type', subnet.Tags); + if (type === undefined) { + type = subnet.MapPublicIpOnLaunch ? 'Public' : 'Private'; + } + + const name = getTag('aws-cdk:subnet-name', subnet.Tags) || type; + + return { az: subnet.AvailabilityZone!, type: type as SubnetType, name, subnetId: subnet.SubnetId! }; + }); + + const grouped = groupSubnets(subnets); + + return { + vpcId, + availabilityZones: grouped.azs, + isolatedSubnetIds: collapse(flatMap(findGroups(SubnetType.Isolated, grouped), group => group.subnets.map(s => s.subnetId))), + isolatedSubnetNames: collapse(flatMap(findGroups(SubnetType.Isolated, grouped), group => group.name ? [group.name] : [])), + privateSubnetIds: collapse(flatMap(findGroups(SubnetType.Private, grouped), group => group.subnets.map(s => s.subnetId))), + privateSubnetNames: collapse(flatMap(findGroups(SubnetType.Private, grouped), group => group.name ? [group.name] : [])), + publicSubnetIds: collapse(flatMap(findGroups(SubnetType.Public, grouped), group => group.subnets.map(s => s.subnetId))), + publicSubnetNames: collapse(flatMap(findGroups(SubnetType.Public, grouped), group => group.name ? [group.name] : [])), + }; + } +} + +/** + * Return the value of a tag from a set of tags + */ +function getTag(name: string, tags?: AWS.EC2.Tag[]): string | undefined { + for (const tag of tags || []) { + if (tag.Key === name) { + return tag.Value; + } + } + return undefined; +} + +/** + * Group subnets of the same type together, and order by AZ + */ +function groupSubnets(subnets: Subnet[]): SubnetGroups { + const grouping: {[key: string]: Subnet[]} = {}; + for (const subnet of subnets) { + const key = [subnet.type, subnet.name].toString(); + if (!(key in grouping)) { grouping[key] = []; } + grouping[key].push(subnet); + } + + const groups = Object.values(grouping).map(sns => { + sns.sort((a: Subnet, b: Subnet) => a.az.localeCompare(b.az)); + return { + type: sns[0].type, + name: sns[0].name, + subnets: sns, + }; + }); + + const azs = groups[0].subnets.map(s => s.az); + + for (const group of groups) { + const groupAZs = group.subnets.map(s => s.az); + if (!arraysEqual(groupAZs, azs)) { + throw new Error(`Not all subnets in VPC have the same AZs: ${groupAZs} vs ${azs}`); + } + } + + return { azs, groups }; +} + +enum SubnetType { + Public = 'Public', + Private = 'Private', + Isolated = 'Isolated' +} + +interface Subnet { + az: string; + type: SubnetType; + name?: string; + subnetId: string; +} + +interface SubnetGroup { + type: SubnetType; + name?: string; + subnets: Subnet[]; +} + +interface SubnetGroups { + azs: string[]; + groups: SubnetGroup[]; +} + +function arraysEqual(as: string[], bs: string[]): boolean { + if (as.length !== bs.length) { return false; } + + for (let i = 0; i < as.length; i++) { + if (as[i] !== bs[i]) { + return false; + } + } + + return true; +} + +function findGroups(type: SubnetType, groups: SubnetGroups): SubnetGroup[] { + return groups.groups.filter(g => g.type === type); +} + +function flatMap(xs: T[], fn: (x: T) => U[]): U[] { + const ret = new Array(); + for (const x of xs) { + ret.push(...fn(x)); + } + return ret; +} + +function collapse(xs: T[]): T[] | undefined { + if (xs.length > 0) { return xs; } + return undefined; + +} diff --git a/packages/aws-cdk/lib/contextplugins.ts b/packages/aws-cdk/lib/contextplugins.ts deleted file mode 100644 index 8af2987a3d214..0000000000000 --- a/packages/aws-cdk/lib/contextplugins.ts +++ /dev/null @@ -1,156 +0,0 @@ -import cxapi = require('@aws-cdk/cx-api'); -import { Mode, SDK } from './api'; -import { debug } from './logging'; -import { Settings } from './settings'; - -export interface ContextProviderPlugin { - getValue(args: {[key: string]: any}): Promise; -} - -export type ProviderMap = {[name: string]: ContextProviderPlugin}; - -/** - * Plugin to retrieve the Availability Zones for the current account - */ -export class AZContextProviderPlugin implements ContextProviderPlugin { - constructor(private readonly aws: SDK) { - } - - public async getValue(args: {[key: string]: any}) { - const region = args.region; - const account = args.account; - debug(`Reading AZs for ${account}:${region}`); - const ec2 = await this.aws.ec2(account, region, Mode.ForReading); - const response = await ec2.describeAvailabilityZones().promise(); - if (!response.AvailabilityZones) { return []; } - const azs = response.AvailabilityZones.filter(zone => zone.State === 'available').map(zone => zone.ZoneName); - return azs; - } -} - -/** - * Plugin to read arbitrary SSM parameter names - */ -export class SSMContextProviderPlugin implements ContextProviderPlugin { - constructor(private readonly aws: SDK) { - } - - public async getValue(args: {[key: string]: any}) { - const region = args.region; - const account = args.account; - if (!('parameterName' in args)) { - throw new Error('parameterName must be provided in props for SSMContextProviderPlugin'); - } - const parameterName = args.parameterName; - debug(`Reading SSM parameter ${account}:${region}:${parameterName}`); - - const ssm = await this.aws.ssm(account, region, Mode.ForReading); - const response = await ssm.getParameter({ Name: parameterName }).promise(); - if (!response.Parameter || response.Parameter.Value === undefined) { - throw new Error(`SSM parameter not available in account ${account}, region ${region}: ${parameterName}`); - } - return response.Parameter.Value; - } -} - -export interface HostedZoneProviderProps { - /** - * The domain name e.g. example.com to lookup - */ - domainName: string; - - /** - * True if the zone you want to find is a private hosted zone - */ - privateZone?: boolean; - - /** - * The VPC ID to that the private zone must be associated with - * - * If you provide VPC ID and privateZone is false, this will return no results - * and raise an error. - */ - vpcId?: string; -} - -export class HostedZoneContextProviderPlugin implements ContextProviderPlugin { - - constructor(private readonly aws: SDK) { - } - - public async getValue(args: {[key: string]: any}) { - const account = args.account; - const region = args.region; - if (!this.isHostedZoneProps(args)) { - throw new Error(`HostedZoneProvider requires domainName property to be set in ${args.props}`); - } - const domainName = args.domainName; - debug(`Reading hosted zone ${account}:${region}:${domainName}`); - const r53 = await this.aws.route53(account, region, Mode.ForReading); - const response = await r53.listHostedZonesByName({ DNSName: domainName }).promise(); - if (!response.HostedZones) { - throw new Error(`Hosted Zone not found in account ${account}, region ${region}: ${domainName}`); - } - const candidateZones = await this.filterZones(r53, response.HostedZones, args); - if (candidateZones.length !== 1) { - const filteProps = `dns:${domainName}, privateZone:${args.privateZone}, vpcId:${args.vpcId}`; - throw new Error(`Found zones: ${JSON.stringify(candidateZones)} for ${filteProps}, but wanted exactly 1 zone`); - } - return candidateZones[0]; - } - - private async filterZones( - r53: AWS.Route53, zones: AWS.Route53.HostedZone[], - props: HostedZoneProviderProps): Promise { - - let candidates: AWS.Route53.HostedZone[] = []; - const domainName = props.domainName.endsWith('.') ? props.domainName : `${props.domainName}.`; - debug(`Found the following zones ${JSON.stringify(zones)}`); - candidates = zones.filter( zone => zone.Name === domainName); - debug(`Found the following matched name zones ${JSON.stringify(candidates)}`); - if (props.privateZone) { - candidates = candidates.filter(zone => zone.Config && zone.Config.PrivateZone); - } else { - candidates = candidates.filter(zone => !zone.Config || !zone.Config.PrivateZone); - } - if (props.vpcId) { - const vpcZones: AWS.Route53.HostedZone[] = []; - for (const zone of candidates) { - const data = await r53.getHostedZone({ Id: zone. Id }).promise(); - if (!data.VPCs) { - debug(`Expected VPC for private zone but no VPC found ${zone.Id}`); - continue; - } - if (data.VPCs.map(vpc => vpc.VPCId).includes(props.vpcId)) { - vpcZones.push(zone); - } - } - return vpcZones; - } - return candidates; - } - - private isHostedZoneProps(props: HostedZoneProviderProps | any): props is HostedZoneProviderProps { - return (props as HostedZoneProviderProps).domainName !== undefined; - } -} -/** - * Iterate over the list of missing context values and invoke the appropriate providers from the map to retrieve them - */ -export async function provideContextValues( - missingValues: { [key: string]: cxapi.MissingContext }, - projectConfig: Settings, - availableContextProviders: ProviderMap) { - for (const key of Object.keys(missingValues)) { - const missingContext = missingValues[key]; - - const provider = availableContextProviders[missingContext.provider]; - if (!provider) { - throw new Error(`Unrecognized context provider name: ${missingContext.provider}`); - } - - const value = await provider.getValue(missingContext.props); - projectConfig.set(['context', key], value); - debug(`Setting "${key}" context to ${JSON.stringify(value)}`); - } -} diff --git a/tools/cdk-integ-tools/lib/integ-helpers.ts b/tools/cdk-integ-tools/lib/integ-helpers.ts index 60936c394a5c6..cb20c87655d0d 100644 --- a/tools/cdk-integ-tools/lib/integ-helpers.ts +++ b/tools/cdk-integ-tools/lib/integ-helpers.ts @@ -115,6 +115,12 @@ export const STATIC_TEST_CONTEXT = { "ssm:account=12345678:parameterName=/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2:region=test-region": "ami-1234", "ssm:account=12345678:parameterName=/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2:region=test-region": "ami-1234", "ssm:account=12345678:parameterName=/aws/service/ecs/optimized-ami/amazon-linux/recommended:region=test-region": "{\"image_id\": \"ami-1234\"}", + "vpc-provider:account=12345678:filter.isDefault=true:region=test-region": { + vpcId: "vpc-60900905", + availabilityZones: [ "us-east-1a", "us-east-1b", "us-east-1c" ], + publicSubnetIds: [ "subnet-e19455ca", "subnet-e0c24797", "subnet-ccd77395", ], + publicSubnetNames: [ "Public" ] + } }; /**