From 58dffd86f49ced2465fe2d044602a79f173a37a4 Mon Sep 17 00:00:00 2001 From: Philip White Date: Mon, 6 Jun 2022 09:46:51 -0700 Subject: [PATCH] feat(aws-ec2): control over VPC AZs (#20562) With this change, the `Vpc` construct gains a new constructor prop, `availabilityZones`, which gives more control over AZs than the existing `maxAzs` prop. closes #5847 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-ec2/README.md | 4 +- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 33 +- .../@aws-cdk/aws-ec2/test/integ.vpc-azs.ts | 11 + .../aws-cdk-ec2-vpc-azs.assets.json | 19 + .../aws-cdk-ec2-vpc-azs.template.json | 214 ++++++++++ .../test/vpc-azs.integ.snapshot/cdk.out | 1 + .../test/vpc-azs.integ.snapshot/integ.json | 14 + .../test/vpc-azs.integ.snapshot/manifest.json | 100 +++++ .../test/vpc-azs.integ.snapshot/tree.json | 388 ++++++++++++++++++ packages/@aws-cdk/aws-ec2/test/vpc.test.ts | 32 ++ 10 files changed, 812 insertions(+), 4 deletions(-) create mode 100644 packages/@aws-cdk/aws-ec2/test/integ.vpc-azs.ts create mode 100644 packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/aws-cdk-ec2-vpc-azs.assets.json create mode 100644 packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/aws-cdk-ec2-vpc-azs.template.json create mode 100644 packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/integ.json create mode 100644 packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/tree.json diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 531904ad2ede6..9c4df86cd8bf4 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -230,7 +230,9 @@ const vpc = new ec2.Vpc(this, 'TheVPC', { // The IP space will be divided over the configured subnets. cidr: '10.0.0.0/21', - // 'maxAzs' configures the maximum number of availability zones to use + // 'maxAzs' configures the maximum number of availability zones to use. + // If you want to specify the exact availability zones you want the VPC + // to use, use `availabilityZones` instead. maxAzs: 3, // 'subnetConfiguration' specifies the "subnet groups" to create. diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index d566f545d20dc..846ce2883cdaf 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -841,10 +841,21 @@ export interface VpcProps { * only 2 AZs, so to use more than 2 AZs, be sure to specify the account and * region on your stack. * + * Specify this option only if you do not specify `availabilityZones`. + * * @default 3 */ readonly maxAzs?: number; + /** + * Availability zones this VPC spans. + * + * Specify this option only if you do not specify `maxAzs`. + * + * @default - a subset of AZs of the stack + */ + readonly availabilityZones?: string[]; + /** * The number of NAT Gateways/Instances to create. * @@ -1289,6 +1300,10 @@ export class Vpc extends VpcBase { throw new Error('To use DNS Hostnames, DNS Support must be enabled, however, it was explicitly disabled.'); } + if (props.availabilityZones && props.maxAzs) { + throw new Error('Vpc supports \'availabilityZones\' or \'maxAzs\', but not both.'); + } + const cidrBlock = ifUndefined(props.cidr, Vpc.DEFAULT_CIDR_RANGE); if (Token.isUnresolved(cidrBlock)) { throw new Error('\'cidr\' property must be a concrete CIDR string, got a Token (we need to parse it for automatic subdivision)'); @@ -1317,10 +1332,22 @@ export class Vpc extends VpcBase { Tags.of(this).add(NAME_TAG, props.vpcName || this.node.path); - this.availabilityZones = stack.availabilityZones; + if (props.availabilityZones) { + // If given AZs and stack AZs are both resolved, then validate their compatibility. + const resolvedStackAzs = stack.availabilityZones.filter(az => !Token.isUnresolved(az)); + const areGivenAzsSubsetOfStack = resolvedStackAzs.length === 0 // stack AZs are tokenized, so we cannot validate it + || props.availabilityZones.every( + az => Token.isUnresolved(az) // given AZ is tokenized, such as in integ tests, so we cannot validate it + || resolvedStackAzs.includes(az)); + if (!areGivenAzsSubsetOfStack) { + throw new Error(`Given VPC 'availabilityZones' ${props.availabilityZones} must be a subset of the stack's availability zones ${stack.availabilityZones}`); + } + this.availabilityZones = props.availabilityZones; + } else { + const maxAZs = props.maxAzs ?? 3; + this.availabilityZones = stack.availabilityZones.slice(0, maxAZs); + } - const maxAZs = props.maxAzs ?? 3; - this.availabilityZones = this.availabilityZones.slice(0, maxAZs); this.vpcId = this.resource.ref; this.vpcArn = Arn.format({ diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpc-azs.ts b/packages/@aws-cdk/aws-ec2/test/integ.vpc-azs.ts new file mode 100644 index 0000000000000..434a50b143f16 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpc-azs.ts @@ -0,0 +1,11 @@ +import * as cdk from '@aws-cdk/core'; +import * as ec2 from '../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-cdk-ec2-vpc-azs'); + +new ec2.Vpc(stack, 'MyVpc', { + availabilityZones: [stack.availabilityZones[1]], +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/aws-cdk-ec2-vpc-azs.assets.json b/packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/aws-cdk-ec2-vpc-azs.assets.json new file mode 100644 index 0000000000000..9f45fad4995bc --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/aws-cdk-ec2-vpc-azs.assets.json @@ -0,0 +1,19 @@ +{ + "version": "20.0.0", + "files": { + "77e3ec18ea13d0b9b35f46a46f01b621e0df5c75fdad25fb81a3ffef3a6f1a17": { + "source": { + "path": "aws-cdk-ec2-vpc-azs.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "77e3ec18ea13d0b9b35f46a46f01b621e0df5c75fdad25fb81a3ffef3a6f1a17.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/aws-cdk-ec2-vpc-azs.template.json b/packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/aws-cdk-ec2-vpc-azs.template.json new file mode 100644 index 0000000000000..9b8b1b13c92d7 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/aws-cdk-ec2-vpc-azs.template.json @@ -0,0 +1,214 @@ +{ + "Resources": { + "MyVpcF9F0CA6F": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-azs/MyVpc" + } + ] + } + }, + "MyVpcPublicSubnet1SubnetF6608456": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.0.0/17", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet1RouteTableC46AB2F4": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet1RouteTableAssociation2ECEE1CB": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + } + } + }, + "MyVpcPublicSubnet1DefaultRoute95FDF9EB": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + }, + "DependsOn": [ + "MyVpcVPCGW488ACE0D" + ] + }, + "MyVpcPublicSubnet1EIP096967CB": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet1NATGatewayAD3400C1": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + }, + "AllocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet1EIP096967CB", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPrivateSubnet1Subnet5057CF7E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.128.0/17", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-azs/MyVpc/PrivateSubnet1" + } + ] + } + }, + "MyVpcPrivateSubnet1RouteTable8819E6E2": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-azs/MyVpc/PrivateSubnet1" + } + ] + } + }, + "MyVpcPrivateSubnet1RouteTableAssociation56D38C7E": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + "SubnetId": { + "Ref": "MyVpcPrivateSubnet1Subnet5057CF7E" + } + } + }, + "MyVpcPrivateSubnet1DefaultRouteA8CDE2FA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "MyVpcPublicSubnet1NATGatewayAD3400C1" + } + } + }, + "MyVpcIGW5C4A4F63": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-azs/MyVpc" + } + ] + } + }, + "MyVpcVPCGW488ACE0D": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "InternetGatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..588d7b269d34f --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/integ.json b/packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/integ.json new file mode 100644 index 0000000000000..005486355f297 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/integ.json @@ -0,0 +1,14 @@ +{ + "version": "20.0.0", + "testCases": { + "integ.vpc-azs": { + "stacks": [ + "aws-cdk-ec2-vpc-azs" + ], + "diffAssets": false, + "stackUpdateWorkflow": true + } + }, + "synthContext": {}, + "enableLookups": false +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..c83b84036cf6a --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/manifest.json @@ -0,0 +1,100 @@ +{ + "version": "20.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "aws-cdk-ec2-vpc-azs": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-cdk-ec2-vpc-azs.template.json", + "validateOnSynth": false + }, + "metadata": { + "/aws-cdk-ec2-vpc-azs/MyVpc/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcF9F0CA6F" + } + ], + "/aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPublicSubnet1SubnetF6608456" + } + ], + "/aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPublicSubnet1RouteTableC46AB2F4" + } + ], + "/aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPublicSubnet1RouteTableAssociation2ECEE1CB" + } + ], + "/aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPublicSubnet1DefaultRoute95FDF9EB" + } + ], + "/aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1/EIP": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPublicSubnet1EIP096967CB" + } + ], + "/aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1/NATGateway": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPublicSubnet1NATGatewayAD3400C1" + } + ], + "/aws-cdk-ec2-vpc-azs/MyVpc/PrivateSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPrivateSubnet1Subnet5057CF7E" + } + ], + "/aws-cdk-ec2-vpc-azs/MyVpc/PrivateSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPrivateSubnet1RouteTable8819E6E2" + } + ], + "/aws-cdk-ec2-vpc-azs/MyVpc/PrivateSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPrivateSubnet1RouteTableAssociation56D38C7E" + } + ], + "/aws-cdk-ec2-vpc-azs/MyVpc/PrivateSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcPrivateSubnet1DefaultRouteA8CDE2FA" + } + ], + "/aws-cdk-ec2-vpc-azs/MyVpc/IGW": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcIGW5C4A4F63" + } + ], + "/aws-cdk-ec2-vpc-azs/MyVpc/VPCGW": [ + { + "type": "aws:cdk:logicalId", + "data": "MyVpcVPCGW488ACE0D" + } + ] + }, + "displayName": "aws-cdk-ec2-vpc-azs" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/tree.json b/packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/tree.json new file mode 100644 index 0000000000000..c673ae28ddc2d --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/vpc-azs.integ.snapshot/tree.json @@ -0,0 +1,388 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.0.9" + } + }, + "aws-cdk-ec2-vpc-azs": { + "id": "aws-cdk-ec2-vpc-azs", + "path": "aws-cdk-ec2-vpc-azs", + "children": { + "MyVpc": { + "id": "MyVpc", + "path": "aws-cdk-ec2-vpc-azs/MyVpc", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-ec2-vpc-azs/MyVpc/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPC", + "aws:cdk:cloudformation:props": { + "cidrBlock": "10.0.0.0/16", + "enableDnsHostnames": true, + "enableDnsSupport": true, + "instanceTenancy": "default", + "tags": [ + { + "key": "Name", + "value": "aws-cdk-ec2-vpc-azs/MyVpc" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnVPC", + "version": "0.0.0" + } + }, + "PublicSubnet1": { + "id": "PublicSubnet1", + "path": "aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "availabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.0.0/17", + "mapPublicIpOnLaunch": true, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Public" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Public" + }, + { + "key": "Name", + "value": "aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "tags": [ + { + "key": "Name", + "value": "aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" + }, + "subnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" + }, + "destinationCidrBlock": "0.0.0.0/0", + "gatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + }, + "EIP": { + "id": "EIP", + "path": "aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1/EIP", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::EIP", + "aws:cdk:cloudformation:props": { + "domain": "vpc", + "tags": [ + { + "key": "Name", + "value": "aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnEIP", + "version": "0.0.0" + } + }, + "NATGateway": { + "id": "NATGateway", + "path": "aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1/NATGateway", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::NatGateway", + "aws:cdk:cloudformation:props": { + "subnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + }, + "allocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet1EIP096967CB", + "AllocationId" + ] + }, + "tags": [ + { + "key": "Name", + "value": "aws-cdk-ec2-vpc-azs/MyVpc/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnNatGateway", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PublicSubnet", + "version": "0.0.0" + } + }, + "PrivateSubnet1": { + "id": "PrivateSubnet1", + "path": "aws-cdk-ec2-vpc-azs/MyVpc/PrivateSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "aws-cdk-ec2-vpc-azs/MyVpc/PrivateSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "availabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.128.0/17", + "mapPublicIpOnLaunch": false, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Private" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Private" + }, + { + "key": "Name", + "value": "aws-cdk-ec2-vpc-azs/MyVpc/PrivateSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "aws-cdk-ec2-vpc-azs/MyVpc/PrivateSubnet1/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "aws-cdk-ec2-vpc-azs/MyVpc/PrivateSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "tags": [ + { + "key": "Name", + "value": "aws-cdk-ec2-vpc-azs/MyVpc/PrivateSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "aws-cdk-ec2-vpc-azs/MyVpc/PrivateSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + "subnetId": { + "Ref": "MyVpcPrivateSubnet1Subnet5057CF7E" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "aws-cdk-ec2-vpc-azs/MyVpc/PrivateSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + "destinationCidrBlock": "0.0.0.0/0", + "natGatewayId": { + "Ref": "MyVpcPublicSubnet1NATGatewayAD3400C1" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PrivateSubnet", + "version": "0.0.0" + } + }, + "IGW": { + "id": "IGW", + "path": "aws-cdk-ec2-vpc-azs/MyVpc/IGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::InternetGateway", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "Name", + "value": "aws-cdk-ec2-vpc-azs/MyVpc" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnInternetGateway", + "version": "0.0.0" + } + }, + "VPCGW": { + "id": "VPCGW", + "path": "aws-cdk-ec2-vpc-azs/MyVpc/VPCGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPCGatewayAttachment", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "internetGatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnVPCGatewayAttachment", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.Vpc", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts index 1bc1379ebfdfe..0000bdb63cee1 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts @@ -588,6 +588,38 @@ describe('vpc', () => { }); }); + + test('throws error when both availabilityZones and maxAzs are set', () => { + const stack = getTestStack(); + expect(() => { + new Vpc(stack, 'VPC', { + availabilityZones: stack.availabilityZones, + maxAzs: 1, + }); + }).toThrow(/Vpc supports 'availabilityZones' or 'maxAzs', but not both./); + }); + + test('with availabilityZones set correctly', () => { + const stack = getTestStack(); + const specificAz = stack.availabilityZones[1]; // not the first item + new Vpc(stack, 'VPC', { + availabilityZones: [specificAz], + }); + Template.fromStack(stack).resourceCountIs('AWS::EC2::Subnet', 2); + Template.fromStack(stack).hasResourceProperties('AWS::EC2::Subnet', { + AvailabilityZone: specificAz, + }); + }); + + test('with availabilityZones set to zones different from stack', () => { + const stack = getTestStack(); + expect(() => { + new Vpc(stack, 'VPC', { + availabilityZones: [stack.availabilityZones[0] + 'invalid'], + }); + }).toThrow(/must be a subset of the stack/); + }); + test('with natGateway set to 1', () => { const stack = getTestStack(); new Vpc(stack, 'VPC', {