Repository navigation
feat(ec2): support IPAM CIDR allocation for Subnet #38810
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
bcc4bfc
7e91225
68d49ff
d6a6bcd
b7fce1b
6ee04ed
4cbdaa0
95b1b5e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| import * as cdk from 'aws-cdk-lib'; | ||
| import { ExpectedResult, IntegTest } from '@aws-cdk/integ-tests-alpha'; | ||
| import { CfnIPAM, CfnIPAMPool, IpAddresses, Subnet, Vpc } from 'aws-cdk-lib/aws-ec2'; | ||
| import { EC2_RESTRICT_DEFAULT_SECURITY_GROUP } from 'aws-cdk-lib/cx-api'; | ||
|
|
||
| /* | ||
| * Stack verification steps: | ||
| * * The subnet is created without a CidrBlock property; IPAM allocates its CIDR at deploy time | ||
| * * The assertion checks that the allocated CIDR is the first /24 of the pool's provisioned range | ||
| * | ||
| * ### MANUAL CLEAN UP REQUIRED ### | ||
| * | ||
| * As in integ.vpc-ipam.ts, the IPAM and the pool are retained after the test run and must be | ||
| * deleted manually. | ||
| */ | ||
|
|
||
| const app = new cdk.App(); | ||
| const stack = new cdk.Stack(app, 'aws-cdk-ec2-ipam-subnet'); | ||
| stack.node.setContext(EC2_RESTRICT_DEFAULT_SECURITY_GROUP, false); | ||
|
|
||
| const ipam = new CfnIPAM(stack, 'IPAM', { | ||
| operatingRegions: [ | ||
| { regionName: stack.region }, | ||
| ], | ||
| tags: [{ | ||
| key: 'stack', | ||
| value: stack.stackId, | ||
| }], | ||
| }); | ||
| ipam.applyRemovalPolicy(cdk.RemovalPolicy.RETAIN); | ||
|
|
||
| // A VPC with a concrete CIDR and no subnets of its own | ||
| const vpc = new Vpc(stack, 'Vpc', { | ||
| ipAddresses: IpAddresses.cidr('10.0.0.0/16'), | ||
| subnetConfiguration: [], | ||
| }); | ||
|
|
||
| // A pool that plans the VPC's address space for subnets: it provisions the VPC CIDR | ||
| const pool = new CfnIPAMPool(stack, 'Pool', { | ||
| description: 'Subnet pool for the VPC', | ||
| addressFamily: 'ipv4', | ||
| autoImport: false, | ||
| locale: stack.region, | ||
| ipamScopeId: ipam.attrPrivateDefaultScopeId, | ||
| provisionedCidrs: [{ | ||
| cidr: '10.0.0.0/16', | ||
| }], | ||
| }); | ||
| pool.applyRemovalPolicy(cdk.RemovalPolicy.RETAIN); | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For subnets, IPAM allocates from a resource planning pool: a pool whose const pool = new CfnIPAMPool(stack, 'Pool', {
description: 'Resource planning pool for the VPC',
addressFamily: 'ipv4',
autoImport: false,
locale: stack.region,
ipamScopeId: ipam.attrPrivateDefaultScopeId,
sourceResource: {
resourceId: vpc.vpcId,
resourceOwner: stack.account,
resourceRegion: stack.region,
resourceType: 'vpc',
},
provisionedCidrs: [{ cidr: '10.0.0.0/16' }],
});The provisioned CIDR has to match the VPC's CIDR, and
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done: the pool in the integ test is now a resource planning pool for the VPC ( |
||
| const subnet = new Subnet(stack, 'IpamSubnet', { | ||
| vpcId: vpc.vpcId, | ||
| availabilityZone: vpc.availabilityZones[0], | ||
| ipv4IpamAllocation: { | ||
| ipamPool: pool, | ||
| netmaskLength: 24, | ||
| }, | ||
| }); | ||
|
|
||
| const integ = new IntegTest(app, 'SubnetIpam', { | ||
| testCases: [stack], | ||
| allowDestroy: ['EC2::IPAM'], | ||
| }); | ||
|
|
||
| // The first allocation from a fresh pool is the lowest /24 of the provisioned range | ||
| integ.assertions.awsApiCall('EC2', 'describeSubnets', { | ||
| SubnetIds: [subnet.subnetId], | ||
| }).expect(ExpectedResult.objectLike({ | ||
| Subnets: [ | ||
| { | ||
| CidrBlock: '10.0.0.0/24', | ||
| }, | ||
| ], | ||
| })); | ||
|
Comment on lines
+1
to
+133
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Recommended — This integration test's source is added, but no committed Suggested change: Deploy and record the snapshot rather than granting a no-snapshot exemption — a maintainer can run
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please address this
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not able to run the real deployment for this one: the test needs an IPAM in the Advanced Tier, and I don't have an account where I can run that. Following CONTRIBUTING.md ("What if you cannot run integration tests"), could a maintainer run The IPAM and the pool are retained, so the cleanup is
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, that's right. The changes look good. I'll run the deploy and push the snapshot here.
Comment on lines
+123
to
+133
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚪ Optional — The deploy-time assertion checks only Suggested change: Keep the |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -413,6 +413,28 @@ new ec2.Vpc(this, 'TheVPC', { | |
|
|
||
| With this method of IP address management, no attempt is made to guess at subnet group sizes or to exhaustively allocate the IP range. All subnet groups must have an explicit `cidrMask` set as part of their subnet configuration, or `defaultSubnetIpv4NetmaskLength` must be set for a default size. If not, synthesis will fail and you must provide one or the other. | ||
|
|
||
| #### Allocating a subnet CIDR from AWS IPAM | ||
|
|
||
| A standalone `Subnet` (or `PublicSubnet`/`PrivateSubnet`) can also have its IPv4 CIDR block allocated from an IPAM pool at deploy time instead of taking a concrete `cidrBlock`. Pass the pool and the netmask length of the block to allocate in `ipv4IpamAllocation`; exactly one of `cidrBlock` and `ipv4IpamAllocation` must be set: | ||
|
|
||
| ```ts | ||
| declare const vpc: ec2.Vpc; | ||
| declare const pool: ec2.CfnIPAMPool; | ||
|
|
||
| const subnet = new ec2.Subnet(this, 'IpamSubnet', { | ||
| vpcId: vpc.vpcId, | ||
| availabilityZone: vpc.availabilityZones[0], | ||
| ipv4IpamAllocation: { | ||
| ipamPool: pool, | ||
| netmaskLength: 24, | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| The CIDR block allocated from the pool must lie within the CIDR of the VPC. To use a pool that is not defined in your CDK app (for example one shared with your account through AWS RAM), reference it by ID with `ec2.CfnIPAMPool.fromIpamPoolId(this, 'Pool', 'ipam-pool-0123456789abcdef0')`. If the pool's address space is provisioned through separate `CfnIPAMPoolCidr` resources, add a dependency from the subnet on them so the pool has space to allocate from when the subnet is created. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The CIDR block allocated from the pool must lie within the CIDR of the VPC" is true, but users will trip on the pool type. It has to be a resource planning pool for this VPC, and private-scope pools need the IPAM Advanced Tier. Showing the pool in the example makes it copyable: declare const vpc: ec2.Vpc;
declare const ipam: ec2.CfnIPAM;
const pool = new ec2.CfnIPAMPool(this, 'SubnetPool', {
addressFamily: 'ipv4',
ipamScopeId: ipam.attrPrivateDefaultScopeId,
locale: this.region,
sourceResource: {
resourceId: vpc.vpcId,
resourceOwner: this.account,
resourceRegion: this.region,
resourceType: 'vpc',
},
provisionedCidrs: [{ cidr: vpc.vpcCidrBlock }],
});
new ec2.Subnet(this, 'IpamSubnet', {
vpcId: vpc.vpcId,
availabilityZone: vpc.availabilityZones[0],
ipv4IpamAllocation: { ipamPool: pool, netmaskLength: 24 },
});The
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done: the README example now creates the resource planning pool from the VPC, and the text after it says the pool must be a resource planning pool for the subnet's VPC and that private-scope pools require the IPAM Advanced Tier. I added the same note to the |
||
|
|
||
| Because the CIDR block is only known at deploy time, `subnet.ipv4CidrBlock` is a CloudFormation attribute reference rather than a concrete string. Subnet filters that parse the CIDR (`SubnetFilter.byCidrMask()`, `SubnetFilter.byCidrRanges()` and `SubnetFilter.containsIpAddresses()`) therefore cannot be used with IPAM-allocated subnets. Subnets created by `Vpc` from `subnetConfiguration` are not affected by this option; they keep getting their CIDRs from the VPC's `IpAddresses` provider. | ||
|
|
||
| ### Dual Stack configuration | ||
|
|
||
| To allocate both IPv4 and IPv6 addresses in your VPC, you can configure your VPC to have a dual stack protocol. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ import type { ClientVpnEndpointOptions } from './client-vpn-endpoint'; | |
| import { ClientVpnEndpoint } from './client-vpn-endpoint'; | ||
| import type { | ||
| CfnVPCCidrBlock, | ||
| IIPAMPoolRef, | ||
| ISubnetRef, | ||
| IVPCRef, SubnetReference, VPCReference, | ||
| } from './ec2.generated'; | ||
|
|
@@ -2085,6 +2086,26 @@ function subnetTypeTagValue(type: SubnetType) { | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Allocation of a subnet's IPv4 CIDR block from an Amazon VPC IP Address Manager (IPAM) pool | ||
| */ | ||
| export interface SubnetIpamAllocation { | ||
| /** | ||
| * The IPAM pool from which the CIDR block is allocated at deploy time | ||
| * | ||
| * The allocated CIDR block must lie within the CIDR of the VPC the subnet belongs to. | ||
| * To reference a pool that is not defined in this app, use `CfnIPAMPool.fromIpamPoolId()`. | ||
| */ | ||
| readonly ipamPool: IIPAMPoolRef; | ||
|
|
||
| /** | ||
| * The netmask length of the CIDR block to allocate from the pool | ||
| * | ||
| * Must be between 16 and 28 (inclusive), the sizes allowed for an IPv4 subnet. | ||
| */ | ||
|
Comment on lines
+2103
to
+2107
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚪ Optional — Suggested change: If covering the pool-default case is desired, make it |
||
| readonly netmaskLength: number; | ||
| } | ||
|
|
||
| /** | ||
| * Specify configuration parameters for a VPC subnet | ||
| */ | ||
|
|
@@ -2102,8 +2123,25 @@ export interface SubnetProps { | |
|
|
||
| /** | ||
| * The CIDR notation for this subnet | ||
| * | ||
| * Exactly one of `cidrBlock` and `ipv4IpamAllocation` must be specified. | ||
| * | ||
| * @default - the CIDR block is allocated from the IPAM pool given in `ipv4IpamAllocation` | ||
| */ | ||
| readonly cidrBlock: string; | ||
| readonly cidrBlock?: string; | ||
|
|
||
| /** | ||
| * Allocate the IPv4 CIDR block of this subnet from an IPAM pool at deploy time | ||
| * | ||
| * When set, `ipv4CidrBlock` resolves to a deploy-time attribute of the subnet instead of a | ||
| * concrete string, so `SubnetFilter.byCidrMask()`, `SubnetFilter.byCidrRanges()` and | ||
| * `SubnetFilter.containsIpAddresses()` cannot be used with this subnet. | ||
| * | ||
| * Exactly one of `cidrBlock` and `ipv4IpamAllocation` must be specified. | ||
| * | ||
| * @default - the subnet uses the concrete `cidrBlock` | ||
| */ | ||
| readonly ipv4IpamAllocation?: SubnetIpamAllocation; | ||
|
|
||
| /** | ||
| * Controls if a public IP is associated to an instance at launch | ||
|
|
@@ -2166,6 +2204,11 @@ export class Subnet extends Resource implements ISubnet { | |
| public readonly availabilityZone: string; | ||
|
|
||
| /** | ||
| * The IPv4 CIDR block for this subnet | ||
| * | ||
| * If the subnet is allocated from an IPAM pool (`ipv4IpamAllocation`), this is a | ||
| * deploy-time attribute of the subnet rather than a concrete string. | ||
| * | ||
| * @attribute | ||
| */ | ||
| public readonly ipv4CidrBlock: string; | ||
|
|
@@ -2224,20 +2267,34 @@ export class Subnet extends Resource implements ISubnet { | |
| // Enhanced CDK Analytics Telemetry | ||
| addConstructMetadata(this, props); | ||
|
|
||
| if (props.cidrBlock !== undefined && props.ipv4IpamAllocation !== undefined) { | ||
| throw new ValidationError(lit`CannotSpecifyBothCidrBlockAndIpv4IpamAllocation`, 'Cannot specify both \'cidrBlock\' and \'ipv4IpamAllocation\'; supply exactly one of them', this); | ||
| } | ||
| if (props.cidrBlock === undefined && props.ipv4IpamAllocation === undefined) { | ||
| throw new ValidationError(lit`MissingCidrBlockOrIpv4IpamAllocation`, 'Either \'cidrBlock\' or \'ipv4IpamAllocation\' must be specified', this); | ||
| } | ||
| const netmaskLength = props.ipv4IpamAllocation?.netmaskLength; | ||
| if (netmaskLength !== undefined && !Token.isUnresolved(netmaskLength) && (netmaskLength < 16 || netmaskLength > 28)) { | ||
| throw new ValidationError(lit`InvalidIpv4IpamAllocationNetmaskLength`, `'ipv4IpamAllocation.netmaskLength' must be between 16 and 28, got ${netmaskLength}`, this); | ||
| } | ||
|
|
||
| Object.defineProperty(this, VPC_SUBNET_SYMBOL, { value: true }); | ||
|
|
||
| Tags.of(this).add(NAME_TAG, this.node.path); | ||
|
|
||
| this.availabilityZone = props.availabilityZone; | ||
| this.ipv4CidrBlock = props.cidrBlock; | ||
| const subnet = new CfnSubnet(this, 'Subnet', { | ||
| vpcId: props.vpcId, | ||
| cidrBlock: props.cidrBlock, | ||
| ipv4IpamPoolId: props.ipv4IpamAllocation?.ipamPool.ipamPoolRef.ipamPoolId, | ||
| ipv4NetmaskLength: netmaskLength, | ||
| availabilityZone: props.availabilityZone, | ||
| mapPublicIpOnLaunch: props.mapPublicIpOnLaunch, | ||
| ipv6CidrBlock: props.ipv6CidrBlock, | ||
| assignIpv6AddressOnCreation: props.assignIpv6AddressOnCreation, | ||
| }); | ||
| // With IPAM the CIDR block is only known at deploy time | ||
| this.ipv4CidrBlock = props.cidrBlock ?? subnet.attrCidrBlock; | ||
| this.subnetId = subnet.ref; | ||
| this.subnetVpcId = subnet.attrVpcId; | ||
| this.subnetAvailabilityZone = subnet.attrAvailabilityZone; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ import { | |
| AclTraffic, | ||
| BastionHostLinux, | ||
| CfnEIP, | ||
| CfnIPAMPool, | ||
| CfnSubnet, | ||
| CfnVPC, | ||
| SubnetFilter, | ||
|
|
@@ -3541,6 +3542,169 @@ describe('vpc', () => { | |
| }); | ||
| }); | ||
|
|
||
| describe('Subnet', () => { | ||
| const ipamPoolId = 'ipam-pool-0123456789abcdef0'; | ||
|
|
||
| test('subnet with a concrete cidrBlock renders CidrBlock and no IPAM properties', () => { | ||
| // GIVEN | ||
| const stack = new Stack(); | ||
|
|
||
| // WHEN | ||
| const subnet = new Subnet(stack, 'Subnet', { | ||
| vpcId: 'vpc-1234', | ||
| availabilityZone: 'dummy1a', | ||
| cidrBlock: '10.0.0.0/24', | ||
| }); | ||
|
|
||
| // THEN | ||
| expect(subnet.ipv4CidrBlock).toEqual('10.0.0.0/24'); | ||
| Template.fromStack(stack).hasResourceProperties('AWS::EC2::Subnet', { | ||
| VpcId: 'vpc-1234', | ||
| AvailabilityZone: 'dummy1a', | ||
| CidrBlock: '10.0.0.0/24', | ||
| Ipv4IpamPoolId: Match.absent(), | ||
| Ipv4NetmaskLength: Match.absent(), | ||
| }); | ||
| }); | ||
|
|
||
| test('subnet allocated from an IPAM pool renders Ipv4IpamPoolId and Ipv4NetmaskLength instead of CidrBlock', () => { | ||
| // GIVEN | ||
| const stack = new Stack(); | ||
| const pool = CfnIPAMPool.fromIpamPoolId(stack, 'Pool', ipamPoolId); | ||
|
|
||
| // WHEN | ||
| new Subnet(stack, 'Subnet', { | ||
| vpcId: 'vpc-1234', | ||
| availabilityZone: 'dummy1a', | ||
| ipv4IpamAllocation: { ipamPool: pool, netmaskLength: 24 }, | ||
| }); | ||
|
|
||
| // THEN | ||
| Template.fromStack(stack).hasResourceProperties('AWS::EC2::Subnet', { | ||
| VpcId: 'vpc-1234', | ||
| AvailabilityZone: 'dummy1a', | ||
| CidrBlock: Match.absent(), | ||
| Ipv4IpamPoolId: ipamPoolId, | ||
| Ipv4NetmaskLength: 24, | ||
| }); | ||
| }); | ||
|
|
||
| test('subnet allocated from an IPAM pool defined in the same stack references that pool', () => { | ||
| // GIVEN | ||
| const stack = new Stack(); | ||
| const pool = new CfnIPAMPool(stack, 'Pool', { addressFamily: 'ipv4', ipamScopeId: 'ipam-scope-0123456789abcdef0' }); | ||
|
|
||
| // WHEN | ||
| new Subnet(stack, 'Subnet', { | ||
| vpcId: 'vpc-1234', | ||
| availabilityZone: 'dummy1a', | ||
| ipv4IpamAllocation: { ipamPool: pool, netmaskLength: 24 }, | ||
| }); | ||
|
|
||
| // THEN | ||
| Template.fromStack(stack).hasResourceProperties('AWS::EC2::Subnet', { | ||
| Ipv4IpamPoolId: stack.resolve(pool.ipamPoolRef.ipamPoolId), | ||
| Ipv4NetmaskLength: 24, | ||
| }); | ||
| }); | ||
|
|
||
| test('ipv4CidrBlock of an IPAM-allocated subnet is the CidrBlock attribute of the subnet', () => { | ||
| // GIVEN | ||
| const stack = new Stack(); | ||
| const pool = CfnIPAMPool.fromIpamPoolId(stack, 'Pool', ipamPoolId); | ||
|
|
||
| // WHEN | ||
| const subnet = new Subnet(stack, 'Subnet', { | ||
| vpcId: 'vpc-1234', | ||
| availabilityZone: 'dummy1a', | ||
| ipv4IpamAllocation: { ipamPool: pool, netmaskLength: 24 }, | ||
| }); | ||
|
|
||
| // THEN | ||
| expect(Token.isUnresolved(subnet.ipv4CidrBlock)).toBe(true); | ||
| expect(stack.resolve(subnet.ipv4CidrBlock)).toEqual({ | ||
| 'Fn::GetAtt': [stack.getLogicalId(subnet.node.defaultChild as CfnSubnet), 'CidrBlock'], | ||
| }); | ||
| }); | ||
|
|
||
| test('PublicSubnet and PrivateSubnet accept ipv4IpamAllocation', () => { | ||
| // GIVEN | ||
| const stack = new Stack(); | ||
| const ipv4IpamAllocation = { ipamPool: CfnIPAMPool.fromIpamPoolId(stack, 'Pool', ipamPoolId), netmaskLength: 24 }; | ||
|
|
||
| // WHEN | ||
| const publicSubnet = new PublicSubnet(stack, 'Public', { vpcId: 'vpc-1234', availabilityZone: 'dummy1a', ipv4IpamAllocation }); | ||
| new PrivateSubnet(stack, 'Private', { vpcId: 'vpc-1234', availabilityZone: 'dummy1a', ipv4IpamAllocation }); | ||
| publicSubnet.addNatGateway(); | ||
|
|
||
| // THEN | ||
| const template = Template.fromStack(stack); | ||
| template.resourceCountIs('AWS::EC2::Subnet', 2); | ||
| template.allResourcesProperties('AWS::EC2::Subnet', { | ||
| CidrBlock: Match.absent(), | ||
| Ipv4IpamPoolId: ipamPoolId, | ||
| Ipv4NetmaskLength: 24, | ||
| }); | ||
| template.resourceCountIs('AWS::EC2::NatGateway', 1); | ||
| }); | ||
|
|
||
| test('a token netmaskLength is passed through without validation', () => { | ||
| // GIVEN | ||
| const stack = new Stack(); | ||
| const pool = CfnIPAMPool.fromIpamPoolId(stack, 'Pool', ipamPoolId); | ||
|
|
||
| // WHEN | ||
| new Subnet(stack, 'Subnet', { | ||
| vpcId: 'vpc-1234', | ||
| availabilityZone: 'dummy1a', | ||
| ipv4IpamAllocation: { ipamPool: pool, netmaskLength: Lazy.number({ produce: () => 24 }) }, | ||
| }); | ||
|
|
||
| // THEN | ||
| Template.fromStack(stack).hasResourceProperties('AWS::EC2::Subnet', { | ||
| Ipv4NetmaskLength: 24, | ||
| }); | ||
| }); | ||
|
|
||
| test('fails when both cidrBlock and ipv4IpamAllocation are specified', () => { | ||
| // GIVEN | ||
| const stack = new Stack(); | ||
| const pool = CfnIPAMPool.fromIpamPoolId(stack, 'Pool', ipamPoolId); | ||
|
|
||
| // THEN | ||
| expect(() => new Subnet(stack, 'Subnet', { | ||
| vpcId: 'vpc-1234', | ||
| availabilityZone: 'dummy1a', | ||
| cidrBlock: '10.0.0.0/24', | ||
| ipv4IpamAllocation: { ipamPool: pool, netmaskLength: 24 }, | ||
| })).toThrow(/Cannot specify both 'cidrBlock' and 'ipv4IpamAllocation'/); | ||
| }); | ||
|
|
||
| test('fails when neither cidrBlock nor ipv4IpamAllocation is specified', () => { | ||
| // GIVEN | ||
| const stack = new Stack(); | ||
|
|
||
| // THEN | ||
| expect(() => new Subnet(stack, 'Subnet', { | ||
| vpcId: 'vpc-1234', | ||
| availabilityZone: 'dummy1a', | ||
| })).toThrow(/Either 'cidrBlock' or 'ipv4IpamAllocation' must be specified/); | ||
| }); | ||
|
|
||
| test.each([15, 29])('fails for ipv4IpamAllocation.netmaskLength /%d outside the /16-/28 subnet range', (netmaskLength) => { | ||
| // GIVEN | ||
| const stack = new Stack(); | ||
| const pool = CfnIPAMPool.fromIpamPoolId(stack, 'Pool', ipamPoolId); | ||
|
|
||
| // THEN | ||
| expect(() => new Subnet(stack, 'Subnet', { | ||
| vpcId: 'vpc-1234', | ||
| availabilityZone: 'dummy1a', | ||
| ipv4IpamAllocation: { ipamPool: pool, netmaskLength }, | ||
| })).toThrow(/'ipv4IpamAllocation.netmaskLength' must be between 16 and 28/); | ||
| }); | ||
| }); | ||
|
Comment on lines
+3694
to
+3724
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: this checks that /15 and /29 are rejected. Checking that /16 and /28 are accepted would catch an off-by-one: test.each([16, 28])('accepts ipv4IpamAllocation.netmaskLength /%d', (netmaskLength) => {
const stack = new Stack();
const pool = CfnIPAMPool.fromIpamPoolId(stack, 'Pool', ipamPoolId);
new Subnet(stack, 'Subnet', {
vpcId: 'vpc-1234',
availabilityZone: 'dummy1a',
ipv4IpamAllocation: { ipamPool: pool, netmaskLength },
});
Template.fromStack(stack).hasResourceProperties('AWS::EC2::Subnet', {
Ipv4NetmaskLength: netmaskLength,
});
});
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added |
||
|
|
||
| function getTestStack(): Stack { | ||
| const stack = new Stack(undefined, 'TestStack', { env: { account: '123456789012', region: 'us-east-1' } }); | ||
| acknowledgeTestValidationRules(stack); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.