Skip to content

Commit e0885db

Browse files
authored
feat(ec2): subnet ipv4 cidr blocks on imported vpc (#23317)
When using `Vpc.fromVpcAttributes()` to import a VPC, it is possible to specify all subnet properties except for the IPv4 CIDR block. This means that any attempt to read the `ipv4CidrBlock` property of a subnet on such a VPC will throw with the message: > You cannot reference an imported Subnet's IPv4 CIDR if it was not supplied. Add the ipv4CidrBlock when importing using Subnet.fromSubnetAttributes() This PR extends the `VpcAttributes` interface to allow for subnet IPv4 CIDR blocks to be passed in alongside the IDs, names and route table IDs for each subnet group (public, private, isolated). I have added a unit test showing how the values passed into `Vpc.fromVpcAttributes()` end up in the correct positions across the `ImportedSubnet` instances. As far as I can tell, there are no existing integration tests for this type of import, leading me to assume an integration test is not needed for this PR either. After checking the current README in the `@aws-cdk/aws-ec2` module, I am of the impression that nothing needs to be added here. Let me know if you believe otherwise. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Construct Runtime Dependencies: * [ ] This PR adds new construct runtime dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-construct-runtime-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] 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*
1 parent 4e98c63 commit e0885db

File tree

4 files changed

+168
-9
lines changed

4 files changed

+168
-9
lines changed

packages/@aws-cdk/aws-ec2/README.md

+26
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,32 @@ const vpc = ec2.Vpc.fromVpcAttributes(this, 'VPC', {
527527
});
528528
```
529529

530+
For each subnet group the import function accepts optional parameters for subnet
531+
names, route table ids and IPv4 CIDR blocks. When supplied, the length of these
532+
lists are required to match the length of the list of subnet ids, allowing the
533+
lists to be zipped together to form `ISubnet` instances.
534+
535+
Public subnet group example (for private or isolated subnet groups, use the properties with the respective prefix):
536+
537+
```ts
538+
const vpc = ec2.Vpc.fromVpcAttributes(this, 'VPC', {
539+
vpcId: 'vpc-1234',
540+
availabilityZones: ['us-east-1a', 'us-east-1b', 'us-east-1c'],
541+
publicSubnetIds: ['s-12345', 's-34567', 's-56789'],
542+
publicSubnetNames: ['Subnet A', 'Subnet B', 'Subnet C'],
543+
publicSubnetRouteTableIds: ['rt-12345', 'rt-34567', 'rt-56789'],
544+
publicSubnetIpv4CidrBlocks: ['10.0.0.0/24', '10.0.1.0/24', '10.0.2.0/24'],
545+
});
546+
```
547+
548+
The above example will create an `IVpc` instance with three public subnets:
549+
550+
| Subnet id | Availability zone | Subnet name | Route table id | IPv4 CIDR |
551+
| --------- | ----------------- | ----------- | -------------- | ----------- |
552+
| s-12345 | us-east-1a | Subnet A | rt-12345 | 10.0.0.0/24 |
553+
| s-34567 | us-east-1b | Subnet B | rt-34567 | 10.0.1.0/24 |
554+
| s-56789 | us-east-1c | Subnet B | rt-56789 | 10.0.2.0/24 |
555+
530556
## Allowing Connections
531557

532558
In AWS, all network traffic in and out of **Elastic Network Interfaces** (ENIs)

packages/@aws-cdk/aws-ec2/lib/util.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,24 @@ export class ImportSubnetGroup {
4646
private readonly subnetIds: string[];
4747
private readonly names: string[];
4848
private readonly routeTableIds: string[];
49+
private readonly ipv4CidrBlocks: string[];
4950
private readonly groups: number;
5051

5152
constructor(
5253
subnetIds: string[] | undefined,
5354
names: string[] | undefined,
5455
routeTableIds: string[] | undefined,
56+
ipv4CidrBlocks: string[] | undefined,
5557
type: SubnetType,
5658
private readonly availabilityZones: string[],
5759
idField: string,
5860
nameField: string,
59-
routeTableIdField: string) {
61+
routeTableIdField: string,
62+
ipv4CidrBlockField: string) {
6063

6164
this.subnetIds = subnetIds || [];
6265
this.routeTableIds = routeTableIds || [];
66+
this.ipv4CidrBlocks = ipv4CidrBlocks || [];
6367
this.groups = this.subnetIds.length / this.availabilityZones.length;
6468

6569
if (Math.floor(this.groups) !== this.groups) {
@@ -71,6 +75,11 @@ export class ImportSubnetGroup {
7175
/* eslint-disable max-len */
7276
throw new Error(`Number of ${routeTableIdField} (${this.routeTableIds.length}) must be equal to the amount of ${idField} (${this.subnetIds.length}).`);
7377
}
78+
if (this.ipv4CidrBlocks.length !== this.subnetIds.length && ipv4CidrBlocks != null) {
79+
// We don't err if no ipv4CidrBlocks were provided to maintain backwards-compatibility.
80+
/* eslint-disable max-len */
81+
throw new Error(`Number of ${ipv4CidrBlockField} (${this.ipv4CidrBlocks.length}) must be equal to the amount of ${idField} (${this.subnetIds.length}).`);
82+
}
7483

7584
this.names = this.normalizeNames(names, defaultSubnetName(type), nameField);
7685
}
@@ -82,6 +91,7 @@ export class ImportSubnetGroup {
8291
availabilityZone: this.pickAZ(i),
8392
subnetId: this.subnetIds[i],
8493
routeTableId: this.routeTableIds[i],
94+
ipv4CidrBlock: this.ipv4CidrBlocks[i],
8595
});
8696
});
8797
}

packages/@aws-cdk/aws-ec2/lib/vpc.ts

+51-6
Original file line numberDiff line numberDiff line change
@@ -701,65 +701,110 @@ export interface VpcAttributes {
701701
* List of public subnet IDs
702702
*
703703
* Must be undefined or match the availability zones in length and order.
704+
*
705+
* @default - The VPC does not have any public subnets
704706
*/
705707
readonly publicSubnetIds?: string[];
706708

707709
/**
708710
* List of names for the public subnets
709711
*
710712
* Must be undefined or have a name for every public subnet group.
713+
*
714+
* @default - All public subnets will have the name `Public`
711715
*/
712716
readonly publicSubnetNames?: string[];
713717

714718
/**
715-
* List of IDs of routing tables for the public subnets.
719+
* List of IDs of route tables for the public subnets.
716720
*
717721
* Must be undefined or have a name for every public subnet group.
722+
*
723+
* @default - Retrieving the route table ID of any public subnet will fail
718724
*/
719725
readonly publicSubnetRouteTableIds?: string[];
720726

727+
/**
728+
* List of IPv4 CIDR blocks for the public subnets.
729+
*
730+
* Must be undefined or have an entry for every public subnet group.
731+
*
732+
* @default - Retrieving the IPv4 CIDR block of any public subnet will fail
733+
*/
734+
readonly publicSubnetIpv4CidrBlocks?: string[];
735+
721736
/**
722737
* List of private subnet IDs
723738
*
724739
* Must be undefined or match the availability zones in length and order.
740+
*
741+
* @default - The VPC does not have any private subnets
725742
*/
726743
readonly privateSubnetIds?: string[];
727744

728745
/**
729746
* List of names for the private subnets
730747
*
731748
* Must be undefined or have a name for every private subnet group.
749+
*
750+
* @default - All private subnets will have the name `Private`
732751
*/
733752
readonly privateSubnetNames?: string[];
734753

735754
/**
736-
* List of IDs of routing tables for the private subnets.
755+
* List of IDs of route tables for the private subnets.
737756
*
738757
* Must be undefined or have a name for every private subnet group.
758+
*
759+
* @default - Retrieving the route table ID of any private subnet will fail
739760
*/
740761
readonly privateSubnetRouteTableIds?: string[];
741762

763+
/**
764+
* List of IPv4 CIDR blocks for the private subnets.
765+
*
766+
* Must be undefined or have an entry for every private subnet group.
767+
*
768+
* @default - Retrieving the IPv4 CIDR block of any private subnet will fail
769+
*/
770+
readonly privateSubnetIpv4CidrBlocks?: string[];
771+
742772
/**
743773
* List of isolated subnet IDs
744774
*
745775
* Must be undefined or match the availability zones in length and order.
776+
*
777+
* @default - The VPC does not have any isolated subnets
746778
*/
747779
readonly isolatedSubnetIds?: string[];
748780

749781
/**
750782
* List of names for the isolated subnets
751783
*
752784
* Must be undefined or have a name for every isolated subnet group.
785+
*
786+
* @default - All isolated subnets will have the name `Isolated`
753787
*/
754788
readonly isolatedSubnetNames?: string[];
755789

756790
/**
757-
* List of IDs of routing tables for the isolated subnets.
791+
* List of IDs of route tables for the isolated subnets.
758792
*
759793
* Must be undefined or have a name for every isolated subnet group.
794+
*
795+
* @default - Retrieving the route table ID of any isolated subnet will fail
760796
*/
761797
readonly isolatedSubnetRouteTableIds?: string[];
762798

799+
/**
800+
* List of IPv4 CIDR blocks for the isolated subnets.
801+
*
802+
* Must be undefined or have an entry for every isolated subnet group.
803+
*
804+
* @default - Retrieving the IPv4 CIDR block of any isolated subnet will fail
805+
*/
806+
readonly isolatedSubnetIpv4CidrBlocks?: string[];
807+
763808
/**
764809
* VPN gateway's identifier
765810
*/
@@ -2084,9 +2129,9 @@ class ImportedVpc extends VpcBase {
20842129
}
20852130

20862131
/* eslint-disable max-len */
2087-
const pub = new ImportSubnetGroup(props.publicSubnetIds, props.publicSubnetNames, props.publicSubnetRouteTableIds, SubnetType.PUBLIC, this.availabilityZones, 'publicSubnetIds', 'publicSubnetNames', 'publicSubnetRouteTableIds');
2088-
const priv = new ImportSubnetGroup(props.privateSubnetIds, props.privateSubnetNames, props.privateSubnetRouteTableIds, SubnetType.PRIVATE_WITH_EGRESS, this.availabilityZones, 'privateSubnetIds', 'privateSubnetNames', 'privateSubnetRouteTableIds');
2089-
const iso = new ImportSubnetGroup(props.isolatedSubnetIds, props.isolatedSubnetNames, props.isolatedSubnetRouteTableIds, SubnetType.PRIVATE_ISOLATED, this.availabilityZones, 'isolatedSubnetIds', 'isolatedSubnetNames', 'isolatedSubnetRouteTableIds');
2132+
const pub = new ImportSubnetGroup(props.publicSubnetIds, props.publicSubnetNames, props.publicSubnetRouteTableIds, props.publicSubnetIpv4CidrBlocks, SubnetType.PUBLIC, this.availabilityZones, 'publicSubnetIds', 'publicSubnetNames', 'publicSubnetRouteTableIds', 'publicSubnetIpv4CidrBlocks');
2133+
const priv = new ImportSubnetGroup(props.privateSubnetIds, props.privateSubnetNames, props.privateSubnetRouteTableIds, props.privateSubnetIpv4CidrBlocks, SubnetType.PRIVATE_WITH_EGRESS, this.availabilityZones, 'privateSubnetIds', 'privateSubnetNames', 'privateSubnetRouteTableIds', 'privateSubnetIpv4CidrBlocks');
2134+
const iso = new ImportSubnetGroup(props.isolatedSubnetIds, props.isolatedSubnetNames, props.isolatedSubnetRouteTableIds, props.isolatedSubnetIpv4CidrBlocks, SubnetType.PRIVATE_ISOLATED, this.availabilityZones, 'isolatedSubnetIds', 'isolatedSubnetNames', 'isolatedSubnetRouteTableIds', 'isolatedSubnetIpv4CidrBlocks');
20902135
/* eslint-enable max-len */
20912136

20922137
this.publicSubnets = pub.import(this);

packages/@aws-cdk/aws-ec2/test/vpc.test.ts

+80-2
Original file line numberDiff line numberDiff line change
@@ -1222,10 +1222,11 @@ describe('vpc', () => {
12221222
NetworkInterfaceId: 'router-1',
12231223
});
12241224

1225-
12261225
});
1226+
});
12271227

1228-
test('fromVpcAttributes passes region correctly', () => {
1228+
describe('fromVpcAttributes', () => {
1229+
test('passes region correctly', () => {
12291230
// GIVEN
12301231
const stack = getTestStack();
12311232

@@ -1241,6 +1242,83 @@ describe('vpc', () => {
12411242
// THEN
12421243
expect(vpc.env.region).toEqual('region-12345');
12431244
});
1245+
1246+
test('passes subnet IPv4 CIDR blocks correctly', () => {
1247+
// GIVEN
1248+
const stack = new Stack();
1249+
const vpc = Vpc.fromVpcAttributes(stack, 'VPC', {
1250+
vpcId: 'vpc-1234',
1251+
availabilityZones: ['dummy1a', 'dummy1b', 'dummy1c'],
1252+
publicSubnetIds: ['pub-1', 'pub-2', 'pub-3'],
1253+
publicSubnetIpv4CidrBlocks: ['10.0.0.0/18', '10.0.64.0/18', '10.0.128.0/18'],
1254+
privateSubnetIds: ['pri-1', 'pri-2', 'pri-3'],
1255+
privateSubnetIpv4CidrBlocks: ['10.10.0.0/18', '10.10.64.0/18', '10.10.128.0/18'],
1256+
isolatedSubnetIds: ['iso-1', 'iso-2', 'iso-3'],
1257+
isolatedSubnetIpv4CidrBlocks: ['10.20.0.0/18', '10.20.64.0/18', '10.20.128.0/18'],
1258+
});
1259+
1260+
// WHEN
1261+
const public1 = vpc.publicSubnets.find(({ subnetId }) => subnetId === 'pub-1');
1262+
const public2 = vpc.publicSubnets.find(({ subnetId }) => subnetId === 'pub-2');
1263+
const public3 = vpc.publicSubnets.find(({ subnetId }) => subnetId === 'pub-3');
1264+
const private1 = vpc.privateSubnets.find(({ subnetId }) => subnetId === 'pri-1');
1265+
const private2 = vpc.privateSubnets.find(({ subnetId }) => subnetId === 'pri-2');
1266+
const private3 = vpc.privateSubnets.find(({ subnetId }) => subnetId === 'pri-3');
1267+
const isolated1 = vpc.isolatedSubnets.find(({ subnetId }) => subnetId === 'iso-1');
1268+
const isolated2 = vpc.isolatedSubnets.find(({ subnetId }) => subnetId === 'iso-2');
1269+
const isolated3 = vpc.isolatedSubnets.find(({ subnetId }) => subnetId === 'iso-3');
1270+
1271+
// THEN
1272+
expect(public1?.ipv4CidrBlock).toEqual('10.0.0.0/18');
1273+
expect(public2?.ipv4CidrBlock).toEqual('10.0.64.0/18');
1274+
expect(public3?.ipv4CidrBlock).toEqual('10.0.128.0/18');
1275+
expect(private1?.ipv4CidrBlock).toEqual('10.10.0.0/18');
1276+
expect(private2?.ipv4CidrBlock).toEqual('10.10.64.0/18');
1277+
expect(private3?.ipv4CidrBlock).toEqual('10.10.128.0/18');
1278+
expect(isolated1?.ipv4CidrBlock).toEqual('10.20.0.0/18');
1279+
expect(isolated2?.ipv4CidrBlock).toEqual('10.20.64.0/18');
1280+
expect(isolated3?.ipv4CidrBlock).toEqual('10.20.128.0/18');
1281+
1282+
});
1283+
1284+
test('throws on incorrect number of subnet names', () => {
1285+
const stack = new Stack();
1286+
1287+
expect(() =>
1288+
Vpc.fromVpcAttributes(stack, 'VPC', {
1289+
vpcId: 'vpc-1234',
1290+
availabilityZones: ['us-east-1a', 'us-east-1b', 'us-east-1c'],
1291+
publicSubnetIds: ['s-12345', 's-34567', 's-56789'],
1292+
publicSubnetNames: ['Public 1', 'Public 2'],
1293+
}),
1294+
).toThrow(/publicSubnetNames must have an entry for every corresponding subnet group/);
1295+
});
1296+
1297+
test('throws on incorrect number of route table ids', () => {
1298+
const stack = new Stack();
1299+
1300+
expect(() =>
1301+
Vpc.fromVpcAttributes(stack, 'VPC', {
1302+
vpcId: 'vpc-1234',
1303+
availabilityZones: ['us-east-1a', 'us-east-1b', 'us-east-1c'],
1304+
publicSubnetIds: ['s-12345', 's-34567', 's-56789'],
1305+
publicSubnetRouteTableIds: ['rt-12345'],
1306+
}),
1307+
).toThrow('Number of publicSubnetRouteTableIds (1) must be equal to the amount of publicSubnetIds (3).');
1308+
});
1309+
1310+
test('throws on incorrect number of subnet IPv4 CIDR blocks', () => {
1311+
const stack = new Stack();
1312+
1313+
expect(() =>
1314+
Vpc.fromVpcAttributes(stack, 'VPC', {
1315+
vpcId: 'vpc-1234',
1316+
availabilityZones: ['us-east-1a', 'us-east-1b', 'us-east-1c'],
1317+
publicSubnetIds: ['s-12345', 's-34567', 's-56789'],
1318+
publicSubnetIpv4CidrBlocks: ['10.0.0.0/18', '10.0.64.0/18'],
1319+
}),
1320+
).toThrow('Number of publicSubnetIpv4CidrBlocks (2) must be equal to the amount of publicSubnetIds (3).');
1321+
});
12441322
});
12451323

12461324
describe('NAT instances', () => {

0 commit comments

Comments
 (0)