From 28aea1323a32170fc50aff4ae9b6aa2fa9c8dd14 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Thu, 5 Jul 2018 22:32:30 -0700 Subject: [PATCH 01/35] initial network builder design initial bare bones working vpc --- packages/@aws-cdk/ec2/lib/network-util.ts | 300 ++++++++++++++---- packages/@aws-cdk/ec2/lib/vpc.ts | 151 ++++++++- .../@aws-cdk/ec2/test/test.network-utils.ts | 142 ++++++++- packages/@aws-cdk/ec2/test/test.vpc.ts | 43 ++- 4 files changed, 560 insertions(+), 76 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/network-util.ts b/packages/@aws-cdk/ec2/lib/network-util.ts index 0ad284d5d59a2..5b354ce1569d7 100644 --- a/packages/@aws-cdk/ec2/lib/network-util.ts +++ b/packages/@aws-cdk/ec2/lib/network-util.ts @@ -3,12 +3,12 @@ * range that is either not valid, or outside of the VPC size limits. */ export class InvalidCidrRangeError extends Error { - constructor(cidr: string) { - super(cidr + ' is not a valid VPC CIDR range (must be between /16 and /28)'); - // The following line is required for type checking of custom errors, and must be called right after super() - // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript - Object.setPrototypeOf(this, InvalidCidrRangeError.prototype); - } + constructor(cidr: string) { + super(cidr + ' is not a valid VPC CIDR range (must be between /16 and /28)'); + // The following line is required for type checking of custom errors, and must be called right after super() + // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript + Object.setPrototypeOf(this, InvalidCidrRangeError.prototype); + } } /** @@ -16,12 +16,38 @@ export class InvalidCidrRangeError extends Error { * subnets than it has IP space for. */ export class InvalidSubnetCountError extends Error { - constructor(cidr: string, count: number) { - super('VPC range (' + cidr + ') does not have enough IP space to be split into ' + count + ' subnets'); - // The following line is required for type checking of custom errors, and must be called right after super() - // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript - Object.setPrototypeOf(this, InvalidSubnetCountError.prototype); - } + constructor(cidr: string, count: number) { + super('VPC range (' + cidr + ') does not have enough IP space to be split into ' + count + ' subnets'); + // The following line is required for type checking of custom errors, and must be called right after super() + // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript + Object.setPrototypeOf(this, InvalidSubnetCountError.prototype); + } +} + +/** + * InvalidSubnetCidrError is thrown when attempting to split a CIDR range into more + * subnets than it has IP space for. + */ +export class InvalidSubnetCidrError extends Error { + constructor(cidr: string, subnet: string) { + super('VPC range (' + cidr + ') does not contain ' + subnet); + // The following line is required for type checking of custom errors, and must be called right after super() + // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript + Object.setPrototypeOf(this, InvalidSubnetCidrError.prototype); + } +} + +/** + * InvalidIpAddressError is thrown when attempting to split a CIDR range into more + * subnets than it has IP space for. + */ +export class InvalidIpAddressError extends Error { + constructor(ip: string) { + super(ip + ' is not a valid IP must be (0-255).(0-255).(0-255).(0-255).'); + // The following line is required for type checking of custom errors, and must be called right after super() + // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript + Object.setPrototypeOf(this, InvalidIpAddressError.prototype); + } } /** @@ -29,61 +55,203 @@ export class InvalidSubnetCountError extends Error { */ export class NetworkUtils { - /** - * - * splitCIDR takes a CIDR range (e.g. 10.0.0.0/16) and splits it into - * the provided number of smaller subnets (eg 2 of 10.0.0.0/17). - * - * @param {string} cidr The CIDR range to split (e.g. 10.0.0.0/16) - * @param {number} subnetCount How many subnets to create (min:2 max:30) - * @returns Array An array of CIDR strings (e.g. [ '10.0.0.0/17', '10.0.128.0/17' ]) - */ - public static splitCIDR(cidr: string, subnetCount: number): string[] { - - const parts = cidr.toString().split(/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)\/([0-9]+)/); - - if (parts.length !== 7) { - throw new InvalidCidrRangeError(cidr); - } - - const range = parseInt(parts[5], 10); - const rangeHosts = Math.pow(2, (32 - range)); - const subnetSize = range + Math.round((subnetCount / 2)); - const subnetHosts = Math.pow(2, (32 - subnetSize)); - - // Ensure the VPC cidr range fits within the EC2 VPC parameter ranges - if (range < 16 || range > 28) { - throw new InvalidCidrRangeError(cidr); - } - - // Ensure the resulting subnet size is within the EC2 VPC parameter ranges - if (subnetSize < 16 || subnetSize > 28) { - throw new InvalidSubnetCountError(cidr, subnetCount); - } - - // Check that the requested number of subnets fits into the provided CIDR range - if (subnetHosts === 0 || subnetHosts * subnetCount > rangeHosts) { - throw new InvalidSubnetCountError(cidr, subnetCount); - } - - // Convert the initial CIDR to decimal format - const rangeDec = ((((((+parts[1]) * 256) + (+parts[2])) * 256) + (+parts[3])) * 256) + (+parts[4]); - - // Generate each of the subnets required - const subnets: string[] = []; - for (let i = 0; i < subnetCount; i++) { - const subnetDec = rangeDec + (i * subnetHosts); - // tslint:disable:no-bitwise - const p1 = subnetDec & 255; - const p2 = ((subnetDec >> 8) & 255); - const p3 = ((subnetDec >> 16) & 255); - const p4 = ((subnetDec >> 24) & 255); - // tslint:enable:no-bitwise - subnets.push(p4 + '.' + p3 + '.' + p2 + '.' + p1 + '/' + subnetSize); - } - - return subnets; + /** + * + * splitCIDR takes a CIDR range (e.g. 10.0.0.0/16) and splits it into + * the provided number of smaller subnets (eg 2 of 10.0.0.0/17). + * + * @param {string} cidr The CIDR range to split (e.g. 10.0.0.0/16) + * @param {number} subnetCount How many subnets to create (min:2 max:30) + * @returns Array An array of CIDR strings (e.g. [ '10.0.0.0/17', '10.0.128.0/17' ]) + */ + public static splitCIDR(cidr: string, subnetCount: number): string[] { + + const parts = cidr.toString().split(/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)\/([0-9]+)/); + + if (parts.length !== 7) { + throw new InvalidCidrRangeError(cidr); + } + + const range = parseInt(parts[5], 10); + const rangeHosts = Math.pow(2, (32 - range)); + const subnetSize = range + Math.round((subnetCount / 2)); + const subnetHosts = Math.pow(2, (32 - subnetSize)); + + // Ensure the VPC cidr range fits within the EC2 VPC parameter ranges + if (range < 16 || range > 28) { + throw new InvalidCidrRangeError(cidr); + } + + // Ensure the resulting subnet size is within the EC2 VPC parameter ranges + if (subnetSize < 16 || subnetSize > 28) { + throw new InvalidSubnetCountError(cidr, subnetCount); + } + + // Check that the requested number of subnets fits into the provided CIDR range + if (subnetHosts === 0 || subnetHosts * subnetCount > rangeHosts) { + throw new InvalidSubnetCountError(cidr, subnetCount); + } + + // Convert the initial CIDR to decimal format + const rangeDec = ((((((+parts[1]) * 256) + (+parts[2])) * 256) + (+parts[3])) * 256) + (+parts[4]); + + // Generate each of the subnets required + const subnets: string[] = []; + for (let i = 0; i < subnetCount; i++) { + const subnetDec = rangeDec + (i * subnetHosts); + // tslint:disable:no-bitwise + const p1 = subnetDec & 255; + const p2 = ((subnetDec >> 8) & 255); + const p3 = ((subnetDec >> 16) & 255); + const p4 = ((subnetDec >> 24) & 255); + // tslint:enable:no-bitwise + subnets.push(p4 + '.' + p3 + '.' + p2 + '.' + p1 + '/' + subnetSize); + } + + return subnets; + } + + public static validIp(ipAddress: string): boolean { + return ipAddress.split('.').map( + (octet: string) => (parseInt(octet, 10) >= 0 && parseInt(octet, 10) <= 255)). + reduce((valid: boolean, value: boolean) => (valid && value)); + } + /** + * + * takes an IP Address (e.g. 174.66.173.168) and converts to a number + * (e.g 2923605416); currently only supports IPv4 + * + * Uses the formula: + * (first octet * 256³) + (second octet * 256²) + (third octet * 256) + + * (fourth octet) + * + * @param {string} the IP address (e.g. 174.66.173.168) + * @returns {number} the integer value of the IP address (e.g 2923605416) + */ + + public static ipToNum(ipAddress: string): number { + if (!this.validIp(ipAddress)) { + throw new InvalidIpAddressError(ipAddress); + } + + return ipAddress + .split('.') + .reduce( + (p: number, c: string, i: number) => p + parseInt(c, 10) * 256 ** (3 - i), + 0 + ); + } + + public static numToIp(ipNum: number): string { + // this all because bitwise math is signed + let remaining = ipNum; + const address = []; + for (let i = 0; i < 4; i++) { + if (remaining !== 0) { + address.push(Math.floor(remaining / 256 ** (3 - i))); + remaining = remaining % 256 ** (3 - i); + } else { + address.push(0); + } + } + const ipAddress: string = address.join('.'); + if ( !this.validIp(ipAddress) ) { + throw new InvalidIpAddressError(ipAddress); + } + return ipAddress; + } + +} + +export class NetworkBuilder { + public readonly networkCidr: CidrBlock; + protected subnets: CidrBlock[]; + protected maxIpConsumed: string; + + constructor(cidr: string) { + this.networkCidr = new CidrBlock(cidr); + this.subnets = []; + this.maxIpConsumed = NetworkUtils.numToIp(NetworkUtils. + ipToNum(this.networkCidr.minIp()) - 1); + } + + public addSubnet(mask: number): string { + return this.addSubnets(mask, 1)[0]; + } + + public addSubnets(mask: number, count?: number): string[] { + if (mask < 16 || mask > 28) { + throw new InvalidCidrRangeError(`x.x.x.x/${mask}`); } + const subnets: CidrBlock[] = []; + if (!count) { + count = 1; + } + for (let i = 0; i < count; i ++) { + const subnet: CidrBlock = CidrBlock.fromOffsetIp(this.maxIpConsumed, mask); + this.maxIpConsumed = subnet.maxIp(); + this.subnets.push(subnet); + if (!this.validate()) { + throw new InvalidSubnetCidrError(this.networkCidr.cidr, subnet.cidr); + } + subnets.push(subnet); + } + return subnets.map((subnet) => (subnet.cidr)); + } + + public getSubnets(): string[] { + return this.subnets.map((subnet) => (subnet.cidr)); + } + + public validate(): boolean { + return this.subnets.map( + (cidrBlock) => this.networkCidr.containsCidr(cidrBlock)).reduce( + (containsAll: boolean, contains: boolean) => (containsAll && contains)); + } + +} + +export class CidrBlock { + + public static calculateNetmask(mask: number): string { + return NetworkUtils.numToIp(2 ** 32 - 2 ** (32 - mask)); + } + + public static fromOffsetIp(ipAddress: string, mask: number): CidrBlock { + const initialCidr = new CidrBlock(`${ipAddress}/${mask}`); + const baseIpNum = NetworkUtils.ipToNum(initialCidr.maxIp()) + 1; + return new this(`${NetworkUtils.numToIp(baseIpNum)}/${mask}`); + } + + public readonly cidr: string; + public readonly netmask: string; + public readonly mask: number; + + constructor(cidr: string) { + this.cidr = cidr; + this.mask = parseInt(cidr.split('/')[1], 10); + this.netmask = CidrBlock.calculateNetmask(this.mask); + } + + public maxIp(): string { + const minIpNum = NetworkUtils.ipToNum(this.minIp()); + return NetworkUtils.numToIp(minIpNum + 2 ** (32 - this.mask) - 1); + } + + public minIp(): string { + const netmaskOct = this.netmask.split('.'); + const ipOct = this.cidr.split('/')[0].split('.'); + // tslint:disable:no-bitwise + return netmaskOct.map( + (maskOct, index) => parseInt(maskOct, 10) & parseInt(ipOct[index], 10)).join('.'); + // tslint:enable:no-bitwise + } + public containsCidr(cidr: CidrBlock): boolean { + return [ + NetworkUtils.ipToNum(this.minIp()) <= NetworkUtils.ipToNum(cidr.minIp()), + NetworkUtils.ipToNum(this.maxIp()) >= NetworkUtils.ipToNum(cidr.maxIp()), + ].reduce((contained: boolean, value: boolean) => (contained && value), true); + } } diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index 4bd60087aa0de..f1ab8637db0e5 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -1,6 +1,7 @@ import { AvailabilityZoneProvider, Construct, Tag, Token } from '@aws-cdk/core'; import { ec2 } from '@aws-cdk/resources'; -import { NetworkUtils } from './network-util'; +import { Obj } from '@aws-cdk/util'; +import { NetworkBuilder, NetworkUtils } from './network-util'; import { VpcNetworkId, VpcNetworkRef, VpcSubnetId, VpcSubnetRef } from './vpc-ref'; /** * VpcNetworkProps allows you to specify configuration options for a VPC @@ -58,6 +59,31 @@ export interface VpcNetworkProps { * @default All AZs in the region */ maxAZs?: number; + + /** + * Define subnets that will be built by default per AZ (maxAZs). If left + * empty the default VPC configuration will be used + * + * The subnets are contructed in the context of the VPC so you only need + * specify the type and configuration, the VPC will allocate the details. + * + * For example if you want three private subnets and three public subnets + * across 3 AZs then maxAZs = 3 and provide the following: + * subnets: [ + * { + * cidrMask: 24; + * name: ingress; + * subnetType: SubnetType.Public; + * natGateway: true; + * }, + * { + * cidrMask: 24; + * name: application; + * subnetType: SubnetType.Private; + * } + * ] + */ + subnets?: VpcSubnetBuilderProps[]; } /** @@ -142,6 +168,13 @@ export class VpcNetwork extends VpcNetworkRef { */ public readonly privateSubnets: VpcSubnetRef[] = []; + /** + * List of internal subnets in this VPC + */ + public readonly internalSubnets: VpcSubnetRef[] = []; + + public readonly natGatewayByAZ: Obj = {}; + /** * The VPC resource */ @@ -185,7 +218,7 @@ export class VpcNetwork extends VpcNetworkRef { outboundTraffic === OutboundTrafficMode.FromPublicAndPrivateSubnets; // Create public and private subnets in each AZ - this.createSubnets(cidrBlock, outboundTraffic, props.maxAZs); + this.createSubnets(cidrBlock, outboundTraffic, props.maxAZs, props.subnets); // Create an Internet Gateway and attach it (if the outbound traffic mode != None) if (allowOutbound) { @@ -213,7 +246,11 @@ export class VpcNetwork extends VpcNetworkRef { * createSubnets takes a VPC, and creates a public and private subnet * in each Availability Zone. */ - private createSubnets(cidr: string, outboundTraffic: OutboundTrafficMode, maxAZs?: number) { + private createSubnets( + cidr: string, + outboundTraffic: OutboundTrafficMode, + maxAZs?: number, + subnets?: VpcSubnetBuilderProps[]) { // Calculate number of public/private subnets based on number of AZs const zones = new AvailabilityZoneProvider(this).availabilityZones; @@ -224,11 +261,16 @@ export class VpcNetwork extends VpcNetworkRef { zones.splice(maxAZs); } - // Split the CIDR range into each availablity zone - const ranges = NetworkUtils.splitCIDR(cidr, zones.length); + if (subnets != null) { + const networkBuilder = new NetworkBuilder(cidr); + this.createCustomSubnets(networkBuilder, subnets, zones); + } else { + // Split the CIDR range into each availablity zone + const ranges = NetworkUtils.splitCIDR(cidr, zones.length); - for (let i = 0; i < zones.length; i++) { + for (let i = 0; i < zones.length; i++) { this.createSubnetPair(ranges[i], zones[i], i + 1, outboundTraffic); + } } } @@ -263,7 +305,104 @@ export class VpcNetwork extends VpcNetworkRef { this.privateSubnets.push(privateSubnet); this.dependencyElements.push(publicSubnet, privateSubnet); } + private createCustomSubnets(builder: NetworkBuilder, subnets: VpcSubnetBuilderProps[], zones: string[]) { + + for (const subnet of subnets) { + let azs = zones; + if (subnet.numAZs != null) { + azs = zones.slice(subnet.numAZs); + } + for (const zone of azs) { + const cidr: string = builder.addSubnet(subnet.cidrMask); + const name: string = `${subnet.name}AZ${zone.substr(-1)}`; + switch (subnet.subnetType) { + case SubnetType.Public: + const publicSubnet = new VpcPublicSubnet(this, name, { + mapPublicIpOnLaunch: subnet.mapPublicIpOnLaunch || true, + vpcId: this.vpcId, + availabilityZone: zone, + cidrBlock: cidr + }); + if (subnet.natGateway) { + this.natGatewayByAZ[zone] = publicSubnet.addNatGateway(); + } + this.publicSubnets.push(publicSubnet); + break; + case SubnetType.Private: + const privateSubnet = new VpcPrivateSubnet(this, name, { + mapPublicIpOnLaunch: subnet.mapPublicIpOnLaunch || false, + vpcId: this.vpcId, + availabilityZone: zone, + cidrBlock: cidr + }); + this.privateSubnets.push(privateSubnet); + break; + case SubnetType.Internal: + const internalSubnet = new VpcPrivateSubnet(this, name, { + mapPublicIpOnLaunch: subnet.mapPublicIpOnLaunch || false, + vpcId: this.vpcId, + availabilityZone: zone, + cidrBlock: cidr + }); + this.internalSubnets.push(internalSubnet); + break; + } + } + } + (this.privateSubnets as VpcPrivateSubnet[]).forEach((privateSubnet, i) => { + let ngwId = this.natGatewayByAZ[privateSubnet.availabilityZone]; + if (ngwId === undefined) { + const ngwArray = Array.from(Object.values(this.natGatewayByAZ)); + ngwId = ngwArray[i % ngwArray.length]; + } + privateSubnet.addDefaultNatRouteEntry(ngwId); + }); + } + +} +/** + * The type of Subnet + */ +export enum SubnetType { + + /** + * Outbound traffic is not routed. This can be good for subnets with RDS or + * Elasticache endpoints + */ + Internal = 1, + + /** + * Outbound traffic will be routed via an Internet Gateway. If this is set + * and OutboundTrafficMode.None is configure an error will be thrown. + */ + Public = 2, + + /** + * Outbound traffic will be routed via a NAT Gateways preference being in + * the same AZ, but if not available will use another AZ. This is common for + * experimental cost conscious accounts or accounts where HA outbound + * traffic is not needed. + */ + Private = 3 +} + +/** + * Specify configuration parameters for a VPC to be built + */ +export interface VpcSubnetBuilderProps { + // the cidr mask value from 16-28 + cidrMask: number; + // Public (IGW), Private (Nat GW), Internal (no outbound) + subnetType: SubnetType; + // name that will be used to generate an AZ specific name e.g. name-2a + name: string; + // if true will place a NAT Gateway in this subnet, subnetType must be Public + natGateway?: boolean; + // defaults to false if true will map public IPs on launch + mapPublicIpOnLaunch?: boolean; + // number of AZs to build this subnet in + numAZs?: number; } /** diff --git a/packages/@aws-cdk/ec2/test/test.network-utils.ts b/packages/@aws-cdk/ec2/test/test.network-utils.ts index 87eed55ac9108..25af50f2fc40d 100644 --- a/packages/@aws-cdk/ec2/test/test.network-utils.ts +++ b/packages/@aws-cdk/ec2/test/test.network-utils.ts @@ -1,5 +1,13 @@ import { Test } from 'nodeunit'; -import { InvalidCidrRangeError, InvalidSubnetCountError, NetworkUtils } from '../lib/network-util'; +import { + CidrBlock, + InvalidCidrRangeError, + InvalidIpAddressError, + InvalidSubnetCidrError, + InvalidSubnetCountError, + NetworkBuilder, + NetworkUtils +} from '../lib/network-util'; export = { @@ -34,7 +42,135 @@ export = { ]); test.done(); } + }, + IP: { + "should convert a valid IP Address to an integer"(test: Test) { + test.strictEqual(NetworkUtils.ipToNum('174.66.173.168'), 2923605416); + test.done(); + }, + "should throw on invalid IP Address"(test: Test) { + test.throws(() => { + NetworkUtils.ipToNum('174.266.173.168'); + }, InvalidIpAddressError); + test.done(); + }, + "should convert a valid IP integer to a staring"(test: Test) { + test.strictEqual(NetworkUtils.numToIp(2923605416), '174.66.173.168'); + test.done(); + }, + "should throw an error for invalid IP"(test: Test) { + test.throws(() => { + NetworkUtils.numToIp(2923605416 * 5); + }, InvalidIpAddressError); + test.throws(() => { + NetworkUtils.numToIp(-1); + }, InvalidIpAddressError); + test.done(); + }, + }, + CidrBlock: { + "should return the next valid subnet from offset IP"(test: Test) { + const newBlock = CidrBlock.fromOffsetIp('10.0.1.10', 24); + test.strictEqual(newBlock.cidr, '10.0.2.0/24'); + test.done(); + }, + "maxIp() should return the last usable IP from the CidrBlock"(test: Test) { + const block = new CidrBlock('10.0.3.0/24'); + test.strictEqual(block.maxIp(), '10.0.3.255'); + test.done(); + }, + "minIp() should return the first usable IP from the CidrBlock"(test: Test) { + const testValues = [ + ['192.168.0.0/18', '192.168.0.0'], + ['10.0.3.0/24', '10.0.3.0'] + ]; + for (const answer of testValues) { + const block = new CidrBlock(answer[0]); + test.strictEqual(block.minIp(), answer[1]); + } + test.done(); + }, + "containsCidr returns true if fully contained"(test: Test) { + const block = new CidrBlock('10.0.3.0/24'); + const contained = new CidrBlock('10.0.3.0/26'); + test.strictEqual(block.containsCidr(contained), true); + test.done(); + }, + "containsCidr returns false if not fully contained"(test: Test) { + const block = new CidrBlock('10.0.3.0/26'); + const notContained = new CidrBlock('10.0.3.0/25'); + test.strictEqual(block.containsCidr(notContained), false); + test.done(); + }, + "calculateNetmask returns the ip string mask"(test: Test) { + const netmask = CidrBlock.calculateNetmask(27); + test.strictEqual(netmask, '255.255.255.224'); + test.done(); + }, + }, + NetworkBuilder: { + "allows you to carve subnets our of CIDR network"(test: Test) { + const answers = [ + [ + '192.168.0.0/28', + '192.168.0.16/28', + '192.168.0.32/28', + '192.168.0.128/25', + '192.168.1.0/25', + '192.168.4.0/22', + ], + [ + '192.168.0.0/24', + '192.168.1.0/24', + '192.168.2.0/24', + '192.168.3.0/25', + '192.168.3.128/25', + '192.168.4.0/25', + '192.168.4.128/28', + '192.168.4.144/28', + '192.168.4.160/28', + ] + ]; + const wasteful = new NetworkBuilder('192.168.0.0/18'); + const efficient = new NetworkBuilder('192.168.0.0/18'); + wasteful.addSubnets(28, 3); + wasteful.addSubnets(25, 2); + wasteful.addSubnets(22, 1); + efficient.addSubnets(24, 3); + efficient.addSubnets(25, 3); + efficient.addSubnets(28, 3); + const expected = [ + wasteful.getSubnets().sort(), + efficient.getSubnets().sort() + ]; + for (let i = 0; i < answers.length; i++) { + test.deepEqual(answers[i].sort(), expected[i]); + } + test.done(); + }, + "throws on subnets < 16 or > 28"(test: Test) { + const builder = new NetworkBuilder('192.168.0.0/18'); + test.throws(() => { + builder.addSubnet(15); + }, InvalidCidrRangeError); + test.throws(() => { + builder.addSubnet(29); + }, InvalidCidrRangeError); + test.done(); + }, + "throws if you add a subnet outside of the cidr"(test: Test) { + const builder = new NetworkBuilder('192.168.0.0/18'); + const builder2 = new NetworkBuilder('10.0.0.0/21'); + builder.addSubnets(19, 2); + builder2.addSubnets(24, 8); + test.throws(() => { + builder.addSubnet(19); + }, InvalidSubnetCidrError); + test.throws(() => { + builder2.addSubnet(24); + }, InvalidSubnetCidrError); + test.done(); + } } - -}; \ No newline at end of file +}; diff --git a/packages/@aws-cdk/ec2/test/test.vpc.ts b/packages/@aws-cdk/ec2/test/test.vpc.ts index 8160b146f84fb..b56143cf0e48f 100644 --- a/packages/@aws-cdk/ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/ec2/test/test.vpc.ts @@ -1,7 +1,7 @@ import { countResources, expect, haveResource } from '@aws-cdk/assert'; import { AvailabilityZoneProvider, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; -import { DefaultInstanceTenancy, OutboundTrafficMode, VpcNetwork } from '../lib'; +import { DefaultInstanceTenancy, OutboundTrafficMode, SubnetType, VpcNetwork } from '../lib'; export = { @@ -87,6 +87,47 @@ export = { test.done(); }, + "with custom subents, the VPC should have the right number of subnets, an IGW, and a NAT Gateway per AZ"(test: Test) { + const stack = getTestStack(); + const zones = new AvailabilityZoneProvider(stack).availabilityZones.length; + new VpcNetwork(stack, 'TheVPC', { + cidr: '10.0.0.0/21', + outboundTraffic: OutboundTrafficMode.FromPublicAndPrivateSubnets, + subnets: [ + { + cidrMask: 24, + name: 'ingress', + subnetType: SubnetType.Public, + natGateway: true, + }, + { + cidrMask: 24, + name: 'application', + subnetType: SubnetType.Private, + }, + { + cidrMask: 28, + name: 'rds', + subnetType: SubnetType.Internal, + } + ], + maxAZs: 3 + }); + expect(stack).to(countResources("AWS::EC2::InternetGateway", 1)); + expect(stack).to(countResources("AWS::EC2::NatGateway", zones)); + expect(stack).to(countResources("AWS::EC2::Subnet", 9)); + for (let i = 0; i < 6; i++) { + expect(stack).to(haveResource("AWS::EC2::Subnet", { + CidrBlock: `10.0.${i}.0/24` + })); + } + for (let i = 0; i < 3; i++) { + expect(stack).to(haveResource("AWS::EC2::Subnet", { + CidrBlock: `10.0.6.${i * 16}/28` + })); + } + test.done(); + }, "with enableDnsHostnames enabled but enableDnsSupport disabled, should throw an Error"(test: Test) { const stack = getTestStack(); test.throws(() => new VpcNetwork(stack, 'TheVPC', { From 2beb4a78b9cc712c3cc6e277ca06dc5850a6c6f6 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Mon, 9 Jul 2018 22:25:26 -0700 Subject: [PATCH 02/35] fixing tabs == 4 spaces in network-util.ts --- packages/@aws-cdk/ec2/lib/network-util.ts | 398 +++++++++++----------- 1 file changed, 199 insertions(+), 199 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/network-util.ts b/packages/@aws-cdk/ec2/lib/network-util.ts index 5b354ce1569d7..6c7809f60ce8a 100644 --- a/packages/@aws-cdk/ec2/lib/network-util.ts +++ b/packages/@aws-cdk/ec2/lib/network-util.ts @@ -3,12 +3,12 @@ * range that is either not valid, or outside of the VPC size limits. */ export class InvalidCidrRangeError extends Error { - constructor(cidr: string) { - super(cidr + ' is not a valid VPC CIDR range (must be between /16 and /28)'); - // The following line is required for type checking of custom errors, and must be called right after super() - // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript - Object.setPrototypeOf(this, InvalidCidrRangeError.prototype); - } + constructor(cidr: string) { + super(cidr + ' is not a valid VPC CIDR range (must be between /16 and /28)'); + // The following line is required for type checking of custom errors, and must be called right after super() + // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript + Object.setPrototypeOf(this, InvalidCidrRangeError.prototype); + } } /** @@ -16,12 +16,12 @@ export class InvalidCidrRangeError extends Error { * subnets than it has IP space for. */ export class InvalidSubnetCountError extends Error { - constructor(cidr: string, count: number) { - super('VPC range (' + cidr + ') does not have enough IP space to be split into ' + count + ' subnets'); - // The following line is required for type checking of custom errors, and must be called right after super() - // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript - Object.setPrototypeOf(this, InvalidSubnetCountError.prototype); - } + constructor(cidr: string, count: number) { + super('VPC range (' + cidr + ') does not have enough IP space to be split into ' + count + ' subnets'); + // The following line is required for type checking of custom errors, and must be called right after super() + // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript + Object.setPrototypeOf(this, InvalidSubnetCountError.prototype); + } } /** @@ -29,12 +29,12 @@ export class InvalidSubnetCountError extends Error { * subnets than it has IP space for. */ export class InvalidSubnetCidrError extends Error { - constructor(cidr: string, subnet: string) { - super('VPC range (' + cidr + ') does not contain ' + subnet); - // The following line is required for type checking of custom errors, and must be called right after super() - // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript - Object.setPrototypeOf(this, InvalidSubnetCidrError.prototype); - } + constructor(cidr: string, subnet: string) { + super('VPC range (' + cidr + ') does not contain ' + subnet); + // The following line is required for type checking of custom errors, and must be called right after super() + // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript + Object.setPrototypeOf(this, InvalidSubnetCidrError.prototype); + } } /** @@ -42,12 +42,12 @@ export class InvalidSubnetCidrError extends Error { * subnets than it has IP space for. */ export class InvalidIpAddressError extends Error { - constructor(ip: string) { - super(ip + ' is not a valid IP must be (0-255).(0-255).(0-255).(0-255).'); - // The following line is required for type checking of custom errors, and must be called right after super() - // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript - Object.setPrototypeOf(this, InvalidIpAddressError.prototype); - } + constructor(ip: string) { + super(ip + ' is not a valid IP must be (0-255).(0-255).(0-255).(0-255).'); + // The following line is required for type checking of custom errors, and must be called right after super() + // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript + Object.setPrototypeOf(this, InvalidIpAddressError.prototype); + } } /** @@ -55,203 +55,203 @@ export class InvalidIpAddressError extends Error { */ export class NetworkUtils { - /** - * - * splitCIDR takes a CIDR range (e.g. 10.0.0.0/16) and splits it into - * the provided number of smaller subnets (eg 2 of 10.0.0.0/17). - * - * @param {string} cidr The CIDR range to split (e.g. 10.0.0.0/16) - * @param {number} subnetCount How many subnets to create (min:2 max:30) - * @returns Array An array of CIDR strings (e.g. [ '10.0.0.0/17', '10.0.128.0/17' ]) - */ - public static splitCIDR(cidr: string, subnetCount: number): string[] { - - const parts = cidr.toString().split(/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)\/([0-9]+)/); - - if (parts.length !== 7) { - throw new InvalidCidrRangeError(cidr); - } + /** + * + * splitCIDR takes a CIDR range (e.g. 10.0.0.0/16) and splits it into + * the provided number of smaller subnets (eg 2 of 10.0.0.0/17). + * + * @param {string} cidr The CIDR range to split (e.g. 10.0.0.0/16) + * @param {number} subnetCount How many subnets to create (min:2 max:30) + * @returns Array An array of CIDR strings (e.g. [ '10.0.0.0/17', '10.0.128.0/17' ]) + */ + public static splitCIDR(cidr: string, subnetCount: number): string[] { + + const parts = cidr.toString().split(/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)\/([0-9]+)/); + + if (parts.length !== 7) { + throw new InvalidCidrRangeError(cidr); + } + + const range = parseInt(parts[5], 10); + const rangeHosts = Math.pow(2, (32 - range)); + const subnetSize = range + Math.round((subnetCount / 2)); + const subnetHosts = Math.pow(2, (32 - subnetSize)); + + // Ensure the VPC cidr range fits within the EC2 VPC parameter ranges + if (range < 16 || range > 28) { + throw new InvalidCidrRangeError(cidr); + } + + // Ensure the resulting subnet size is within the EC2 VPC parameter ranges + if (subnetSize < 16 || subnetSize > 28) { + throw new InvalidSubnetCountError(cidr, subnetCount); + } + + // Check that the requested number of subnets fits into the provided CIDR range + if (subnetHosts === 0 || subnetHosts * subnetCount > rangeHosts) { + throw new InvalidSubnetCountError(cidr, subnetCount); + } + + // Convert the initial CIDR to decimal format + const rangeDec = ((((((+parts[1]) * 256) + (+parts[2])) * 256) + (+parts[3])) * 256) + (+parts[4]); + + // Generate each of the subnets required + const subnets: string[] = []; + for (let i = 0; i < subnetCount; i++) { + const subnetDec = rangeDec + (i * subnetHosts); + // tslint:disable:no-bitwise + const p1 = subnetDec & 255; + const p2 = ((subnetDec >> 8) & 255); + const p3 = ((subnetDec >> 16) & 255); + const p4 = ((subnetDec >> 24) & 255); + // tslint:enable:no-bitwise + subnets.push(p4 + '.' + p3 + '.' + p2 + '.' + p1 + '/' + subnetSize); + } + + return subnets; - const range = parseInt(parts[5], 10); - const rangeHosts = Math.pow(2, (32 - range)); - const subnetSize = range + Math.round((subnetCount / 2)); - const subnetHosts = Math.pow(2, (32 - subnetSize)); + } - // Ensure the VPC cidr range fits within the EC2 VPC parameter ranges - if (range < 16 || range > 28) { - throw new InvalidCidrRangeError(cidr); + public static validIp(ipAddress: string): boolean { + return ipAddress.split('.').map( + (octet: string) => (parseInt(octet, 10) >= 0 && parseInt(octet, 10) <= 255)). + reduce((valid: boolean, value: boolean) => (valid && value)); + } + /** + * + * takes an IP Address (e.g. 174.66.173.168) and converts to a number + * (e.g 2923605416); currently only supports IPv4 + * + * Uses the formula: + * (first octet * 256³) + (second octet * 256²) + (third octet * 256) + + * (fourth octet) + * + * @param {string} the IP address (e.g. 174.66.173.168) + * @returns {number} the integer value of the IP address (e.g 2923605416) + */ + + public static ipToNum(ipAddress: string): number { + if (!this.validIp(ipAddress)) { + throw new InvalidIpAddressError(ipAddress); + } + + return ipAddress + .split('.') + .reduce( + (p: number, c: string, i: number) => p + parseInt(c, 10) * 256 ** (3 - i), + 0 + ); } - // Ensure the resulting subnet size is within the EC2 VPC parameter ranges - if (subnetSize < 16 || subnetSize > 28) { - throw new InvalidSubnetCountError(cidr, subnetCount); + public static numToIp(ipNum: number): string { + // this all because bitwise math is signed + let remaining = ipNum; + const address = []; + for (let i = 0; i < 4; i++) { + if (remaining !== 0) { + address.push(Math.floor(remaining / 256 ** (3 - i))); + remaining = remaining % 256 ** (3 - i); + } else { + address.push(0); + } + } + const ipAddress: string = address.join('.'); + if ( !this.validIp(ipAddress) ) { + throw new InvalidIpAddressError(ipAddress); + } + return ipAddress; } - // Check that the requested number of subnets fits into the provided CIDR range - if (subnetHosts === 0 || subnetHosts * subnetCount > rangeHosts) { - throw new InvalidSubnetCountError(cidr, subnetCount); +} + +export class NetworkBuilder { + public readonly networkCidr: CidrBlock; + protected subnets: CidrBlock[]; + protected maxIpConsumed: string; + + constructor(cidr: string) { + this.networkCidr = new CidrBlock(cidr); + this.subnets = []; + this.maxIpConsumed = NetworkUtils.numToIp(NetworkUtils. + ipToNum(this.networkCidr.minIp()) - 1); } - // Convert the initial CIDR to decimal format - const rangeDec = ((((((+parts[1]) * 256) + (+parts[2])) * 256) + (+parts[3])) * 256) + (+parts[4]); - - // Generate each of the subnets required - const subnets: string[] = []; - for (let i = 0; i < subnetCount; i++) { - const subnetDec = rangeDec + (i * subnetHosts); - // tslint:disable:no-bitwise - const p1 = subnetDec & 255; - const p2 = ((subnetDec >> 8) & 255); - const p3 = ((subnetDec >> 16) & 255); - const p4 = ((subnetDec >> 24) & 255); - // tslint:enable:no-bitwise - subnets.push(p4 + '.' + p3 + '.' + p2 + '.' + p1 + '/' + subnetSize); + public addSubnet(mask: number): string { + return this.addSubnets(mask, 1)[0]; } - return subnets; - - } - - public static validIp(ipAddress: string): boolean { - return ipAddress.split('.').map( - (octet: string) => (parseInt(octet, 10) >= 0 && parseInt(octet, 10) <= 255)). - reduce((valid: boolean, value: boolean) => (valid && value)); - } - /** - * - * takes an IP Address (e.g. 174.66.173.168) and converts to a number - * (e.g 2923605416); currently only supports IPv4 - * - * Uses the formula: - * (first octet * 256³) + (second octet * 256²) + (third octet * 256) + - * (fourth octet) - * - * @param {string} the IP address (e.g. 174.66.173.168) - * @returns {number} the integer value of the IP address (e.g 2923605416) - */ - - public static ipToNum(ipAddress: string): number { - if (!this.validIp(ipAddress)) { - throw new InvalidIpAddressError(ipAddress); + public addSubnets(mask: number, count?: number): string[] { + if (mask < 16 || mask > 28) { + throw new InvalidCidrRangeError(`x.x.x.x/${mask}`); + } + const subnets: CidrBlock[] = []; + if (!count) { + count = 1; + } + for (let i = 0; i < count; i ++) { + const subnet: CidrBlock = CidrBlock.fromOffsetIp(this.maxIpConsumed, mask); + this.maxIpConsumed = subnet.maxIp(); + this.subnets.push(subnet); + if (!this.validate()) { + throw new InvalidSubnetCidrError(this.networkCidr.cidr, subnet.cidr); + } + subnets.push(subnet); + } + return subnets.map((subnet) => (subnet.cidr)); } - return ipAddress - .split('.') - .reduce( - (p: number, c: string, i: number) => p + parseInt(c, 10) * 256 ** (3 - i), - 0 - ); - } - - public static numToIp(ipNum: number): string { - // this all because bitwise math is signed - let remaining = ipNum; - const address = []; - for (let i = 0; i < 4; i++) { - if (remaining !== 0) { - address.push(Math.floor(remaining / 256 ** (3 - i))); - remaining = remaining % 256 ** (3 - i); - } else { - address.push(0); - } + public getSubnets(): string[] { + return this.subnets.map((subnet) => (subnet.cidr)); } - const ipAddress: string = address.join('.'); - if ( !this.validIp(ipAddress) ) { - throw new InvalidIpAddressError(ipAddress); + + public validate(): boolean { + return this.subnets.map( + (cidrBlock) => this.networkCidr.containsCidr(cidrBlock)).reduce( + (containsAll: boolean, contains: boolean) => (containsAll && contains)); } - return ipAddress; - } } -export class NetworkBuilder { - public readonly networkCidr: CidrBlock; - protected subnets: CidrBlock[]; - protected maxIpConsumed: string; - - constructor(cidr: string) { - this.networkCidr = new CidrBlock(cidr); - this.subnets = []; - this.maxIpConsumed = NetworkUtils.numToIp(NetworkUtils. - ipToNum(this.networkCidr.minIp()) - 1); - } - - public addSubnet(mask: number): string { - return this.addSubnets(mask, 1)[0]; - } - - public addSubnets(mask: number, count?: number): string[] { - if (mask < 16 || mask > 28) { - throw new InvalidCidrRangeError(`x.x.x.x/${mask}`); - } - const subnets: CidrBlock[] = []; - if (!count) { - count = 1; +export class CidrBlock { + + public static calculateNetmask(mask: number): string { + return NetworkUtils.numToIp(2 ** 32 - 2 ** (32 - mask)); } - for (let i = 0; i < count; i ++) { - const subnet: CidrBlock = CidrBlock.fromOffsetIp(this.maxIpConsumed, mask); - this.maxIpConsumed = subnet.maxIp(); - this.subnets.push(subnet); - if (!this.validate()) { - throw new InvalidSubnetCidrError(this.networkCidr.cidr, subnet.cidr); - } - subnets.push(subnet); + + public static fromOffsetIp(ipAddress: string, mask: number): CidrBlock { + const initialCidr = new CidrBlock(`${ipAddress}/${mask}`); + const baseIpNum = NetworkUtils.ipToNum(initialCidr.maxIp()) + 1; + return new this(`${NetworkUtils.numToIp(baseIpNum)}/${mask}`); } - return subnets.map((subnet) => (subnet.cidr)); - } - public getSubnets(): string[] { - return this.subnets.map((subnet) => (subnet.cidr)); - } + public readonly cidr: string; + public readonly netmask: string; + public readonly mask: number; - public validate(): boolean { - return this.subnets.map( - (cidrBlock) => this.networkCidr.containsCidr(cidrBlock)).reduce( - (containsAll: boolean, contains: boolean) => (containsAll && contains)); - } + constructor(cidr: string) { + this.cidr = cidr; + this.mask = parseInt(cidr.split('/')[1], 10); + this.netmask = CidrBlock.calculateNetmask(this.mask); + } -} + public maxIp(): string { + const minIpNum = NetworkUtils.ipToNum(this.minIp()); + return NetworkUtils.numToIp(minIpNum + 2 ** (32 - this.mask) - 1); + } -export class CidrBlock { + public minIp(): string { + const netmaskOct = this.netmask.split('.'); + const ipOct = this.cidr.split('/')[0].split('.'); + // tslint:disable:no-bitwise + return netmaskOct.map( + (maskOct, index) => parseInt(maskOct, 10) & parseInt(ipOct[index], 10)).join('.'); + // tslint:enable:no-bitwise + } - public static calculateNetmask(mask: number): string { - return NetworkUtils.numToIp(2 ** 32 - 2 ** (32 - mask)); - } - - public static fromOffsetIp(ipAddress: string, mask: number): CidrBlock { - const initialCidr = new CidrBlock(`${ipAddress}/${mask}`); - const baseIpNum = NetworkUtils.ipToNum(initialCidr.maxIp()) + 1; - return new this(`${NetworkUtils.numToIp(baseIpNum)}/${mask}`); - } - - public readonly cidr: string; - public readonly netmask: string; - public readonly mask: number; - - constructor(cidr: string) { - this.cidr = cidr; - this.mask = parseInt(cidr.split('/')[1], 10); - this.netmask = CidrBlock.calculateNetmask(this.mask); - } - - public maxIp(): string { - const minIpNum = NetworkUtils.ipToNum(this.minIp()); - return NetworkUtils.numToIp(minIpNum + 2 ** (32 - this.mask) - 1); - } - - public minIp(): string { - const netmaskOct = this.netmask.split('.'); - const ipOct = this.cidr.split('/')[0].split('.'); - // tslint:disable:no-bitwise - return netmaskOct.map( - (maskOct, index) => parseInt(maskOct, 10) & parseInt(ipOct[index], 10)).join('.'); - // tslint:enable:no-bitwise - } - - public containsCidr(cidr: CidrBlock): boolean { - return [ - NetworkUtils.ipToNum(this.minIp()) <= NetworkUtils.ipToNum(cidr.minIp()), - NetworkUtils.ipToNum(this.maxIp()) >= NetworkUtils.ipToNum(cidr.maxIp()), - ].reduce((contained: boolean, value: boolean) => (contained && value), true); - } + public containsCidr(cidr: CidrBlock): boolean { + return [ + NetworkUtils.ipToNum(this.minIp()) <= NetworkUtils.ipToNum(cidr.minIp()), + NetworkUtils.ipToNum(this.maxIp()) >= NetworkUtils.ipToNum(cidr.maxIp()), + ].reduce((contained: boolean, value: boolean) => (contained && value), true); + } } From 2a172ed4c8701dd76ef2625766cacf4ab6357c87 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Mon, 9 Jul 2018 23:22:55 -0700 Subject: [PATCH 03/35] moving to `get` methods for subnets, adding additional comment detail --- packages/@aws-cdk/ec2/lib/network-util.ts | 25 ++++++++++++++----- packages/@aws-cdk/ec2/lib/vpc.ts | 25 +++++++++++-------- .../@aws-cdk/ec2/test/test.network-utils.ts | 4 +-- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/network-util.ts b/packages/@aws-cdk/ec2/lib/network-util.ts index 6c7809f60ce8a..e5355a7b05145 100644 --- a/packages/@aws-cdk/ec2/lib/network-util.ts +++ b/packages/@aws-cdk/ec2/lib/network-util.ts @@ -143,6 +143,19 @@ export class NetworkUtils { ); } + /** + * + * takes a number (e.g 2923605416) and converts it to an IPv4 address string + * currently only supports IPv4 + * + * Uses the formula: + * (first octet * 256³) + (second octet * 256²) + (third octet * 256) + + * (fourth octet) + * + * @param {number} the integer value of the IP address (e.g 2923605416) + * @returns {string} the IPv4 address (e.g. 174.66.173.168) + */ + public static numToIp(ipNum: number): string { // this all because bitwise math is signed let remaining = ipNum; @@ -166,12 +179,12 @@ export class NetworkUtils { export class NetworkBuilder { public readonly networkCidr: CidrBlock; - protected subnets: CidrBlock[]; + protected subnetCidrs: CidrBlock[]; protected maxIpConsumed: string; constructor(cidr: string) { this.networkCidr = new CidrBlock(cidr); - this.subnets = []; + this.subnetCidrs = []; this.maxIpConsumed = NetworkUtils.numToIp(NetworkUtils. ipToNum(this.networkCidr.minIp()) - 1); } @@ -191,7 +204,7 @@ export class NetworkBuilder { for (let i = 0; i < count; i ++) { const subnet: CidrBlock = CidrBlock.fromOffsetIp(this.maxIpConsumed, mask); this.maxIpConsumed = subnet.maxIp(); - this.subnets.push(subnet); + this.subnetCidrs.push(subnet); if (!this.validate()) { throw new InvalidSubnetCidrError(this.networkCidr.cidr, subnet.cidr); } @@ -200,12 +213,12 @@ export class NetworkBuilder { return subnets.map((subnet) => (subnet.cidr)); } - public getSubnets(): string[] { - return this.subnets.map((subnet) => (subnet.cidr)); + public get subnets(): string[] { + return this.subnetCidrs.map((subnet) => (subnet.cidr)); } public validate(): boolean { - return this.subnets.map( + return this.subnetCidrs.map( (cidrBlock) => this.networkCidr.containsCidr(cidrBlock)).reduce( (containsAll: boolean, contains: boolean) => (containsAll && contains)); } diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index f1ab8637db0e5..0622133c91485 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -61,27 +61,30 @@ export interface VpcNetworkProps { maxAZs?: number; /** - * Define subnets that will be built by default per AZ (maxAZs). If left - * empty the default VPC configuration will be used + * Define subnets that will be built per AZ (maxAZs), if numAZs is + * specified then only that number of AZs will have subnets. * - * The subnets are contructed in the context of the VPC so you only need - * specify the type and configuration, the VPC will allocate the details. + * The subnets are constructed in the context of the VPC so you only need + * specify the configuration. The VPC details (VPC ID, specific CIDR, + * specific AZ will be calculated during creation) * * For example if you want three private subnets and three public subnets * across 3 AZs then maxAZs = 3 and provide the following: * subnets: [ * { - * cidrMask: 24; - * name: ingress; - * subnetType: SubnetType.Public; - * natGateway: true; + * cidrMask: 24, + * name: ingress, + * subnetType: SubnetType.Public, + * natGateway: true, * }, * { - * cidrMask: 24; - * name: application; - * subnetType: SubnetType.Private; + * cidrMask: 24, + * name: application, + * subnetType: SubnetType.Private, * } * ] + * @default the VPC CIDR will be evenly divided between 1 public and 1 + * private subnet per AZ */ subnets?: VpcSubnetBuilderProps[]; } diff --git a/packages/@aws-cdk/ec2/test/test.network-utils.ts b/packages/@aws-cdk/ec2/test/test.network-utils.ts index 25af50f2fc40d..bc674daef19fc 100644 --- a/packages/@aws-cdk/ec2/test/test.network-utils.ts +++ b/packages/@aws-cdk/ec2/test/test.network-utils.ts @@ -141,8 +141,8 @@ export = { efficient.addSubnets(25, 3); efficient.addSubnets(28, 3); const expected = [ - wasteful.getSubnets().sort(), - efficient.getSubnets().sort() + wasteful.subnets.sort(), + efficient.subnets.sort() ]; for (let i = 0; i < answers.length; i++) { test.deepEqual(answers[i].sort(), expected[i]); From 9da7a2f1336b3bea20edc9b20b866614a91efb73 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Mon, 9 Jul 2018 23:39:26 -0700 Subject: [PATCH 04/35] moving network-util vaildIp func to filter instead of reduce for readability --- packages/@aws-cdk/ec2/lib/network-util.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/network-util.ts b/packages/@aws-cdk/ec2/lib/network-util.ts index e5355a7b05145..bf9ab1c4e9046 100644 --- a/packages/@aws-cdk/ec2/lib/network-util.ts +++ b/packages/@aws-cdk/ec2/lib/network-util.ts @@ -113,9 +113,8 @@ export class NetworkUtils { } public static validIp(ipAddress: string): boolean { - return ipAddress.split('.').map( - (octet: string) => (parseInt(octet, 10) >= 0 && parseInt(octet, 10) <= 255)). - reduce((valid: boolean, value: boolean) => (valid && value)); + return ipAddress.split('.').map((octet: string) => parseInt(octet, 10)). + filter((octet: number) => octet >= 0 && octet <= 255).length === 4; } /** * From 55e3666931a590f93b8dfa82d2b43bb2b0fd3a0b Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Tue, 10 Jul 2018 09:54:35 -0700 Subject: [PATCH 05/35] renaming of VpcSubnetBuilderProps to SubnetConfiguration; doc updates; removed public NatGatewayByAz to local variable --- packages/@aws-cdk/ec2/lib/network-util.ts | 6 +--- packages/@aws-cdk/ec2/lib/vpc.ts | 37 +++++++++++++---------- packages/@aws-cdk/ec2/test/test.vpc.ts | 2 +- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/network-util.ts b/packages/@aws-cdk/ec2/lib/network-util.ts index bf9ab1c4e9046..0e71623cf69b0 100644 --- a/packages/@aws-cdk/ec2/lib/network-util.ts +++ b/packages/@aws-cdk/ec2/lib/network-util.ts @@ -144,13 +144,9 @@ export class NetworkUtils { /** * - * takes a number (e.g 2923605416) and converts it to an IPv4 address string + * Takes a number (e.g 2923605416) and converts it to an IPv4 address string * currently only supports IPv4 * - * Uses the formula: - * (first octet * 256³) + (second octet * 256²) + (third octet * 256) + - * (fourth octet) - * * @param {number} the integer value of the IP address (e.g 2923605416) * @returns {string} the IPv4 address (e.g. 174.66.173.168) */ diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index 0622133c91485..93316ee2d7556 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -61,8 +61,7 @@ export interface VpcNetworkProps { maxAZs?: number; /** - * Define subnets that will be built per AZ (maxAZs), if numAZs is - * specified then only that number of AZs will have subnets. + * Configure the subnets to build for each AZ * * The subnets are constructed in the context of the VPC so you only need * specify the configuration. The VPC details (VPC ID, specific CIDR, @@ -86,7 +85,7 @@ export interface VpcNetworkProps { * @default the VPC CIDR will be evenly divided between 1 public and 1 * private subnet per AZ */ - subnets?: VpcSubnetBuilderProps[]; + subnetConfigurations?: SubnetConfiguration[]; } /** @@ -176,8 +175,6 @@ export class VpcNetwork extends VpcNetworkRef { */ public readonly internalSubnets: VpcSubnetRef[] = []; - public readonly natGatewayByAZ: Obj = {}; - /** * The VPC resource */ @@ -221,7 +218,7 @@ export class VpcNetwork extends VpcNetworkRef { outboundTraffic === OutboundTrafficMode.FromPublicAndPrivateSubnets; // Create public and private subnets in each AZ - this.createSubnets(cidrBlock, outboundTraffic, props.maxAZs, props.subnets); + this.createSubnets(cidrBlock, outboundTraffic, props.maxAZs, props.subnetConfigurations); // Create an Internet Gateway and attach it (if the outbound traffic mode != None) if (allowOutbound) { @@ -253,7 +250,7 @@ export class VpcNetwork extends VpcNetworkRef { cidr: string, outboundTraffic: OutboundTrafficMode, maxAZs?: number, - subnets?: VpcSubnetBuilderProps[]) { + subnets?: SubnetConfiguration[]) { // Calculate number of public/private subnets based on number of AZs const zones = new AvailabilityZoneProvider(this).availabilityZones; @@ -308,7 +305,9 @@ export class VpcNetwork extends VpcNetworkRef { this.privateSubnets.push(privateSubnet); this.dependencyElements.push(publicSubnet, privateSubnet); } - private createCustomSubnets(builder: NetworkBuilder, subnets: VpcSubnetBuilderProps[], zones: string[]) { + private createCustomSubnets(builder: NetworkBuilder, subnets: SubnetConfiguration[], zones: string[]) { + + const natGatewayByAZ: Obj = {}; for (const subnet of subnets) { let azs = zones; @@ -327,7 +326,7 @@ export class VpcNetwork extends VpcNetworkRef { cidrBlock: cidr }); if (subnet.natGateway) { - this.natGatewayByAZ[zone] = publicSubnet.addNatGateway(); + natGatewayByAZ[zone] = publicSubnet.addNatGateway(); } this.publicSubnets.push(publicSubnet); break; @@ -353,9 +352,9 @@ export class VpcNetwork extends VpcNetworkRef { } } (this.privateSubnets as VpcPrivateSubnet[]).forEach((privateSubnet, i) => { - let ngwId = this.natGatewayByAZ[privateSubnet.availabilityZone]; + let ngwId = natGatewayByAZ[privateSubnet.availabilityZone]; if (ngwId === undefined) { - const ngwArray = Array.from(Object.values(this.natGatewayByAZ)); + const ngwArray = Array.from(Object.values(natGatewayByAZ)); ngwId = ngwArray[i % ngwArray.length]; } privateSubnet.addDefaultNatRouteEntry(ngwId); @@ -370,18 +369,24 @@ export class VpcNetwork extends VpcNetworkRef { export enum SubnetType { /** - * Outbound traffic is not routed. This can be good for subnets with RDS or + * Internal Subnets do not route Outbound traffic + * + * This can be good for subnets with RDS or * Elasticache endpoints */ Internal = 1, /** - * Outbound traffic will be routed via an Internet Gateway. If this is set - * and OutboundTrafficMode.None is configure an error will be thrown. + * Public subnets route outbound traffic via an Internet Gateway + * + * If this is set and OutboundTrafficMode.None is configure an error + * will be thrown. */ Public = 2, /** + * Private subnets route outbound traffic via a NAT Gateway + * * Outbound traffic will be routed via a NAT Gateways preference being in * the same AZ, but if not available will use another AZ. This is common for * experimental cost conscious accounts or accounts where HA outbound @@ -393,7 +398,7 @@ export enum SubnetType { /** * Specify configuration parameters for a VPC to be built */ -export interface VpcSubnetBuilderProps { +export interface SubnetConfiguration { // the cidr mask value from 16-28 cidrMask: number; // Public (IGW), Private (Nat GW), Internal (no outbound) @@ -402,7 +407,7 @@ export interface VpcSubnetBuilderProps { name: string; // if true will place a NAT Gateway in this subnet, subnetType must be Public natGateway?: boolean; - // defaults to false if true will map public IPs on launch + // defaults to true in Subnet.Public, false in Subnet.Private or Subnet.Internal mapPublicIpOnLaunch?: boolean; // number of AZs to build this subnet in numAZs?: number; diff --git a/packages/@aws-cdk/ec2/test/test.vpc.ts b/packages/@aws-cdk/ec2/test/test.vpc.ts index b56143cf0e48f..ce1c1a980118b 100644 --- a/packages/@aws-cdk/ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/ec2/test/test.vpc.ts @@ -93,7 +93,7 @@ export = { new VpcNetwork(stack, 'TheVPC', { cidr: '10.0.0.0/21', outboundTraffic: OutboundTrafficMode.FromPublicAndPrivateSubnets, - subnets: [ + subnetConfigurations: [ { cidrMask: 24, name: 'ingress', From a2b527d1d103f89cb68bdac6c14780e1c263c73a Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Tue, 10 Jul 2018 13:25:50 -0700 Subject: [PATCH 06/35] removing custom error classes and asserting on regex message --- packages/@aws-cdk/ec2/lib/network-util.ts | 36 +++---------------- .../@aws-cdk/ec2/test/test.network-utils.ts | 17 +++++---- 2 files changed, 13 insertions(+), 40 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/network-util.ts b/packages/@aws-cdk/ec2/lib/network-util.ts index 0e71623cf69b0..a176fa1dd36a6 100644 --- a/packages/@aws-cdk/ec2/lib/network-util.ts +++ b/packages/@aws-cdk/ec2/lib/network-util.ts @@ -24,32 +24,6 @@ export class InvalidSubnetCountError extends Error { } } -/** - * InvalidSubnetCidrError is thrown when attempting to split a CIDR range into more - * subnets than it has IP space for. - */ -export class InvalidSubnetCidrError extends Error { - constructor(cidr: string, subnet: string) { - super('VPC range (' + cidr + ') does not contain ' + subnet); - // The following line is required for type checking of custom errors, and must be called right after super() - // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript - Object.setPrototypeOf(this, InvalidSubnetCidrError.prototype); - } -} - -/** - * InvalidIpAddressError is thrown when attempting to split a CIDR range into more - * subnets than it has IP space for. - */ -export class InvalidIpAddressError extends Error { - constructor(ip: string) { - super(ip + ' is not a valid IP must be (0-255).(0-255).(0-255).(0-255).'); - // The following line is required for type checking of custom errors, and must be called right after super() - // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript - Object.setPrototypeOf(this, InvalidIpAddressError.prototype); - } -} - /** * NetworkUtils contains helpers to work with network constructs (subnets/ranges) */ @@ -131,7 +105,7 @@ export class NetworkUtils { public static ipToNum(ipAddress: string): number { if (!this.validIp(ipAddress)) { - throw new InvalidIpAddressError(ipAddress); + throw new Error(`${ipAddress} is not valid`); } return ipAddress @@ -165,7 +139,7 @@ export class NetworkUtils { } const ipAddress: string = address.join('.'); if ( !this.validIp(ipAddress) ) { - throw new InvalidIpAddressError(ipAddress); + throw new Error(`${ipAddress} is not a valid IP Address`); } return ipAddress; } @@ -201,7 +175,7 @@ export class NetworkBuilder { this.maxIpConsumed = subnet.maxIp(); this.subnetCidrs.push(subnet); if (!this.validate()) { - throw new InvalidSubnetCidrError(this.networkCidr.cidr, subnet.cidr); + throw new Error(`${this.networkCidr.cidr} does not fully contain ${subnet.cidr}`); } subnets.push(subnet); } @@ -214,8 +188,8 @@ export class NetworkBuilder { public validate(): boolean { return this.subnetCidrs.map( - (cidrBlock) => this.networkCidr.containsCidr(cidrBlock)).reduce( - (containsAll: boolean, contains: boolean) => (containsAll && contains)); + (cidrBlock) => this.networkCidr.containsCidr(cidrBlock)). + filter( (contains) => (contains === false)).length === 0; } } diff --git a/packages/@aws-cdk/ec2/test/test.network-utils.ts b/packages/@aws-cdk/ec2/test/test.network-utils.ts index bc674daef19fc..bc14d2f09b483 100644 --- a/packages/@aws-cdk/ec2/test/test.network-utils.ts +++ b/packages/@aws-cdk/ec2/test/test.network-utils.ts @@ -2,8 +2,6 @@ import { Test } from 'nodeunit'; import { CidrBlock, InvalidCidrRangeError, - InvalidIpAddressError, - InvalidSubnetCidrError, InvalidSubnetCountError, NetworkBuilder, NetworkUtils @@ -51,7 +49,7 @@ export = { "should throw on invalid IP Address"(test: Test) { test.throws(() => { NetworkUtils.ipToNum('174.266.173.168'); - }, InvalidIpAddressError); + }, Error, 'is not valid'); test.done(); }, "should convert a valid IP integer to a staring"(test: Test) { @@ -61,10 +59,10 @@ export = { "should throw an error for invalid IP"(test: Test) { test.throws(() => { NetworkUtils.numToIp(2923605416 * 5); - }, InvalidIpAddressError); + }, /is not a valid/); test.throws(() => { NetworkUtils.numToIp(-1); - }, InvalidIpAddressError); + }, /is not a valid/); test.done(); }, }, @@ -162,14 +160,15 @@ export = { "throws if you add a subnet outside of the cidr"(test: Test) { const builder = new NetworkBuilder('192.168.0.0/18'); const builder2 = new NetworkBuilder('10.0.0.0/21'); - builder.addSubnets(19, 2); + builder.addSubnets(19, 1); builder2.addSubnets(24, 8); test.throws(() => { builder.addSubnet(19); - }, InvalidSubnetCidrError); + builder.addSubnet(28); + }, /does not fully contain/); test.throws(() => { - builder2.addSubnet(24); - }, InvalidSubnetCidrError); + builder2.addSubnet(28); + }, /does not fully contain/); test.done(); } } From c9d4f39199a7ad581f963cab8d75ab347b7053d8 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Tue, 10 Jul 2018 14:04:26 -0700 Subject: [PATCH 07/35] removing more bad uses of reduce --- packages/@aws-cdk/ec2/lib/network-util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/ec2/lib/network-util.ts b/packages/@aws-cdk/ec2/lib/network-util.ts index a176fa1dd36a6..91c5947a93b7e 100644 --- a/packages/@aws-cdk/ec2/lib/network-util.ts +++ b/packages/@aws-cdk/ec2/lib/network-util.ts @@ -234,6 +234,6 @@ export class CidrBlock { return [ NetworkUtils.ipToNum(this.minIp()) <= NetworkUtils.ipToNum(cidr.minIp()), NetworkUtils.ipToNum(this.maxIp()) >= NetworkUtils.ipToNum(cidr.maxIp()), - ].reduce((contained: boolean, value: boolean) => (contained && value), true); + ].filter((contains) => (contains === false)).length === 0; } } From af75c415ecafb0d72e9509a260fa28304f88a50b Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Tue, 10 Jul 2018 14:26:44 -0700 Subject: [PATCH 08/35] network-utils doc improvements --- packages/@aws-cdk/ec2/lib/network-util.ts | 71 ++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/ec2/lib/network-util.ts b/packages/@aws-cdk/ec2/lib/network-util.ts index 91c5947a93b7e..f4ae5dca82cbd 100644 --- a/packages/@aws-cdk/ec2/lib/network-util.ts +++ b/packages/@aws-cdk/ec2/lib/network-util.ts @@ -146,11 +146,33 @@ export class NetworkUtils { } +/** + * Creates a network based on a CIDR Block to build contained subnets + */ export class NetworkBuilder { + + /** + * the CIDR range used when creating the network + */ public readonly networkCidr: CidrBlock; + + /** + * the list of CIDR blocks for subnets within this network + */ protected subnetCidrs: CidrBlock[]; + + /** + * the current highest allocated IP address in any subnet + */ protected maxIpConsumed: string; + /** + * Create a network using the provided CIDR block + * + * No subnets are allocated in the constructor, the maxIpConsumed is set one + * less than the first IP in the network + * + */ constructor(cidr: string) { this.networkCidr = new CidrBlock(cidr); this.subnetCidrs = []; @@ -158,10 +180,16 @@ export class NetworkBuilder { ipToNum(this.networkCidr.minIp()) - 1); } + /** + * Add a subnet to the network and update the maxIpConsumed + */ public addSubnet(mask: number): string { return this.addSubnets(mask, 1)[0]; } + /** + * Add {count} number of subnets to the network and update the maxIpConsumed + */ public addSubnets(mask: number, count?: number): string[] { if (mask < 16 || mask > 28) { throw new InvalidCidrRangeError(`x.x.x.x/${mask}`); @@ -182,11 +210,14 @@ export class NetworkBuilder { return subnets.map((subnet) => (subnet.cidr)); } + /** + * return the CIDR notation strings for all subnets in the network + */ public get subnets(): string[] { return this.subnetCidrs.map((subnet) => (subnet.cidr)); } - public validate(): boolean { + protected validate(): boolean { return this.subnetCidrs.map( (cidrBlock) => this.networkCidr.containsCidr(cidrBlock)). filter( (contains) => (contains === false)).length === 0; @@ -194,33 +225,68 @@ export class NetworkBuilder { } +/** + * Creates a CIDR Block + */ export class CidrBlock { + /** + * Calculates the netmask for a given CIDR mask + * + * For example: + * CidrBlock.calculateNetmask(24) returns '255.255.255.0' + */ public static calculateNetmask(mask: number): string { return NetworkUtils.numToIp(2 ** 32 - 2 ** (32 - mask)); } + /** + * Given an IP and CIDR mask number returns the next CIDR Block available + * + * For example: + * CidrBlock.fromOffsetIp('10.0.0.15', 24) returns '10.0.1.0/24' + */ public static fromOffsetIp(ipAddress: string, mask: number): CidrBlock { const initialCidr = new CidrBlock(`${ipAddress}/${mask}`); const baseIpNum = NetworkUtils.ipToNum(initialCidr.maxIp()) + 1; return new this(`${NetworkUtils.numToIp(baseIpNum)}/${mask}`); } + /* + * The CIDR Block represented as a string e.g. '10.0.0.0/21' + */ public readonly cidr: string; + + /* + * The netmask for the CIDR represented as a string e.g. '255.255.255.0' + */ public readonly netmask: string; + + /* + * The CIDR mask e.g. for CIDR '10.0.0.0/21' returns 21 + */ public readonly mask: number; + /* + * Creates a new CidrBlock + */ constructor(cidr: string) { this.cidr = cidr; this.mask = parseInt(cidr.split('/')[1], 10); this.netmask = CidrBlock.calculateNetmask(this.mask); } + /* + * The maximum IP in the CIDR Blcok e.g. '10.0.8.255' + */ public maxIp(): string { const minIpNum = NetworkUtils.ipToNum(this.minIp()); return NetworkUtils.numToIp(minIpNum + 2 ** (32 - this.mask) - 1); } + /* + * The minimum IP in the CIDR Blcok e.g. '10.0.0.0' + */ public minIp(): string { const netmaskOct = this.netmask.split('.'); const ipOct = this.cidr.split('/')[0].split('.'); @@ -230,6 +296,9 @@ export class CidrBlock { // tslint:enable:no-bitwise } + /* + * Returns true if this CidrBlock fully contains the provided CidrBlock + */ public containsCidr(cidr: CidrBlock): boolean { return [ NetworkUtils.ipToNum(this.minIp()) <= NetworkUtils.ipToNum(cidr.minIp()), From 88a6d94523f6c55c42ebd84b2c11c1ba9a41a4a9 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Tue, 10 Jul 2018 23:36:24 -0700 Subject: [PATCH 09/35] refactor complete snapping a line to debug --- packages/@aws-cdk/ec2/lib/network-util.ts | 37 +- packages/@aws-cdk/ec2/lib/vpc.ts | 339 +++++++++--------- .../@aws-cdk/ec2/test/test.network-utils.ts | 27 ++ packages/@aws-cdk/ec2/test/test.vpc.ts | 10 +- 4 files changed, 225 insertions(+), 188 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/network-util.ts b/packages/@aws-cdk/ec2/lib/network-util.ts index f4ae5dca82cbd..6e946b85b804a 100644 --- a/packages/@aws-cdk/ec2/lib/network-util.ts +++ b/packages/@aws-cdk/ec2/lib/network-util.ts @@ -195,9 +195,7 @@ export class NetworkBuilder { throw new InvalidCidrRangeError(`x.x.x.x/${mask}`); } const subnets: CidrBlock[] = []; - if (!count) { - count = 1; - } + count = count || 1; for (let i = 0; i < count; i ++) { const subnet: CidrBlock = CidrBlock.fromOffsetIp(this.maxIpConsumed, mask); this.maxIpConsumed = subnet.maxIp(); @@ -217,6 +215,19 @@ export class NetworkBuilder { return this.subnetCidrs.map((subnet) => (subnet.cidr)); } + /** + * Calculates the largest subnet to create of the given count from the + * remaining IP space + */ + public maskForRemainingSubnets(subnetCount: number): number { + const remaining: number = + NetworkUtils.ipToNum(this.networkCidr.maxIp()) - + NetworkUtils.ipToNum(this.maxIpConsumed); + const ipsPerSubnet: number = Math.floor(remaining / subnetCount); + return 32 - Math.floor(Math.log2(ipsPerSubnet)); + // return this.addSubnets(mask, subnetCount); + } + protected validate(): boolean { return this.subnetCidrs.map( (cidrBlock) => this.networkCidr.containsCidr(cidrBlock)). @@ -231,21 +242,21 @@ export class NetworkBuilder { export class CidrBlock { /** - * Calculates the netmask for a given CIDR mask - * - * For example: - * CidrBlock.calculateNetmask(24) returns '255.255.255.0' - */ + * Calculates the netmask for a given CIDR mask + * + * For example: + * CidrBlock.calculateNetmask(24) returns '255.255.255.0' + */ public static calculateNetmask(mask: number): string { return NetworkUtils.numToIp(2 ** 32 - 2 ** (32 - mask)); } /** - * Given an IP and CIDR mask number returns the next CIDR Block available - * - * For example: - * CidrBlock.fromOffsetIp('10.0.0.15', 24) returns '10.0.1.0/24' - */ + * Given an IP and CIDR mask number returns the next CIDR Block available + * + * For example: + * CidrBlock.fromOffsetIp('10.0.0.15', 24) returns '10.0.1.0/24' + */ public static fromOffsetIp(ipAddress: string, mask: number): CidrBlock { const initialCidr = new CidrBlock(`${ipAddress}/${mask}`); const baseIpNum = NetworkUtils.ipToNum(initialCidr.maxIp()) + 1; diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index 93316ee2d7556..00a4b5b288a88 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -1,7 +1,7 @@ import { AvailabilityZoneProvider, Construct, Tag, Token } from '@aws-cdk/core'; import { ec2 } from '@aws-cdk/resources'; import { Obj } from '@aws-cdk/util'; -import { NetworkBuilder, NetworkUtils } from './network-util'; +import { NetworkBuilder } from './network-util'; import { VpcNetworkId, VpcNetworkRef, VpcSubnetId, VpcSubnetRef } from './vpc-ref'; /** * VpcNetworkProps allows you to specify configuration options for a VPC @@ -44,12 +44,6 @@ export interface VpcNetworkProps { */ tags?: Tag[]; - /** - * Defines whether the VPC is configured to route outbound traffic from private and/or public subnets. - * By default, outbound traffic will be allowed from public and private subnets. - */ - outboundTraffic?: OutboundTrafficMode; - /** * Define the maximum number of AZs to use in this region * @@ -104,26 +98,68 @@ export enum DefaultInstanceTenancy { } /** - * The outbound traffic mode defines whether the VPC is configured to route outbound traffic. + * The type of Subnet */ -export enum OutboundTrafficMode { +export enum SubnetType { /** - * Outbound traffic is not routed. No Internet Gateway (IGW) will be deployed, and no NAT Gateways will be deployed. + * Internal Subnets do not route Outbound traffic + * + * This can be good for subnets with RDS or + * Elasticache endpoints */ - None = 1, + Internal = 1, /** - * Outbound traffic will be routed from public subnets via an Internet Gateway. - * Outbound traffic from private subnets will not be routed. + * Public subnets route outbound traffic via an Internet Gateway + * + * If this is set and OutboundTrafficMode.None is configure an error + * will be thrown. */ - FromPublicSubnetsOnly = 2, + Public = 2, /** - * Outbound traffic from public subnets will be routed via an Internet Gateway. - * Outbound traffic from private subnets will be routed via a set of NAT Gateways (1 per AZ). + * Private subnets route outbound traffic via a NAT Gateway + * + * Outbound traffic will be routed via a NAT Gateways preference being in + * the same AZ, but if not available will use another AZ. This is common for + * experimental cost conscious accounts or accounts where HA outbound + * traffic is not needed. */ - FromPublicAndPrivateSubnets = 3 + Private = 3 +} + +/** + * Specify configuration parameters for a VPC to be built + */ +export interface SubnetConfiguration { + // the cidr mask value from 16-28 + cidrMask?: number; + // Public (IGW), Private (Nat GW), Internal (no outbound) + subnetType: SubnetType; + // name that will be used to generate an AZ specific name e.g. name-2a + name: string; + // if true will place a NAT Gateway in this subnet, subnetType must be Public + natGateway?: boolean; + // defaults to true in Subnet.Public, false in Subnet.Private or Subnet.Internal + mapPublicIpOnLaunch?: boolean; + // number of AZs to build this subnet in + numAZs?: number; +} + +interface SubnetConfigurationFinalized { + // the cidr mask value from 16-28 + cidrMask: number; + // Public (IGW), Private (Nat GW), Internal (no outbound) + subnetType: SubnetType; + // name that will be used to generate an AZ specific name e.g. name-2a + name: string; + // if true will place a NAT Gateway in this subnet, subnetType must be Public + natGateway: boolean; + // defaults to true in Subnet.Public, false in Subnet.Private or Subnet.Internal + mapPublicIpOnLaunch: boolean; + // availabity zones to buid this subnet in + availabilityZones: string[]; } /** @@ -155,6 +191,24 @@ export class VpcNetwork extends VpcNetworkRef { */ public static readonly DEFAULT_CIDR_RANGE = '10.0.0.0/16'; + /** + * The deafult subnet configuration + * + * 1 Public and 1 Private subnet per AZ evenly split + */ + public static readonly DEFAULT_SUBNETS: SubnetConfiguration[] = [ + { + subnetType: SubnetType.Public, + name: 'Public', + natGateway: true + }, + { + subnetType: SubnetType.Private, + name: 'Private', + natGateway: false + } + ]; + /** * Identifier for this VPC */ @@ -180,6 +234,21 @@ export class VpcNetwork extends VpcNetworkRef { */ private resource: ec2.VPCResource; + /** + * The NetworkBuilder + */ + private networkBuilder: NetworkBuilder; + + /** + * Mapping of NatGateway by AZ + */ + private natGatewayByAZ: Obj = {}; + + /** + * Subnet configurations for this VPC + */ + private subnetConfigurations: SubnetConfiguration[] = []; + /** * VpcNetwork creates a VPC that spans a whole region. * It will automatically divide the provided VPC CIDR range, and create public and private subnets per Availability Zone. @@ -195,11 +264,12 @@ export class VpcNetwork extends VpcNetworkRef { } const cidrBlock = props.cidr || VpcNetwork.DEFAULT_CIDR_RANGE; + this.networkBuilder = new NetworkBuilder(cidrBlock); + const enableDnsHostnames = props.enableDnsHostnames == null ? true : props.enableDnsHostnames; const enableDnsSupport = props.enableDnsSupport == null ? true : props.enableDnsSupport; const instanceTenancy = props.defaultInstanceTenancy || 'default'; const tags = props.tags || []; - const outboundTraffic = props.outboundTraffic || OutboundTrafficMode.FromPublicAndPrivateSubnets; // Define a VPC using the provided CIDR range this.resource = new ec2.VPCResource(this, 'Resource', { @@ -212,13 +282,13 @@ export class VpcNetwork extends VpcNetworkRef { this.vpcId = this.resource.ref; this.dependencyElements.push(this.resource); + this.subnetConfigurations = props.subnetConfigurations || VpcNetwork.DEFAULT_SUBNETS; - const allowOutbound = - outboundTraffic === OutboundTrafficMode.FromPublicSubnetsOnly || - outboundTraffic === OutboundTrafficMode.FromPublicAndPrivateSubnets; + const allowOutbound = this.subnetConfigurations.filter( + (subnet) => (subnet.subnetType != SubnetType.Internal)).length > 0; // Create public and private subnets in each AZ - this.createSubnets(cidrBlock, outboundTraffic, props.maxAZs, props.subnetConfigurations); + this.createSubnets(props.maxAZs); // Create an Internet Gateway and attach it (if the outbound traffic mode != None) if (allowOutbound) { @@ -246,171 +316,102 @@ export class VpcNetwork extends VpcNetworkRef { * createSubnets takes a VPC, and creates a public and private subnet * in each Availability Zone. */ - private createSubnets( - cidr: string, - outboundTraffic: OutboundTrafficMode, - maxAZs?: number, - subnets?: SubnetConfiguration[]) { + private createSubnets(maxAZs?: number) { + let remainingSpaceSubnets: SubnetConfigurationFinalized[] = []; // Calculate number of public/private subnets based on number of AZs - const zones = new AvailabilityZoneProvider(this).availabilityZones; - zones.sort(); + const availabilityZones = new AvailabilityZoneProvider(this).availabilityZones; + availabilityZones.sort(); // Restrict to maxAZs if given if (maxAZs != null) { - zones.splice(maxAZs); + availabilityZones.splice(maxAZs); } - if (subnets != null) { - const networkBuilder = new NetworkBuilder(cidr); - this.createCustomSubnets(networkBuilder, subnets, zones); - } else { - // Split the CIDR range into each availablity zone - const ranges = NetworkUtils.splitCIDR(cidr, zones.length); + // TODO validate numAZs < maxAZz - for (let i = 0; i < zones.length; i++) { - this.createSubnetPair(ranges[i], zones[i], i + 1, outboundTraffic); - } - } - } + for (const subnet of this.subnetConfigurations) { + let subnetFinal: any = {}; + subnetFinal.availabilityZones = availabilityZones; - /** - * Creates a public and private subnet, as well as the needed nat gateway and default route, if necessary. - */ - private createSubnetPair(azCidr: string, zone: string, index: number, outboundTraffic: OutboundTrafficMode) { - // Split the CIDR range for this AZ into two (public and private) - const subnetRanges = NetworkUtils.splitCIDR(azCidr, 2); - const publicSubnet = new VpcPublicSubnet(this, `PublicSubnet${index}`, { - mapPublicIpOnLaunch: true, - vpcId: this.vpcId, - availabilityZone: zone, - cidrBlock: subnetRanges[0] - }); - const privateSubnet = new VpcPrivateSubnet(this, `PrivateSubnet${index}`, { - mapPublicIpOnLaunch: false, - vpcId: this.vpcId, - availabilityZone: zone, - cidrBlock: subnetRanges[1] - }); - - // If outbound traffic from private subnets is configured, also configure NAT Gateways - // in each public subnet, and configure the default route for the private subnet via them. - if (outboundTraffic === OutboundTrafficMode.FromPublicAndPrivateSubnets) { - const ngwId = publicSubnet.addNatGateway(); - privateSubnet.addDefaultNatRouteEntry(ngwId); - } - - this.publicSubnets.push(publicSubnet); - this.privateSubnets.push(privateSubnet); - this.dependencyElements.push(publicSubnet, privateSubnet); - } - private createCustomSubnets(builder: NetworkBuilder, subnets: SubnetConfiguration[], zones: string[]) { + if (subnet.numAZs != null) { + subnetFinal.availabilityZones = availabilityZones.slice(subnet.numAZs); + } - const natGatewayByAZ: Obj = {}; + subnetFinal.cidrMask = subnet.cidrMask || 0; + subnetFinal.name = subnet.name; + subnetFinal.natGateway = subnet.natGateway || false; + subnetFinal.mapPublicIpOnLaunch = subnet.mapPublicIpOnLaunch || + (subnet.subnetType === SubnetType.Public); - for (const subnet of subnets) { - let azs = zones; - if (subnet.numAZs != null) { - azs = zones.slice(subnet.numAZs); - } - for (const zone of azs) { - const cidr: string = builder.addSubnet(subnet.cidrMask); - const name: string = `${subnet.name}AZ${zone.substr(-1)}`; - switch (subnet.subnetType) { - case SubnetType.Public: - const publicSubnet = new VpcPublicSubnet(this, name, { - mapPublicIpOnLaunch: subnet.mapPublicIpOnLaunch || true, - vpcId: this.vpcId, - availabilityZone: zone, - cidrBlock: cidr - }); - if (subnet.natGateway) { - natGatewayByAZ[zone] = publicSubnet.addNatGateway(); - } - this.publicSubnets.push(publicSubnet); - break; - case SubnetType.Private: - const privateSubnet = new VpcPrivateSubnet(this, name, { - mapPublicIpOnLaunch: subnet.mapPublicIpOnLaunch || false, - vpcId: this.vpcId, - availabilityZone: zone, - cidrBlock: cidr - }); - this.privateSubnets.push(privateSubnet); - break; - case SubnetType.Internal: - const internalSubnet = new VpcPrivateSubnet(this, name, { - mapPublicIpOnLaunch: subnet.mapPublicIpOnLaunch || false, - vpcId: this.vpcId, - availabilityZone: zone, - cidrBlock: cidr - }); - this.internalSubnets.push(internalSubnet); - break; - } + if (subnetFinal.cidrMask === 0) { + remainingSpaceSubnets.push(subnetFinal); + continue; + } + this.createSubnetResources(subnetFinal); } - } - (this.privateSubnets as VpcPrivateSubnet[]).forEach((privateSubnet, i) => { - let ngwId = natGatewayByAZ[privateSubnet.availabilityZone]; - if (ngwId === undefined) { - const ngwArray = Array.from(Object.values(natGatewayByAZ)); - ngwId = ngwArray[i % ngwArray.length]; - } - privateSubnet.addDefaultNatRouteEntry(ngwId); - }); - } -} - -/** - * The type of Subnet - */ -export enum SubnetType { + console.log(`Remaining subnets ${remainingSpaceSubnets.length}`); + const cidrMaskForRemaing = this.networkBuilder. + maskForRemainingSubnets(remainingSpaceSubnets.length); - /** - * Internal Subnets do not route Outbound traffic - * - * This can be good for subnets with RDS or - * Elasticache endpoints - */ - Internal = 1, + remainingSpaceSubnets = remainingSpaceSubnets.map( + (subnet) => { subnet.cidrMask = cidrMaskForRemaing; return subnet;}); - /** - * Public subnets route outbound traffic via an Internet Gateway - * - * If this is set and OutboundTrafficMode.None is configure an error - * will be thrown. - */ - Public = 2, + for (const subnetFinal of remainingSpaceSubnets) { + console.log('creating subnet for remaining') + this.createSubnetResources(subnetFinal); + } - /** - * Private subnets route outbound traffic via a NAT Gateway - * - * Outbound traffic will be routed via a NAT Gateways preference being in - * the same AZ, but if not available will use another AZ. This is common for - * experimental cost conscious accounts or accounts where HA outbound - * traffic is not needed. - */ - Private = 3 -} + (this.privateSubnets as VpcPrivateSubnet[]).forEach((privateSubnet, i) => { + let ngwId = this.natGatewayByAZ[privateSubnet.availabilityZone]; + if (ngwId === undefined) { + const ngwArray = Array.from(Object.values(this.natGatewayByAZ)); + ngwId = ngwArray[i % ngwArray.length]; + } + privateSubnet.addDefaultNatRouteEntry(ngwId); + }); + } -/** - * Specify configuration parameters for a VPC to be built - */ -export interface SubnetConfiguration { - // the cidr mask value from 16-28 - cidrMask: number; - // Public (IGW), Private (Nat GW), Internal (no outbound) - subnetType: SubnetType; - // name that will be used to generate an AZ specific name e.g. name-2a - name: string; - // if true will place a NAT Gateway in this subnet, subnetType must be Public - natGateway?: boolean; - // defaults to true in Subnet.Public, false in Subnet.Private or Subnet.Internal - mapPublicIpOnLaunch?: boolean; - // number of AZs to build this subnet in - numAZs?: number; + private createSubnetResources(subnetConfig: SubnetConfigurationFinalized) { + for (const zone of subnetConfig.availabilityZones) { + const cidr: string = this.networkBuilder.addSubnet(subnetConfig.cidrMask); + const name: string = `${subnetConfig.name}AZ${zone.substr(-1)}`; + switch (subnetConfig.subnetType) { + case SubnetType.Public: + const publicSubnet = new VpcPublicSubnet(this, name, { + mapPublicIpOnLaunch: subnetConfig.mapPublicIpOnLaunch || true, + vpcId: this.vpcId, + availabilityZone: zone, + cidrBlock: cidr + }); + if (subnetConfig.natGateway) { + this.natGatewayByAZ[zone] = publicSubnet.addNatGateway(); + } + this.publicSubnets.push(publicSubnet); + break; + case SubnetType.Private: + const privateSubnet = new VpcPrivateSubnet(this, name, { + mapPublicIpOnLaunch: subnetConfig.mapPublicIpOnLaunch || false, + vpcId: this.vpcId, + availabilityZone: zone, + cidrBlock: cidr + }); + this.privateSubnets.push(privateSubnet); + break; + case SubnetType.Internal: + const internalSubnet = new VpcPrivateSubnet(this, name, { + mapPublicIpOnLaunch: subnetConfig.mapPublicIpOnLaunch || false, + vpcId: this.vpcId, + availabilityZone: zone, + cidrBlock: cidr + }); + this.internalSubnets.push(internalSubnet); + break; + } + } + } } /** diff --git a/packages/@aws-cdk/ec2/test/test.network-utils.ts b/packages/@aws-cdk/ec2/test/test.network-utils.ts index bc14d2f09b483..8aa1d4d42c193 100644 --- a/packages/@aws-cdk/ec2/test/test.network-utils.ts +++ b/packages/@aws-cdk/ec2/test/test.network-utils.ts @@ -170,6 +170,33 @@ export = { builder2.addSubnet(28); }, /does not fully contain/); test.done(); + }, + "maskForRemainingSubnets calcs mask for even split of remaining"(test: Test) { + const builder = new NetworkBuilder('10.0.0.0/24'); + builder.addSubnet(25); + test.strictEqual(27, builder.maskForRemainingSubnets(3)); + const builder2 = new NetworkBuilder('192.168.176.0/20'); + builder2.addSubnets(22, 2); + test.strictEqual(22, builder2.maskForRemainingSubnets(2)); + test.done(); } + // "allocateRemaining splits remaining evenly"(test: Test) { + // const builder = new NetworkBuilder('10.0.0.0/24'); + // builder.addSubnet(25); + // const answers = ['10.0.0.128/27', '10.0.0.160/27', '10.0.0.192/27'].sort(); + // test.deepEqual(answers, builder.allocateRemaining(3).sort()); + // const builder2 = new NetworkBuilder('192.168.176.0/20'); + // builder2.addSubnets(22, 2); + // const answers2 = ['192.168.184.0/22', '192.168.188.0/22'].sort(); + // test.deepEqual(answers2, builder2.allocateRemaining(2).sort()); + // test.done(); + // }, + // "allocateRemaining throws an error if no valid split is possible"(test: Test) { + // const builder = new NetworkBuilder('172.0.0.0/27'); + // test.throws(() => { + // builder.allocateRemaining(3); + // }, InvalidCidrRangeError); + // test.done(); + // } } }; diff --git a/packages/@aws-cdk/ec2/test/test.vpc.ts b/packages/@aws-cdk/ec2/test/test.vpc.ts index ce1c1a980118b..b116b62d86e3c 100644 --- a/packages/@aws-cdk/ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/ec2/test/test.vpc.ts @@ -1,7 +1,7 @@ import { countResources, expect, haveResource } from '@aws-cdk/assert'; import { AvailabilityZoneProvider, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; -import { DefaultInstanceTenancy, OutboundTrafficMode, SubnetType, VpcNetwork } from '../lib'; +import { DefaultInstanceTenancy, SubnetType, VpcNetwork } from '../lib'; export = { @@ -38,7 +38,6 @@ export = { enableDnsHostnames: false, enableDnsSupport: false, defaultInstanceTenancy: DefaultInstanceTenancy.Dedicated, - outboundTraffic: OutboundTrafficMode.None, tags: [tag] }); @@ -64,7 +63,7 @@ export = { "with outbound traffic mode None, the VPC should not contain an IGW or NAT Gateways"(test: Test) { const stack = getTestStack(); - new VpcNetwork(stack, 'TheVPC', { outboundTraffic: OutboundTrafficMode.None }); + new VpcNetwork(stack, 'TheVPC', { }); expect(stack).notTo(haveResource("AWS::EC2::InternetGateway")); expect(stack).notTo(haveResource("AWS::EC2::NatGateway")); test.done(); @@ -72,7 +71,7 @@ export = { "with outbound traffic mode FromPublicSubnetsOnly, the VPC should have an IGW but no NAT Gateways"(test: Test) { const stack = getTestStack(); - new VpcNetwork(stack, 'TheVPC', { outboundTraffic: OutboundTrafficMode.FromPublicSubnetsOnly }); + new VpcNetwork(stack, 'TheVPC', { }); expect(stack).to(countResources('AWS::EC2::InternetGateway', 1)); expect(stack).notTo(haveResource("AWS::EC2::NatGateway")); test.done(); @@ -81,7 +80,7 @@ export = { "with outbound traffic mode FromPublicAndPrivateSubnets, the VPC should have an IGW, and a NAT Gateway per AZ"(test: Test) { const stack = getTestStack(); const zones = new AvailabilityZoneProvider(stack).availabilityZones.length; - new VpcNetwork(stack, 'TheVPC', { outboundTraffic: OutboundTrafficMode.FromPublicAndPrivateSubnets }); + new VpcNetwork(stack, 'TheVPC', { }); expect(stack).to(countResources("AWS::EC2::InternetGateway", 1)); expect(stack).to(countResources("AWS::EC2::NatGateway", zones)); test.done(); @@ -92,7 +91,6 @@ export = { const zones = new AvailabilityZoneProvider(stack).availabilityZones.length; new VpcNetwork(stack, 'TheVPC', { cidr: '10.0.0.0/21', - outboundTraffic: OutboundTrafficMode.FromPublicAndPrivateSubnets, subnetConfigurations: [ { cidrMask: 24, From 9ac38f4de53872c997a408de92b5d80e5bc1dfb9 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Wed, 11 Jul 2018 00:38:22 -0700 Subject: [PATCH 10/35] adding error for too many AZs requested --- packages/@aws-cdk/ec2/lib/vpc.ts | 36 ++++---- .../@aws-cdk/ec2/test/test.network-utils.ts | 20 +--- packages/@aws-cdk/ec2/test/test.vpc.ts | 91 ++++++++++++++----- 3 files changed, 88 insertions(+), 59 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index 00a4b5b288a88..d8997700c3485 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -328,23 +328,28 @@ export class VpcNetwork extends VpcNetworkRef { availabilityZones.splice(maxAZs); } - // TODO validate numAZs < maxAZz - - for (const subnet of this.subnetConfigurations) { - let subnetFinal: any = {}; - subnetFinal.availabilityZones = availabilityZones; + let azs = availabilityZones; if (subnet.numAZs != null) { - subnetFinal.availabilityZones = availabilityZones.slice(subnet.numAZs); + if (subnet.numAZs > azs.length) { + throw new Error(`${subnet.name} requires ${subnet.numAZs} AZs but max is ${azs.length}`) + } + azs = availabilityZones.slice(subnet.numAZs); } - subnetFinal.cidrMask = subnet.cidrMask || 0; - subnetFinal.name = subnet.name; - subnetFinal.natGateway = subnet.natGateway || false; - subnetFinal.mapPublicIpOnLaunch = subnet.mapPublicIpOnLaunch || + subnet.mapPublicIpOnLaunch = subnet.mapPublicIpOnLaunch || (subnet.subnetType === SubnetType.Public); + const subnetFinal: SubnetConfigurationFinalized = { + cidrMask: subnet.cidrMask || 0, + availabilityZones: azs, + subnetType: subnet.subnetType, + name: subnet.name, + natGateway: subnet.natGateway || false, + mapPublicIpOnLaunch: subnet.mapPublicIpOnLaunch + }; + if (subnetFinal.cidrMask === 0) { remainingSpaceSubnets.push(subnetFinal); continue; @@ -352,15 +357,14 @@ export class VpcNetwork extends VpcNetworkRef { this.createSubnetResources(subnetFinal); } - console.log(`Remaining subnets ${remainingSpaceSubnets.length}`); - const cidrMaskForRemaing = this.networkBuilder. - maskForRemainingSubnets(remainingSpaceSubnets.length); + const totalRemaining = remainingSpaceSubnets.reduce( (total: number, subnet) => { + return total += subnet.availabilityZones.length; + }, 0); - remainingSpaceSubnets = remainingSpaceSubnets.map( - (subnet) => { subnet.cidrMask = cidrMaskForRemaing; return subnet;}); + const cidrMaskForRemaing = this.networkBuilder.maskForRemainingSubnets(totalRemaining); for (const subnetFinal of remainingSpaceSubnets) { - console.log('creating subnet for remaining') + subnetFinal.cidrMask = cidrMaskForRemaing; this.createSubnetResources(subnetFinal); } diff --git a/packages/@aws-cdk/ec2/test/test.network-utils.ts b/packages/@aws-cdk/ec2/test/test.network-utils.ts index 8aa1d4d42c193..f11c4378a6785 100644 --- a/packages/@aws-cdk/ec2/test/test.network-utils.ts +++ b/packages/@aws-cdk/ec2/test/test.network-utils.ts @@ -178,25 +178,9 @@ export = { const builder2 = new NetworkBuilder('192.168.176.0/20'); builder2.addSubnets(22, 2); test.strictEqual(22, builder2.maskForRemainingSubnets(2)); + const builder3 = new NetworkBuilder('192.168.0.0/16'); + test.strictEqual(17, builder3.maskForRemainingSubnets(2)); test.done(); } - // "allocateRemaining splits remaining evenly"(test: Test) { - // const builder = new NetworkBuilder('10.0.0.0/24'); - // builder.addSubnet(25); - // const answers = ['10.0.0.128/27', '10.0.0.160/27', '10.0.0.192/27'].sort(); - // test.deepEqual(answers, builder.allocateRemaining(3).sort()); - // const builder2 = new NetworkBuilder('192.168.176.0/20'); - // builder2.addSubnets(22, 2); - // const answers2 = ['192.168.184.0/22', '192.168.188.0/22'].sort(); - // test.deepEqual(answers2, builder2.allocateRemaining(2).sort()); - // test.done(); - // }, - // "allocateRemaining throws an error if no valid split is possible"(test: Test) { - // const builder = new NetworkBuilder('172.0.0.0/27'); - // test.throws(() => { - // builder.allocateRemaining(3); - // }, InvalidCidrRangeError); - // test.done(); - // } } }; diff --git a/packages/@aws-cdk/ec2/test/test.vpc.ts b/packages/@aws-cdk/ec2/test/test.vpc.ts index b116b62d86e3c..8af9dbfdaa755 100644 --- a/packages/@aws-cdk/ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/ec2/test/test.vpc.ts @@ -5,26 +5,28 @@ import { DefaultInstanceTenancy, SubnetType, VpcNetwork } from '../lib'; export = { - "When creating a VPC with the default CIDR range": { - - "vpc.vpcId returns a token to the VPC ID"(test: Test) { - const stack = getTestStack(); - const vpc = new VpcNetwork(stack, 'TheVPC'); - test.deepEqual(vpc.vpcId.resolve(), {Ref: 'TheVPC92636AB0' } ); - test.done(); - }, - - "it uses the correct network range"(test: Test) { - const stack = getTestStack(); - new VpcNetwork(stack, 'TheVPC'); - expect(stack).to(haveResource('AWS::EC2::VPC', { - CidrBlock: VpcNetwork.DEFAULT_CIDR_RANGE, - EnableDnsHostnames: true, - EnableDnsSupport: true, - InstanceTenancy: DefaultInstanceTenancy.Default, - Tags: [] - })); - test.done(); + "When creating a VPC": { + "with the default CIDR range": { + + "vpc.vpcId returns a token to the VPC ID"(test: Test) { + const stack = getTestStack(); + const vpc = new VpcNetwork(stack, 'TheVPC'); + test.deepEqual(vpc.vpcId.resolve(), {Ref: 'TheVPC92636AB0' } ); + test.done(); + }, + + "it uses the correct network range"(test: Test) { + const stack = getTestStack(); + new VpcNetwork(stack, 'TheVPC'); + expect(stack).to(haveResource('AWS::EC2::VPC', { + CidrBlock: VpcNetwork.DEFAULT_CIDR_RANGE, + EnableDnsHostnames: true, + EnableDnsSupport: true, + InstanceTenancy: DefaultInstanceTenancy.Default, + Tags: [] + })); + test.done(); + } }, "with all of the properties set, it successfully sets the correct VPC properties"(test: Test) { @@ -61,23 +63,41 @@ export = { test.done(); }, - "with outbound traffic mode None, the VPC should not contain an IGW or NAT Gateways"(test: Test) { + "with only internal subnets, the VPC should not contain an IGW or NAT Gateways"(test: Test) { const stack = getTestStack(); - new VpcNetwork(stack, 'TheVPC', { }); + new VpcNetwork(stack, 'TheVPC', { + subnetConfigurations: [ + { + subnetType: SubnetType.Internal, + name: 'Internal', + } + ] + }); expect(stack).notTo(haveResource("AWS::EC2::InternetGateway")); expect(stack).notTo(haveResource("AWS::EC2::NatGateway")); test.done(); }, - "with outbound traffic mode FromPublicSubnetsOnly, the VPC should have an IGW but no NAT Gateways"(test: Test) { + "with no private subnets, the VPC should have an IGW but no NAT Gateways"(test: Test) { const stack = getTestStack(); - new VpcNetwork(stack, 'TheVPC', { }); + new VpcNetwork(stack, 'TheVPC', { + subnetConfigurations: [ + { + subnetType: SubnetType.Public, + name: 'Public', + }, + { + subnetType: SubnetType.Internal, + name: 'Internal', + } + ] + }); expect(stack).to(countResources('AWS::EC2::InternetGateway', 1)); expect(stack).notTo(haveResource("AWS::EC2::NatGateway")); test.done(); }, - "with outbound traffic mode FromPublicAndPrivateSubnets, the VPC should have an IGW, and a NAT Gateway per AZ"(test: Test) { + "with no subnets defined, the VPC should have an IGW, and a NAT Gateway per AZ"(test: Test) { const stack = getTestStack(); const zones = new AvailabilityZoneProvider(stack).availabilityZones.length; new VpcNetwork(stack, 'TheVPC', { }); @@ -147,6 +167,27 @@ export = { test.done(); } + }, + + "when you specify more AZs in a subnet than are possible an error is raised"(test: Test) { + const stack = getTestStack(); + test.throws(() => { + new VpcNetwork(stack, 'TheVPC', { + subnetConfigurations: [ + { + subnetType: SubnetType.Public, + name: 'Public', + }, + { + subnetType: SubnetType.Internal, + name: 'Internal', + numAZs: 5, + } + ] + }); + }); + test.done(); + } }; From 60ef32d35623fa616de10a48287d777c45b41c8b Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Wed, 11 Jul 2018 01:19:59 -0700 Subject: [PATCH 11/35] additional comments for CIDR math and odd behaviors --- packages/@aws-cdk/ec2/lib/network-util.ts | 3 +++ packages/@aws-cdk/ec2/lib/vpc.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/packages/@aws-cdk/ec2/lib/network-util.ts b/packages/@aws-cdk/ec2/lib/network-util.ts index 6e946b85b804a..39953452d22af 100644 --- a/packages/@aws-cdk/ec2/lib/network-util.ts +++ b/packages/@aws-cdk/ec2/lib/network-util.ts @@ -292,6 +292,7 @@ export class CidrBlock { */ public maxIp(): string { const minIpNum = NetworkUtils.ipToNum(this.minIp()); + // min + (2^(32-mask)) - 1 [zero needs to count] return NetworkUtils.numToIp(minIpNum + 2 ** (32 - this.mask) - 1); } @@ -303,6 +304,8 @@ export class CidrBlock { const ipOct = this.cidr.split('/')[0].split('.'); // tslint:disable:no-bitwise return netmaskOct.map( + // bitwise mask each octet to ensure correct min + // (e.g. 10.0.0.2/24 -> (10 & 255).(0 & 255).(0 & 255).(2 & 0) = 10.0.0.0 (maskOct, index) => parseInt(maskOct, 10) & parseInt(ipOct[index], 10)).join('.'); // tslint:enable:no-bitwise } diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index d8997700c3485..9225866eb3329 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -372,6 +372,7 @@ export class VpcNetwork extends VpcNetworkRef { let ngwId = this.natGatewayByAZ[privateSubnet.availabilityZone]; if (ngwId === undefined) { const ngwArray = Array.from(Object.values(this.natGatewayByAZ)); + // round robin the available NatGW since one is not in your AZ ngwId = ngwArray[i % ngwArray.length]; } privateSubnet.addDefaultNatRouteEntry(ngwId); From 0f0079a05245059479103ffced05d6258f924365 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Wed, 11 Jul 2018 10:08:07 -0700 Subject: [PATCH 12/35] integ test can run but we are breaking existing here --- packages/@aws-cdk/ec2/lib/vpc.ts | 16 ++++++++-------- packages/@aws-cdk/ec2/test/integ.vpc.ts | 6 ++---- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index 9225866eb3329..2ba2effe0ae7b 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -285,7 +285,7 @@ export class VpcNetwork extends VpcNetworkRef { this.subnetConfigurations = props.subnetConfigurations || VpcNetwork.DEFAULT_SUBNETS; const allowOutbound = this.subnetConfigurations.filter( - (subnet) => (subnet.subnetType != SubnetType.Internal)).length > 0; + (subnet) => (subnet.subnetType !== SubnetType.Internal)).length > 0; // Create public and private subnets in each AZ this.createSubnets(props.maxAZs); @@ -317,7 +317,7 @@ export class VpcNetwork extends VpcNetworkRef { * in each Availability Zone. */ private createSubnets(maxAZs?: number) { - let remainingSpaceSubnets: SubnetConfigurationFinalized[] = []; + const remainingSpaceSubnets: SubnetConfigurationFinalized[] = []; // Calculate number of public/private subnets based on number of AZs const availabilityZones = new AvailabilityZoneProvider(this).availabilityZones; @@ -332,9 +332,9 @@ export class VpcNetwork extends VpcNetworkRef { let azs = availabilityZones; if (subnet.numAZs != null) { - if (subnet.numAZs > azs.length) { - throw new Error(`${subnet.name} requires ${subnet.numAZs} AZs but max is ${azs.length}`) - } + if (subnet.numAZs > azs.length) { + throw new Error(`${subnet.name} requires ${subnet.numAZs} AZs but max is ${azs.length}`); + } azs = availabilityZones.slice(subnet.numAZs); } @@ -380,9 +380,9 @@ export class VpcNetwork extends VpcNetworkRef { } private createSubnetResources(subnetConfig: SubnetConfigurationFinalized) { - for (const zone of subnetConfig.availabilityZones) { + subnetConfig.availabilityZones.forEach((zone, index) => { const cidr: string = this.networkBuilder.addSubnet(subnetConfig.cidrMask); - const name: string = `${subnetConfig.name}AZ${zone.substr(-1)}`; + const name: string = `${subnetConfig.name}Subnet${index + 1}`; switch (subnetConfig.subnetType) { case SubnetType.Public: const publicSubnet = new VpcPublicSubnet(this, name, { @@ -415,7 +415,7 @@ export class VpcNetwork extends VpcNetworkRef { this.internalSubnets.push(internalSubnet); break; } - } + }); } } diff --git a/packages/@aws-cdk/ec2/test/integ.vpc.ts b/packages/@aws-cdk/ec2/test/integ.vpc.ts index 59c44830cb55f..99aed25570860 100644 --- a/packages/@aws-cdk/ec2/test/integ.vpc.ts +++ b/packages/@aws-cdk/ec2/test/integ.vpc.ts @@ -1,12 +1,10 @@ import { App, Stack } from '@aws-cdk/core'; -import { OutboundTrafficMode, VpcNetwork } from '../lib'; +import { VpcNetwork } from '../lib'; const app = new App(process.argv); const stack = new Stack(app, 'aws-cdk-ec2-vpc'); -new VpcNetwork(stack, 'MyVpc', { - outboundTraffic: OutboundTrafficMode.FromPublicAndPrivateSubnets -}); +new VpcNetwork(stack, 'MyVpc'); process.stdout.write(app.run()); From abd50867335af8e309d45c511e052475c4c43aa0 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Wed, 11 Jul 2018 14:59:17 -0700 Subject: [PATCH 13/35] making default vpc backwards compatible --- packages/@aws-cdk/ec2/lib/vpc.ts | 64 +++++++++++++++---- .../ec2/test/integ.everything.expected.json | 36 ----------- .../@aws-cdk/ec2/test/integ.vpc.expected.json | 36 ----------- 3 files changed, 50 insertions(+), 86 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index 2ba2effe0ae7b..7c64344820ec1 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -249,6 +249,13 @@ export class VpcNetwork extends VpcNetworkRef { */ private subnetConfigurations: SubnetConfiguration[] = []; + /** + * Maximum AZs to Uses for this VPC + * + * @default All + */ + private availabilityZones: string[]; + /** * VpcNetwork creates a VPC that spans a whole region. * It will automatically divide the provided VPC CIDR range, and create public and private subnets per Availability Zone. @@ -280,16 +287,29 @@ export class VpcNetwork extends VpcNetworkRef { tags }); + this.availabilityZones = new AvailabilityZoneProvider(this).availabilityZones; + if (props.maxAZs != null) { + this.availabilityZones.slice(props.maxAZs); + } + this.availabilityZones.sort(); + + + this.vpcId = this.resource.ref; this.dependencyElements.push(this.resource); - this.subnetConfigurations = props.subnetConfigurations || VpcNetwork.DEFAULT_SUBNETS; + + if (props.subnetConfigurations != null) { + this.subnetConfigurations = props.subnetConfigurations; + this.createSubnets(); + } else { + this.subnetConfigurations = VpcNetwork.DEFAULT_SUBNETS; + this.createDefaultSubnetResources(); + } + const allowOutbound = this.subnetConfigurations.filter( (subnet) => (subnet.subnetType !== SubnetType.Internal)).length > 0; - // Create public and private subnets in each AZ - this.createSubnets(props.maxAZs); - // Create an Internet Gateway and attach it (if the outbound traffic mode != None) if (allowOutbound) { const igw = new ec2.InternetGatewayResource(this, 'IGW'); @@ -316,26 +336,19 @@ export class VpcNetwork extends VpcNetworkRef { * createSubnets takes a VPC, and creates a public and private subnet * in each Availability Zone. */ - private createSubnets(maxAZs?: number) { + private createSubnets() { const remainingSpaceSubnets: SubnetConfigurationFinalized[] = []; // Calculate number of public/private subnets based on number of AZs - const availabilityZones = new AvailabilityZoneProvider(this).availabilityZones; - availabilityZones.sort(); - - // Restrict to maxAZs if given - if (maxAZs != null) { - availabilityZones.splice(maxAZs); - } for (const subnet of this.subnetConfigurations) { - let azs = availabilityZones; + let azs = this.availabilityZones; if (subnet.numAZs != null) { if (subnet.numAZs > azs.length) { throw new Error(`${subnet.name} requires ${subnet.numAZs} AZs but max is ${azs.length}`); } - azs = availabilityZones.slice(subnet.numAZs); + azs = this.availabilityZones.slice(subnet.numAZs); } subnet.mapPublicIpOnLaunch = subnet.mapPublicIpOnLaunch || @@ -379,6 +392,29 @@ export class VpcNetwork extends VpcNetworkRef { }); } + private createDefaultSubnetResources() { + const cidr = this.networkBuilder.maskForRemainingSubnets(this.availabilityZones.length * 2); + this.availabilityZones.forEach((zone, index) => { + const publicSubnet = new VpcPublicSubnet(this, `PublicSubnet${index +1}`, { + mapPublicIpOnLaunch: true, + vpcId: this.vpcId, + availabilityZone: zone, + cidrBlock: this.networkBuilder.addSubnet(cidr), + }); + this.natGatewayByAZ[zone] = publicSubnet.addNatGateway(); + this.publicSubnets.push(publicSubnet); + const privateSubnet = new VpcPrivateSubnet(this, `PrivateSubnet${index +1}`, { + mapPublicIpOnLaunch: false, + vpcId: this.vpcId, + availabilityZone: zone, + cidrBlock: this.networkBuilder.addSubnet(cidr), + }); + this.privateSubnets.push(privateSubnet); + this.dependencyElements.push(publicSubnet, privateSubnet); + }); + + } + private createSubnetResources(subnetConfig: SubnetConfigurationFinalized) { subnetConfig.availabilityZones.forEach((zone, index) => { const cidr: string = this.networkBuilder.addSubnet(subnetConfig.cidrMask); diff --git a/packages/@aws-cdk/ec2/test/integ.everything.expected.json b/packages/@aws-cdk/ec2/test/integ.everything.expected.json index 08d1074802b83..be4a0bb171aee 100644 --- a/packages/@aws-cdk/ec2/test/integ.everything.expected.json +++ b/packages/@aws-cdk/ec2/test/integ.everything.expected.json @@ -102,18 +102,6 @@ } } }, - "VPCPrivateSubnet1DefaultRouteAE1D6490": { - "Type": "AWS::EC2::Route", - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "NatGatewayId": { - "Ref": "VPCPublicSubnet1NATGatewayE0556630" - }, - "RouteTableId": { - "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" - } - } - }, "VPCPublicSubnet2Subnet74179F39": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -206,18 +194,6 @@ } } }, - "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { - "Type": "AWS::EC2::Route", - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "NatGatewayId": { - "Ref": "VPCPublicSubnet2NATGateway3C070193" - }, - "RouteTableId": { - "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" - } - } - }, "VPCPublicSubnet3Subnet631C5E25": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -310,18 +286,6 @@ } } }, - "VPCPrivateSubnet3DefaultRoute27F311AE": { - "Type": "AWS::EC2::Route", - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "NatGatewayId": { - "Ref": "VPCPublicSubnet3NATGatewayD3048F5C" - }, - "RouteTableId": { - "Ref": "VPCPrivateSubnet3RouteTable192186F8" - } - } - }, "VPCIGWB7E252D3": { "Type": "AWS::EC2::InternetGateway" }, diff --git a/packages/@aws-cdk/ec2/test/integ.vpc.expected.json b/packages/@aws-cdk/ec2/test/integ.vpc.expected.json index c5b9cca0ebd0b..7c618e15196b6 100644 --- a/packages/@aws-cdk/ec2/test/integ.vpc.expected.json +++ b/packages/@aws-cdk/ec2/test/integ.vpc.expected.json @@ -102,18 +102,6 @@ } } }, - "MyVpcPrivateSubnet1DefaultRouteA8CDE2FA": { - "Type": "AWS::EC2::Route", - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "NatGatewayId": { - "Ref": "MyVpcPublicSubnet1NATGatewayAD3400C1" - }, - "RouteTableId": { - "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" - } - } - }, "MyVpcPublicSubnet2Subnet492B6BFB": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -206,18 +194,6 @@ } } }, - "MyVpcPrivateSubnet2DefaultRoute9CE96294": { - "Type": "AWS::EC2::Route", - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "NatGatewayId": { - "Ref": "MyVpcPublicSubnet2NATGateway91BFBEC9" - }, - "RouteTableId": { - "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" - } - } - }, "MyVpcPublicSubnet3Subnet57EEE236": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -310,18 +286,6 @@ } } }, - "MyVpcPrivateSubnet3DefaultRouteEC11C0C5": { - "Type": "AWS::EC2::Route", - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "NatGatewayId": { - "Ref": "MyVpcPublicSubnet3NATGatewayD4B50EBE" - }, - "RouteTableId": { - "Ref": "MyVpcPrivateSubnet3RouteTableB790927C" - } - } - }, "MyVpcIGW5C4A4F63": { "Type": "AWS::EC2::InternetGateway" }, From 6dabebe6fb13a0b63cf111c98c0199cc6bb9bc2c Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Wed, 11 Jul 2018 15:37:56 -0700 Subject: [PATCH 14/35] fixing linter errors --- packages/@aws-cdk/ec2/lib/vpc.ts | 50 +++++++++++++++----------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index 7c64344820ec1..5e671c61c1bb7 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -293,8 +293,6 @@ export class VpcNetwork extends VpcNetworkRef { } this.availabilityZones.sort(); - - this.vpcId = this.resource.ref; this.dependencyElements.push(this.resource); @@ -306,7 +304,6 @@ export class VpcNetwork extends VpcNetworkRef { this.createDefaultSubnetResources(); } - const allowOutbound = this.subnetConfigurations.filter( (subnet) => (subnet.subnetType !== SubnetType.Internal)).length > 0; @@ -392,29 +389,6 @@ export class VpcNetwork extends VpcNetworkRef { }); } - private createDefaultSubnetResources() { - const cidr = this.networkBuilder.maskForRemainingSubnets(this.availabilityZones.length * 2); - this.availabilityZones.forEach((zone, index) => { - const publicSubnet = new VpcPublicSubnet(this, `PublicSubnet${index +1}`, { - mapPublicIpOnLaunch: true, - vpcId: this.vpcId, - availabilityZone: zone, - cidrBlock: this.networkBuilder.addSubnet(cidr), - }); - this.natGatewayByAZ[zone] = publicSubnet.addNatGateway(); - this.publicSubnets.push(publicSubnet); - const privateSubnet = new VpcPrivateSubnet(this, `PrivateSubnet${index +1}`, { - mapPublicIpOnLaunch: false, - vpcId: this.vpcId, - availabilityZone: zone, - cidrBlock: this.networkBuilder.addSubnet(cidr), - }); - this.privateSubnets.push(privateSubnet); - this.dependencyElements.push(publicSubnet, privateSubnet); - }); - - } - private createSubnetResources(subnetConfig: SubnetConfigurationFinalized) { subnetConfig.availabilityZones.forEach((zone, index) => { const cidr: string = this.networkBuilder.addSubnet(subnetConfig.cidrMask); @@ -453,6 +427,30 @@ export class VpcNetwork extends VpcNetworkRef { } }); } + + private createDefaultSubnetResources() { + const cidr = this.networkBuilder.maskForRemainingSubnets(this.availabilityZones.length * 2); + this.availabilityZones.forEach((zone, index) => { + const publicSubnet = new VpcPublicSubnet(this, `PublicSubnet${index + 1}`, { + mapPublicIpOnLaunch: true, + vpcId: this.vpcId, + availabilityZone: zone, + cidrBlock: this.networkBuilder.addSubnet(cidr), + }); + this.natGatewayByAZ[zone] = publicSubnet.addNatGateway(); + this.publicSubnets.push(publicSubnet); + const privateSubnet = new VpcPrivateSubnet(this, `PrivateSubnet${index + 1}`, { + mapPublicIpOnLaunch: false, + vpcId: this.vpcId, + availabilityZone: zone, + cidrBlock: this.networkBuilder.addSubnet(cidr), + }); + this.privateSubnets.push(privateSubnet); + this.dependencyElements.push(publicSubnet, privateSubnet); + }); + + } + } /** From 7be9cc56a07f41c2dfc403166d7c3d298145529d Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Thu, 12 Jul 2018 09:03:09 -0700 Subject: [PATCH 15/35] refactoring to keep backwards compatibility with vpc subnet generation strategy --- packages/@aws-cdk/ec2/lib/vpc.ts | 14 +++++++++----- packages/@aws-cdk/ec2/test/test.network-utils.ts | 2 ++ packages/@aws-cdk/ec2/test/test.vpc.ts | 11 +++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index 5e671c61c1bb7..684de5db6fde9 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -288,10 +288,10 @@ export class VpcNetwork extends VpcNetworkRef { }); this.availabilityZones = new AvailabilityZoneProvider(this).availabilityZones; + this.availabilityZones.sort(); if (props.maxAZs != null) { - this.availabilityZones.slice(props.maxAZs); + this.availabilityZones = this.availabilityZones.slice(0, props.maxAZs); } - this.availabilityZones.sort(); this.vpcId = this.resource.ref; this.dependencyElements.push(this.resource); @@ -429,13 +429,17 @@ export class VpcNetwork extends VpcNetworkRef { } private createDefaultSubnetResources() { - const cidr = this.networkBuilder.maskForRemainingSubnets(this.availabilityZones.length * 2); + const azNetwork: number = this.networkBuilder.maskForRemainingSubnets( + this.availabilityZones.length + ); this.availabilityZones.forEach((zone, index) => { + const builder = new NetworkBuilder(this.networkBuilder.addSubnet(azNetwork)); + const cidr = builder.maskForRemainingSubnets(this.subnetConfigurations.length); const publicSubnet = new VpcPublicSubnet(this, `PublicSubnet${index + 1}`, { mapPublicIpOnLaunch: true, vpcId: this.vpcId, availabilityZone: zone, - cidrBlock: this.networkBuilder.addSubnet(cidr), + cidrBlock: builder.addSubnet(cidr), }); this.natGatewayByAZ[zone] = publicSubnet.addNatGateway(); this.publicSubnets.push(publicSubnet); @@ -443,7 +447,7 @@ export class VpcNetwork extends VpcNetworkRef { mapPublicIpOnLaunch: false, vpcId: this.vpcId, availabilityZone: zone, - cidrBlock: this.networkBuilder.addSubnet(cidr), + cidrBlock: builder.addSubnet(cidr), }); this.privateSubnets.push(privateSubnet); this.dependencyElements.push(publicSubnet, privateSubnet); diff --git a/packages/@aws-cdk/ec2/test/test.network-utils.ts b/packages/@aws-cdk/ec2/test/test.network-utils.ts index f11c4378a6785..ed0ad2cba8582 100644 --- a/packages/@aws-cdk/ec2/test/test.network-utils.ts +++ b/packages/@aws-cdk/ec2/test/test.network-utils.ts @@ -180,6 +180,8 @@ export = { test.strictEqual(22, builder2.maskForRemainingSubnets(2)); const builder3 = new NetworkBuilder('192.168.0.0/16'); test.strictEqual(17, builder3.maskForRemainingSubnets(2)); + const builder4 = new NetworkBuilder('10.0.0.0/16'); + test.strictEqual(18, builder4.maskForRemainingSubnets(4)); test.done(); } } diff --git a/packages/@aws-cdk/ec2/test/test.vpc.ts b/packages/@aws-cdk/ec2/test/test.vpc.ts index 8af9dbfdaa755..d956d8a0f3a41 100644 --- a/packages/@aws-cdk/ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/ec2/test/test.vpc.ts @@ -153,6 +153,17 @@ export = { enableDnsSupport: false })); test.done(); + }, + "with maxAZs set to 2"(test: Test) { + const stack = getTestStack(); + new VpcNetwork(stack, 'VPC', { maxAZs: 2 }); + expect(stack).to(countResources("AWS::EC2::Subnet", 4)); + for (let i = 0; i < 4; i++) { + expect(stack).to(haveResource("AWS::EC2::Subnet", { + CidrBlock: `10.0.${i * 64}.0/18` + })); + } + test.done(); } }, From 1ef7db60f3bad433adc11e66be1628d74449c81b Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Thu, 12 Jul 2018 15:12:38 -0700 Subject: [PATCH 16/35] fixing imports removed incorrectly on conflict resolution --- packages/@aws-cdk/ec2/lib/vpc.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index 7175255231829..8feda5a26dd95 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -1,6 +1,7 @@ import { AvailabilityZoneProvider, Construct, Tag, Token } from '@aws-cdk/core'; import { cloudformation } from './ec2.generated'; -import { NetworkUtils } from './network-util'; +import { NetworkBuilder } from './network-util'; +import { Obj } from '@aws-cdk/util'; import { VpcNetworkId, VpcNetworkRef, VpcSubnetId, VpcSubnetRef } from './vpc-ref'; /** * VpcNetworkProps allows you to specify configuration options for a VPC From 06350b4f2993b4d272c2eecd4be8473df9db411b Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Thu, 12 Jul 2018 16:55:17 -0700 Subject: [PATCH 17/35] removed numazs --- packages/@aws-cdk/ec2/lib/vpc.ts | 26 +++++++++++++------------- packages/@aws-cdk/ec2/test/test.vpc.ts | 22 ---------------------- 2 files changed, 13 insertions(+), 35 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index 8feda5a26dd95..a4db6e859d7b0 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -1,7 +1,7 @@ import { AvailabilityZoneProvider, Construct, Tag, Token } from '@aws-cdk/core'; +import { Obj } from '@aws-cdk/util'; import { cloudformation } from './ec2.generated'; import { NetworkBuilder } from './network-util'; -import { Obj } from '@aws-cdk/util'; import { VpcNetworkId, VpcNetworkRef, VpcSubnetId, VpcSubnetRef } from './vpc-ref'; /** * VpcNetworkProps allows you to specify configuration options for a VPC @@ -54,6 +54,17 @@ export interface VpcNetworkProps { */ maxAZs?: number; + /** + * Define the maximum number of NAT Gateways for this VPC + * + * Setting this number enables a VPC to be trade availability for cost of + * running a NAT Gateway. For example, if set to 1 and your subnet + * configuration is for 3 Public subnets with natGateway = `true` then only + * one of the Public subnets will have a gateway and all Private subnets + * will route to this NAT Gateway. + */ + maxNatGateways?: number; + /** * Configure the subnets to build for each AZ * @@ -143,8 +154,6 @@ export interface SubnetConfiguration { natGateway?: boolean; // defaults to true in Subnet.Public, false in Subnet.Private or Subnet.Internal mapPublicIpOnLaunch?: boolean; - // number of AZs to build this subnet in - numAZs?: number; } interface SubnetConfigurationFinalized { @@ -339,21 +348,12 @@ export class VpcNetwork extends VpcNetworkRef { // Calculate number of public/private subnets based on number of AZs for (const subnet of this.subnetConfigurations) { - let azs = this.availabilityZones; - - if (subnet.numAZs != null) { - if (subnet.numAZs > azs.length) { - throw new Error(`${subnet.name} requires ${subnet.numAZs} AZs but max is ${azs.length}`); - } - azs = this.availabilityZones.slice(subnet.numAZs); - } - subnet.mapPublicIpOnLaunch = subnet.mapPublicIpOnLaunch || (subnet.subnetType === SubnetType.Public); const subnetFinal: SubnetConfigurationFinalized = { cidrMask: subnet.cidrMask || 0, - availabilityZones: azs, + availabilityZones: this.availabilityZones, subnetType: subnet.subnetType, name: subnet.name, natGateway: subnet.natGateway || false, diff --git a/packages/@aws-cdk/ec2/test/test.vpc.ts b/packages/@aws-cdk/ec2/test/test.vpc.ts index d956d8a0f3a41..d66e7a78a593c 100644 --- a/packages/@aws-cdk/ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/ec2/test/test.vpc.ts @@ -179,28 +179,6 @@ export = { } }, - - "when you specify more AZs in a subnet than are possible an error is raised"(test: Test) { - const stack = getTestStack(); - test.throws(() => { - new VpcNetwork(stack, 'TheVPC', { - subnetConfigurations: [ - { - subnetType: SubnetType.Public, - name: 'Public', - }, - { - subnetType: SubnetType.Internal, - name: 'Internal', - numAZs: 5, - } - ] - }); - }); - test.done(); - - } - }; function getTestStack(): Stack { From 455d9ef97d17f9ffdf21ac006a50459b65f6f05f Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Fri, 13 Jul 2018 15:23:50 -0700 Subject: [PATCH 18/35] fixing private subnet routes in default --- packages/@aws-cdk/ec2/lib/vpc.ts | 21 ++- .../ec2/test/integ.everything.expected.json | 134 +++++++++++------- .../@aws-cdk/ec2/test/integ.vpc.expected.json | 96 +++++++++---- packages/@aws-cdk/ec2/test/test.vpc.ts | 5 + 4 files changed, 166 insertions(+), 90 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index a4db6e859d7b0..b797159ca8e2e 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -328,6 +328,16 @@ export class VpcNetwork extends VpcNetworkRef { }); this.dependencyElements.push(igw, att); + + (this.privateSubnets as VpcPrivateSubnet[]).forEach((privateSubnet, i) => { + let ngwId = this.natGatewayByAZ[privateSubnet.availabilityZone]; + if (ngwId === undefined) { + const ngwArray = Array.from(Object.values(this.natGatewayByAZ)); + // round robin the available NatGW since one is not in your AZ + ngwId = ngwArray[i % ngwArray.length]; + } + privateSubnet.addDefaultNatRouteEntry(ngwId); + }); } } @@ -377,16 +387,6 @@ export class VpcNetwork extends VpcNetworkRef { subnetFinal.cidrMask = cidrMaskForRemaing; this.createSubnetResources(subnetFinal); } - - (this.privateSubnets as VpcPrivateSubnet[]).forEach((privateSubnet, i) => { - let ngwId = this.natGatewayByAZ[privateSubnet.availabilityZone]; - if (ngwId === undefined) { - const ngwArray = Array.from(Object.values(this.natGatewayByAZ)); - // round robin the available NatGW since one is not in your AZ - ngwId = ngwArray[i % ngwArray.length]; - } - privateSubnet.addDefaultNatRouteEntry(ngwId); - }); } private createSubnetResources(subnetConfig: SubnetConfigurationFinalized) { @@ -452,7 +452,6 @@ export class VpcNetwork extends VpcNetworkRef { this.privateSubnets.push(privateSubnet); this.dependencyElements.push(publicSubnet, privateSubnet); }); - } } diff --git a/packages/@aws-cdk/ec2/test/integ.everything.expected.json b/packages/@aws-cdk/ec2/test/integ.everything.expected.json index be4a0bb171aee..8562b9436d72d 100644 --- a/packages/@aws-cdk/ec2/test/integ.everything.expected.json +++ b/packages/@aws-cdk/ec2/test/integ.everything.expected.json @@ -13,12 +13,12 @@ "VPCPublicSubnet1SubnetB4246D30": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1a", "CidrBlock": "10.0.0.0/19", - "MapPublicIpOnLaunch": true, "VpcId": { "Ref": "VPCB9E5F0B4" - } + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true } }, "VPCPublicSubnet1RouteTableFEE4B781": { @@ -63,24 +63,24 @@ "VPCPublicSubnet1DefaultRoute91CEF279": { "Type": "AWS::EC2::Route", "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { "Ref": "VPCIGWB7E252D3" - }, - "RouteTableId": { - "Ref": "VPCPublicSubnet1RouteTableFEE4B781" } } }, "VPCPrivateSubnet1Subnet8BCA10E0": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1a", "CidrBlock": "10.0.32.0/19", - "MapPublicIpOnLaunch": false, "VpcId": { "Ref": "VPCB9E5F0B4" - } + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false } }, "VPCPrivateSubnet1RouteTableBE8A6027": { @@ -102,15 +102,27 @@ } } }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, "VPCPublicSubnet2Subnet74179F39": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1b", "CidrBlock": "10.0.64.0/19", - "MapPublicIpOnLaunch": true, "VpcId": { "Ref": "VPCB9E5F0B4" - } + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true } }, "VPCPublicSubnet2RouteTable6F1A15F1": { @@ -155,24 +167,24 @@ "VPCPublicSubnet2DefaultRouteB7481BBA": { "Type": "AWS::EC2::Route", "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { "Ref": "VPCIGWB7E252D3" - }, - "RouteTableId": { - "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" } } }, "VPCPrivateSubnet2SubnetCFCDAA7A": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1b", "CidrBlock": "10.0.96.0/19", - "MapPublicIpOnLaunch": false, "VpcId": { "Ref": "VPCB9E5F0B4" - } + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false } }, "VPCPrivateSubnet2RouteTable0A19E10E": { @@ -194,15 +206,27 @@ } } }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet2NATGateway3C070193" + } + } + }, "VPCPublicSubnet3Subnet631C5E25": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1c", "CidrBlock": "10.0.128.0/19", - "MapPublicIpOnLaunch": true, "VpcId": { "Ref": "VPCB9E5F0B4" - } + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true } }, "VPCPublicSubnet3RouteTable98AE0E14": { @@ -247,24 +271,24 @@ "VPCPublicSubnet3DefaultRouteA0D29D46": { "Type": "AWS::EC2::Route", "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { "Ref": "VPCIGWB7E252D3" - }, - "RouteTableId": { - "Ref": "VPCPublicSubnet3RouteTable98AE0E14" } } }, "VPCPrivateSubnet3Subnet3EDCD457": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1c", "CidrBlock": "10.0.160.0/19", - "MapPublicIpOnLaunch": false, "VpcId": { "Ref": "VPCB9E5F0B4" - } + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false } }, "VPCPrivateSubnet3RouteTable192186F8": { @@ -286,17 +310,29 @@ } } }, + "VPCPrivateSubnet3DefaultRoute27F311AE": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet3NATGatewayD3048F5C" + } + } + }, "VPCIGWB7E252D3": { "Type": "AWS::EC2::InternetGateway" }, "VPCVPCGW99B986DC": { "Type": "AWS::EC2::VPCGatewayAttachment", "Properties": { - "InternetGatewayId": { - "Ref": "VPCIGWB7E252D3" - }, "VpcId": { "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" } } }, @@ -322,6 +358,7 @@ "FleetInstanceSecurityGroupPort80LBtofleetDC12B17A": { "Type": "AWS::EC2::SecurityGroupIngress", "Properties": { + "IpProtocol": "tcp", "Description": "Port 80 LB to fleet", "FromPort": 80, "GroupId": { @@ -330,7 +367,6 @@ "GroupId" ] }, - "IpProtocol": "tcp", "SourceSecurityGroupId": { "Fn::GetAtt": [ "LBSecurityGroup8A41EA2B", @@ -370,11 +406,11 @@ "FleetLaunchConfig59F79D36": { "Type": "AWS::AutoScaling::LaunchConfiguration", "Properties": { + "ImageId": "ami-1234", + "InstanceType": "t2.micro", "IamInstanceProfile": { "Ref": "FleetInstanceProfileC6192A66" }, - "ImageId": "ami-1234", - "InstanceType": "t2.micro", "SecurityGroups": [ { "Fn::GetAtt": [ @@ -394,6 +430,8 @@ "FleetASG3971DFE5": { "Type": "AWS::AutoScaling::AutoScalingGroup", "Properties": { + "MaxSize": "1", + "MinSize": "1", "DesiredCapacity": "1", "LaunchConfigurationName": { "Ref": "FleetLaunchConfig59F79D36" @@ -403,8 +441,6 @@ "Ref": "LB8A12904C" } ], - "MaxSize": "1", - "MinSize": "1", "VPCZoneIdentifier": [ { "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" @@ -440,34 +476,27 @@ "LBSecurityGroupPort80LBtofleet0986F2E8": { "Type": "AWS::EC2::SecurityGroupEgress", "Properties": { - "Description": "Port 80 LB to fleet", - "DestinationSecurityGroupId": { + "GroupId": { "Fn::GetAtt": [ - "FleetInstanceSecurityGroupA8C3D7AD", + "LBSecurityGroup8A41EA2B", "GroupId" ] }, - "FromPort": 80, - "GroupId": { + "IpProtocol": "tcp", + "Description": "Port 80 LB to fleet", + "DestinationSecurityGroupId": { "Fn::GetAtt": [ - "LBSecurityGroup8A41EA2B", + "FleetInstanceSecurityGroupA8C3D7AD", "GroupId" ] }, - "IpProtocol": "tcp", + "FromPort": 80, "ToPort": 80 } }, "LB8A12904C": { "Type": "AWS::ElasticLoadBalancing::LoadBalancer", "Properties": { - "HealthCheck": { - "HealthyThreshold": "2", - "Interval": "30", - "Target": "HTTP:80/", - "Timeout": "5", - "UnhealthyThreshold": "5" - }, "Listeners": [ { "InstancePort": "80", @@ -476,6 +505,13 @@ "Protocol": "http" } ], + "HealthCheck": { + "HealthyThreshold": "2", + "Interval": "30", + "Target": "HTTP:80/", + "Timeout": "5", + "UnhealthyThreshold": "5" + }, "Scheme": "internet-facing", "SecurityGroups": [ { diff --git a/packages/@aws-cdk/ec2/test/integ.vpc.expected.json b/packages/@aws-cdk/ec2/test/integ.vpc.expected.json index 7c618e15196b6..39d43110b2996 100644 --- a/packages/@aws-cdk/ec2/test/integ.vpc.expected.json +++ b/packages/@aws-cdk/ec2/test/integ.vpc.expected.json @@ -13,12 +13,12 @@ "MyVpcPublicSubnet1SubnetF6608456": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1a", "CidrBlock": "10.0.0.0/19", - "MapPublicIpOnLaunch": true, "VpcId": { "Ref": "MyVpcF9F0CA6F" - } + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true } }, "MyVpcPublicSubnet1RouteTableC46AB2F4": { @@ -63,24 +63,24 @@ "MyVpcPublicSubnet1DefaultRoute95FDF9EB": { "Type": "AWS::EC2::Route", "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" + }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { "Ref": "MyVpcIGW5C4A4F63" - }, - "RouteTableId": { - "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" } } }, "MyVpcPrivateSubnet1Subnet5057CF7E": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1a", "CidrBlock": "10.0.32.0/19", - "MapPublicIpOnLaunch": false, "VpcId": { "Ref": "MyVpcF9F0CA6F" - } + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false } }, "MyVpcPrivateSubnet1RouteTable8819E6E2": { @@ -102,15 +102,27 @@ } } }, + "MyVpcPrivateSubnet1DefaultRouteA8CDE2FA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "MyVpcPublicSubnet1NATGatewayAD3400C1" + } + } + }, "MyVpcPublicSubnet2Subnet492B6BFB": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1b", "CidrBlock": "10.0.64.0/19", - "MapPublicIpOnLaunch": true, "VpcId": { "Ref": "MyVpcF9F0CA6F" - } + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true } }, "MyVpcPublicSubnet2RouteTable1DF17386": { @@ -155,24 +167,24 @@ "MyVpcPublicSubnet2DefaultRoute052936F6": { "Type": "AWS::EC2::Route", "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" + }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { "Ref": "MyVpcIGW5C4A4F63" - }, - "RouteTableId": { - "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" } } }, "MyVpcPrivateSubnet2Subnet0040C983": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1b", "CidrBlock": "10.0.96.0/19", - "MapPublicIpOnLaunch": false, "VpcId": { "Ref": "MyVpcF9F0CA6F" - } + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false } }, "MyVpcPrivateSubnet2RouteTableCEDCEECE": { @@ -194,15 +206,27 @@ } } }, + "MyVpcPrivateSubnet2DefaultRoute9CE96294": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "MyVpcPublicSubnet2NATGateway91BFBEC9" + } + } + }, "MyVpcPublicSubnet3Subnet57EEE236": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1c", "CidrBlock": "10.0.128.0/19", - "MapPublicIpOnLaunch": true, "VpcId": { "Ref": "MyVpcF9F0CA6F" - } + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true } }, "MyVpcPublicSubnet3RouteTable15028F08": { @@ -247,24 +271,24 @@ "MyVpcPublicSubnet3DefaultRoute3A83AB36": { "Type": "AWS::EC2::Route", "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet3RouteTable15028F08" + }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { "Ref": "MyVpcIGW5C4A4F63" - }, - "RouteTableId": { - "Ref": "MyVpcPublicSubnet3RouteTable15028F08" } } }, "MyVpcPrivateSubnet3Subnet772D6AD7": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1c", "CidrBlock": "10.0.160.0/19", - "MapPublicIpOnLaunch": false, "VpcId": { "Ref": "MyVpcF9F0CA6F" - } + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false } }, "MyVpcPrivateSubnet3RouteTableB790927C": { @@ -286,17 +310,29 @@ } } }, + "MyVpcPrivateSubnet3DefaultRouteEC11C0C5": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet3RouteTableB790927C" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "MyVpcPublicSubnet3NATGatewayD4B50EBE" + } + } + }, "MyVpcIGW5C4A4F63": { "Type": "AWS::EC2::InternetGateway" }, "MyVpcVPCGW488ACE0D": { "Type": "AWS::EC2::VPCGatewayAttachment", "Properties": { - "InternetGatewayId": { - "Ref": "MyVpcIGW5C4A4F63" - }, "VpcId": { "Ref": "MyVpcF9F0CA6F" + }, + "InternetGatewayId": { + "Ref": "MyVpcIGW5C4A4F63" } } } diff --git a/packages/@aws-cdk/ec2/test/test.vpc.ts b/packages/@aws-cdk/ec2/test/test.vpc.ts index d66e7a78a593c..eb1937d0b92cc 100644 --- a/packages/@aws-cdk/ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/ec2/test/test.vpc.ts @@ -158,11 +158,16 @@ export = { const stack = getTestStack(); new VpcNetwork(stack, 'VPC', { maxAZs: 2 }); expect(stack).to(countResources("AWS::EC2::Subnet", 4)); + expect(stack).to(countResources("AWS::EC2::Route", 4)); for (let i = 0; i < 4; i++) { expect(stack).to(haveResource("AWS::EC2::Subnet", { CidrBlock: `10.0.${i * 64}.0/18` })); } + expect(stack).to(haveResource("AWS::EC2::Route", { + DestinationCidrBlock: '0.0.0.0/0', + NatGatewayId: { }, + })); test.done(); } From 701858c3a5afd37b42ae0ead10478cc9149b2a1e Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Sun, 15 Jul 2018 16:01:34 -0700 Subject: [PATCH 19/35] refactor to remove SubnetConfigFinalized --- packages/@aws-cdk/ec2/lib/vpc.ts | 104 ++++++++++++------------------- 1 file changed, 39 insertions(+), 65 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index b797159ca8e2e..521d76c7ecbe6 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -57,11 +57,12 @@ export interface VpcNetworkProps { /** * Define the maximum number of NAT Gateways for this VPC * - * Setting this number enables a VPC to be trade availability for cost of - * running a NAT Gateway. For example, if set to 1 and your subnet + * Setting this number enables a VPC to trade availability for the cost of + * running a NAT Gateway. For example, if set this to 1 and your subnet * configuration is for 3 Public subnets with natGateway = `true` then only * one of the Public subnets will have a gateway and all Private subnets * will route to this NAT Gateway. + * @default maxAZs */ maxNatGateways?: number; @@ -72,19 +73,24 @@ export interface VpcNetworkProps { * specify the configuration. The VPC details (VPC ID, specific CIDR, * specific AZ will be calculated during creation) * - * For example if you want three private subnets and three public subnets - * across 3 AZs then maxAZs = 3 and provide the following: + * For example if you want 1 public subnet, 1 private subnet, and 1 internal + * subnet in each AZ provide the following: * subnets: [ * { * cidrMask: 24, + * name: application, + * subnetType: SubnetType.Private, + * }, + * { + * cidrMask: 26, * name: ingress, * subnetType: SubnetType.Public, * natGateway: true, * }, * { - * cidrMask: 24, - * name: application, - * subnetType: SubnetType.Private, + * cidrMask: 28, + * name: rds, + * subnetType: SubnetType.Internal, * } * ] * @default the VPC CIDR will be evenly divided between 1 public and 1 @@ -156,21 +162,6 @@ export interface SubnetConfiguration { mapPublicIpOnLaunch?: boolean; } -interface SubnetConfigurationFinalized { - // the cidr mask value from 16-28 - cidrMask: number; - // Public (IGW), Private (Nat GW), Internal (no outbound) - subnetType: SubnetType; - // name that will be used to generate an AZ specific name e.g. name-2a - name: string; - // if true will place a NAT Gateway in this subnet, subnetType must be Public - natGateway: boolean; - // defaults to true in Subnet.Public, false in Subnet.Private or Subnet.Internal - mapPublicIpOnLaunch: boolean; - // availabity zones to buid this subnet in - availabilityZones: string[]; -} - /** * VpcNetwork deploys an AWS VPC, with public and private subnets per Availability Zone. * For example: @@ -353,7 +344,7 @@ export class VpcNetwork extends VpcNetworkRef { * in each Availability Zone. */ private createSubnets() { - const remainingSpaceSubnets: SubnetConfigurationFinalized[] = []; + const remainingSpaceSubnets: SubnetConfiguration[] = []; // Calculate number of public/private subnets based on number of AZs @@ -361,67 +352,50 @@ export class VpcNetwork extends VpcNetworkRef { subnet.mapPublicIpOnLaunch = subnet.mapPublicIpOnLaunch || (subnet.subnetType === SubnetType.Public); - const subnetFinal: SubnetConfigurationFinalized = { - cidrMask: subnet.cidrMask || 0, - availabilityZones: this.availabilityZones, - subnetType: subnet.subnetType, - name: subnet.name, - natGateway: subnet.natGateway || false, - mapPublicIpOnLaunch: subnet.mapPublicIpOnLaunch - }; - - if (subnetFinal.cidrMask === 0) { - remainingSpaceSubnets.push(subnetFinal); + if (subnet.cidrMask === undefined) { + remainingSpaceSubnets.push(subnet); continue; } - this.createSubnetResources(subnetFinal); + this.createSubnetResources(subnet); } - - const totalRemaining = remainingSpaceSubnets.reduce( (total: number, subnet) => { - return total += subnet.availabilityZones.length; - }, 0); + const totalRemaining = remainingSpaceSubnets.length * this.availabilityZones.length; const cidrMaskForRemaing = this.networkBuilder.maskForRemainingSubnets(totalRemaining); - for (const subnetFinal of remainingSpaceSubnets) { - subnetFinal.cidrMask = cidrMaskForRemaing; - this.createSubnetResources(subnetFinal); + for (const subnet of remainingSpaceSubnets) { + subnet.cidrMask = cidrMaskForRemaing; + this.createSubnetResources(subnet); } } - private createSubnetResources(subnetConfig: SubnetConfigurationFinalized) { - subnetConfig.availabilityZones.forEach((zone, index) => { - const cidr: string = this.networkBuilder.addSubnet(subnetConfig.cidrMask); + private createSubnetResources(subnetConfig: SubnetConfiguration) { + this.availabilityZones.forEach((zone, index) => { + // this can never happen but satifies the compiler + if (subnetConfig.cidrMask === undefined) { + throw new Error('Subnet CIDR Mask must be set'); + } + const cidrBlock: string = this.networkBuilder.addSubnet(subnetConfig.cidrMask); const name: string = `${subnetConfig.name}Subnet${index + 1}`; + const subnetProps = { + availabilityZone: zone, + vpcId: this.vpcId, + cidrBlock: cidrBlock, + mapPublicIpOnLaunch: subnetConfig.mapPublicIpOnLaunch + } switch (subnetConfig.subnetType) { case SubnetType.Public: - const publicSubnet = new VpcPublicSubnet(this, name, { - mapPublicIpOnLaunch: subnetConfig.mapPublicIpOnLaunch || true, - vpcId: this.vpcId, - availabilityZone: zone, - cidrBlock: cidr - }); + const publicSubnet = new VpcPublicSubnet(this, name, subnetProps); if (subnetConfig.natGateway) { this.natGatewayByAZ[zone] = publicSubnet.addNatGateway(); } this.publicSubnets.push(publicSubnet); break; case SubnetType.Private: - const privateSubnet = new VpcPrivateSubnet(this, name, { - mapPublicIpOnLaunch: subnetConfig.mapPublicIpOnLaunch || false, - vpcId: this.vpcId, - availabilityZone: zone, - cidrBlock: cidr - }); + const privateSubnet = new VpcPrivateSubnet(this, name, subnetProps); this.privateSubnets.push(privateSubnet); break; case SubnetType.Internal: - const internalSubnet = new VpcPrivateSubnet(this, name, { - mapPublicIpOnLaunch: subnetConfig.mapPublicIpOnLaunch || false, - vpcId: this.vpcId, - availabilityZone: zone, - cidrBlock: cidr - }); + const internalSubnet = new VpcPrivateSubnet(this, name, subnetProps); this.internalSubnets.push(internalSubnet); break; } @@ -429,11 +403,11 @@ export class VpcNetwork extends VpcNetworkRef { } private createDefaultSubnetResources() { - const azNetwork: number = this.networkBuilder.maskForRemainingSubnets( + const azCidrMask: number = this.networkBuilder.maskForRemainingSubnets( this.availabilityZones.length ); this.availabilityZones.forEach((zone, index) => { - const builder = new NetworkBuilder(this.networkBuilder.addSubnet(azNetwork)); + const builder = new NetworkBuilder(this.networkBuilder.addSubnet(azCidrMask)); const cidr = builder.maskForRemainingSubnets(this.subnetConfigurations.length); const publicSubnet = new VpcPublicSubnet(this, `PublicSubnet${index + 1}`, { mapPublicIpOnLaunch: true, From 5ecb64a5adf01315765364233d63b57531cd0bb1 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Sun, 15 Jul 2018 20:57:07 -0700 Subject: [PATCH 20/35] updating to support maxNatGateways --- packages/@aws-cdk/ec2/lib/vpc.ts | 26 ++++++++++--- packages/@aws-cdk/ec2/test/test.vpc.ts | 53 ++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index 521d76c7ecbe6..5044f96fa4b29 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -229,6 +229,11 @@ export class VpcNetwork extends VpcNetworkRef { */ public readonly internalSubnets: VpcSubnetRef[] = []; + /** + * Maximum Number of NAT Gateways used to control cost + */ + private readonly maxNatGateways: number; + /** * The VPC resource */ @@ -278,6 +283,11 @@ export class VpcNetwork extends VpcNetworkRef { const instanceTenancy = props.defaultInstanceTenancy || 'default'; const tags = props.tags || []; + // 256 is an arbitrary max that should never be reached + // https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Appendix_Limits.html + // should be triggered first + this.maxNatGateways = props.maxNatGateways || 256; + // Define a VPC using the provided CIDR range this.resource = new cloudformation.VPCResource(this, 'Resource', { cidrBlock, @@ -374,19 +384,22 @@ export class VpcNetwork extends VpcNetworkRef { if (subnetConfig.cidrMask === undefined) { throw new Error('Subnet CIDR Mask must be set'); } - const cidrBlock: string = this.networkBuilder.addSubnet(subnetConfig.cidrMask); const name: string = `${subnetConfig.name}Subnet${index + 1}`; const subnetProps = { availabilityZone: zone, vpcId: this.vpcId, - cidrBlock: cidrBlock, + cidrBlock: this.networkBuilder.addSubnet(subnetConfig.cidrMask), mapPublicIpOnLaunch: subnetConfig.mapPublicIpOnLaunch - } + }; + switch (subnetConfig.subnetType) { case SubnetType.Public: const publicSubnet = new VpcPublicSubnet(this, name, subnetProps); if (subnetConfig.natGateway) { - this.natGatewayByAZ[zone] = publicSubnet.addNatGateway(); + const ngwArray = Array.from(Object.values(this.natGatewayByAZ)); + if (ngwArray.length < this.maxNatGateways) { + this.natGatewayByAZ[zone] = publicSubnet.addNatGateway(); + } } this.publicSubnets.push(publicSubnet); break; @@ -415,7 +428,10 @@ export class VpcNetwork extends VpcNetworkRef { availabilityZone: zone, cidrBlock: builder.addSubnet(cidr), }); - this.natGatewayByAZ[zone] = publicSubnet.addNatGateway(); + const ngwArray = Array.from(Object.values(this.natGatewayByAZ)); + if (ngwArray.length < this.maxNatGateways) { + this.natGatewayByAZ[zone] = publicSubnet.addNatGateway(); + } this.publicSubnets.push(publicSubnet); const privateSubnet = new VpcPrivateSubnet(this, `PrivateSubnet${index + 1}`, { mapPublicIpOnLaunch: false, diff --git a/packages/@aws-cdk/ec2/test/test.vpc.ts b/packages/@aws-cdk/ec2/test/test.vpc.ts index eb1937d0b92cc..e3a1f7a8e7949 100644 --- a/packages/@aws-cdk/ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/ec2/test/test.vpc.ts @@ -146,6 +146,46 @@ export = { } test.done(); }, + "with custom subents and maxNatGateway = 2 there should be only to NATGW"(test: Test) { + const stack = getTestStack(); + new VpcNetwork(stack, 'TheVPC', { + cidr: '10.0.0.0/21', + maxNatGateways: 2, + subnetConfigurations: [ + { + cidrMask: 24, + name: 'ingress', + subnetType: SubnetType.Public, + natGateway: true, + }, + { + cidrMask: 24, + name: 'application', + subnetType: SubnetType.Private, + }, + { + cidrMask: 28, + name: 'rds', + subnetType: SubnetType.Internal, + } + ], + maxAZs: 3 + }); + expect(stack).to(countResources("AWS::EC2::InternetGateway", 1)); + expect(stack).to(countResources("AWS::EC2::NatGateway", 2)); + expect(stack).to(countResources("AWS::EC2::Subnet", 9)); + for (let i = 0; i < 6; i++) { + expect(stack).to(haveResource("AWS::EC2::Subnet", { + CidrBlock: `10.0.${i}.0/24` + })); + } + for (let i = 0; i < 3; i++) { + expect(stack).to(haveResource("AWS::EC2::Subnet", { + CidrBlock: `10.0.6.${i * 16}/28` + })); + } + test.done(); + }, "with enableDnsHostnames enabled but enableDnsSupport disabled, should throw an Error"(test: Test) { const stack = getTestStack(); test.throws(() => new VpcNetwork(stack, 'TheVPC', { @@ -169,6 +209,19 @@ export = { NatGatewayId: { }, })); test.done(); + }, + "with maxNatGateway set to 1"(test: Test) { + const stack = getTestStack(); + new VpcNetwork(stack, 'VPC', { maxNatGateways: 1 }); + expect(stack).to(countResources("AWS::EC2::Subnet", 6)); + expect(stack).to(countResources("AWS::EC2::Route", 6)); + expect(stack).to(countResources("AWS::EC2::Subnet", 6)); + expect(stack).to(countResources("AWS::EC2::NatGateway", 1)); + expect(stack).to(haveResource("AWS::EC2::Route", { + DestinationCidrBlock: '0.0.0.0/0', + NatGatewayId: { }, + })); + test.done(); } }, From a5ce5d9294f8376719ad5af3690411ef777da0c8 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Wed, 18 Jul 2018 16:12:29 -0700 Subject: [PATCH 21/35] updates for integ tests for rds and vpc --- packages/@aws-cdk/ec2/lib/vpc.ts | 74 +++----- .../ec2/test/integ.everything.expected.json | 126 +++++++------- .../@aws-cdk/ec2/test/integ.vpc.expected.json | 126 +++++++------- .../rds/test/integ.cluster.expected.json | 158 ++++++++---------- 4 files changed, 213 insertions(+), 271 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index 5044f96fa4b29..5a22a1800f2ec 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -189,7 +189,15 @@ export class VpcNetwork extends VpcNetworkRef { * This can be overridden using VpcNetworkProps when creating a VPCNetwork resource. * e.g. new VpcResource(this, { cidr: '192.168.0.0./16' }) */ - public static readonly DEFAULT_CIDR_RANGE = '10.0.0.0/16'; + public static readonly DEFAULT_CIDR_RANGE: string = '10.0.0.0/16'; + + /** + * Maximum Number of NAT Gateways used to control cost + * + * @link https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Appendix_Limits.html + * defaulting 256 is an arbitrary max that should be impracticel to reach + */ + public static readonly MAX_NAT_GATEWAYS: number = 256; /** * The deafult subnet configuration @@ -231,6 +239,8 @@ export class VpcNetwork extends VpcNetworkRef { /** * Maximum Number of NAT Gateways used to control cost + * + * @default {VpcNetwork.MAX_NAT_GATEWAYS} */ private readonly maxNatGateways: number; @@ -283,11 +293,6 @@ export class VpcNetwork extends VpcNetworkRef { const instanceTenancy = props.defaultInstanceTenancy || 'default'; const tags = props.tags || []; - // 256 is an arbitrary max that should never be reached - // https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Appendix_Limits.html - // should be triggered first - this.maxNatGateways = props.maxNatGateways || 256; - // Define a VPC using the provided CIDR range this.resource = new cloudformation.VPCResource(this, 'Resource', { cidrBlock, @@ -306,13 +311,10 @@ export class VpcNetwork extends VpcNetworkRef { this.vpcId = this.resource.ref; this.dependencyElements.push(this.resource); - if (props.subnetConfigurations != null) { - this.subnetConfigurations = props.subnetConfigurations; - this.createSubnets(); - } else { - this.subnetConfigurations = VpcNetwork.DEFAULT_SUBNETS; - this.createDefaultSubnetResources(); - } + this.maxNatGateways = props.maxNatGateways || VpcNetwork.MAX_NAT_GATEWAYS; + + this.subnetConfigurations = props.subnetConfigurations || VpcNetwork.DEFAULT_SUBNETS; + this.createSubnets(); const allowOutbound = this.subnetConfigurations.filter( (subnet) => (subnet.subnetType !== SubnetType.Internal)).length > 0; @@ -366,29 +368,23 @@ export class VpcNetwork extends VpcNetworkRef { remainingSpaceSubnets.push(subnet); continue; } - this.createSubnetResources(subnet); + this.createSubnetResources(subnet, subnet.cidrMask); } - const totalRemaining = remainingSpaceSubnets.length * this.availabilityZones.length; + const totalRemaining = remainingSpaceSubnets.length * this.availabilityZones.length; const cidrMaskForRemaing = this.networkBuilder.maskForRemainingSubnets(totalRemaining); - for (const subnet of remainingSpaceSubnets) { - subnet.cidrMask = cidrMaskForRemaing; - this.createSubnetResources(subnet); + this.createSubnetResources(subnet, cidrMaskForRemaing); } } - private createSubnetResources(subnetConfig: SubnetConfiguration) { + private createSubnetResources(subnetConfig: SubnetConfiguration, cidrMask: number) { this.availabilityZones.forEach((zone, index) => { - // this can never happen but satifies the compiler - if (subnetConfig.cidrMask === undefined) { - throw new Error('Subnet CIDR Mask must be set'); - } const name: string = `${subnetConfig.name}Subnet${index + 1}`; const subnetProps = { availabilityZone: zone, vpcId: this.vpcId, - cidrBlock: this.networkBuilder.addSubnet(subnetConfig.cidrMask), + cidrBlock: this.networkBuilder.addSubnet(cidrMask), mapPublicIpOnLaunch: subnetConfig.mapPublicIpOnLaunch }; @@ -414,36 +410,6 @@ export class VpcNetwork extends VpcNetworkRef { } }); } - - private createDefaultSubnetResources() { - const azCidrMask: number = this.networkBuilder.maskForRemainingSubnets( - this.availabilityZones.length - ); - this.availabilityZones.forEach((zone, index) => { - const builder = new NetworkBuilder(this.networkBuilder.addSubnet(azCidrMask)); - const cidr = builder.maskForRemainingSubnets(this.subnetConfigurations.length); - const publicSubnet = new VpcPublicSubnet(this, `PublicSubnet${index + 1}`, { - mapPublicIpOnLaunch: true, - vpcId: this.vpcId, - availabilityZone: zone, - cidrBlock: builder.addSubnet(cidr), - }); - const ngwArray = Array.from(Object.values(this.natGatewayByAZ)); - if (ngwArray.length < this.maxNatGateways) { - this.natGatewayByAZ[zone] = publicSubnet.addNatGateway(); - } - this.publicSubnets.push(publicSubnet); - const privateSubnet = new VpcPrivateSubnet(this, `PrivateSubnet${index + 1}`, { - mapPublicIpOnLaunch: false, - vpcId: this.vpcId, - availabilityZone: zone, - cidrBlock: builder.addSubnet(cidr), - }); - this.privateSubnets.push(privateSubnet); - this.dependencyElements.push(publicSubnet, privateSubnet); - }); - } - } /** diff --git a/packages/@aws-cdk/ec2/test/integ.everything.expected.json b/packages/@aws-cdk/ec2/test/integ.everything.expected.json index 8562b9436d72d..1b57b2f048501 100644 --- a/packages/@aws-cdk/ec2/test/integ.everything.expected.json +++ b/packages/@aws-cdk/ec2/test/integ.everything.expected.json @@ -72,18 +72,18 @@ } } }, - "VPCPrivateSubnet1Subnet8BCA10E0": { + "VPCPublicSubnet2Subnet74179F39": { "Type": "AWS::EC2::Subnet", "Properties": { "CidrBlock": "10.0.32.0/19", "VpcId": { "Ref": "VPCB9E5F0B4" }, - "AvailabilityZone": "test-region-1a", - "MapPublicIpOnLaunch": false + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true } }, - "VPCPrivateSubnet1RouteTableBE8A6027": { + "VPCPublicSubnet2RouteTable6F1A15F1": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { @@ -91,41 +91,61 @@ } } }, - "VPCPrivateSubnet1RouteTableAssociatioin77F7CA18": { + "VPCPublicSubnet2RouteTableAssociatioin766225D7": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { - "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" }, "SubnetId": { - "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + "Ref": "VPCPublicSubnet2Subnet74179F39" } } }, - "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "VPCPublicSubnet2EIP4947BC00": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "VPCPublicSubnet2NATGateway3C070193": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet2EIP4947BC00", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { "Type": "AWS::EC2::Route", "Properties": { "RouteTableId": { - "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" }, "DestinationCidrBlock": "0.0.0.0/0", - "NatGatewayId": { - "Ref": "VPCPublicSubnet1NATGatewayE0556630" + "GatewayId": { + "Ref": "VPCIGWB7E252D3" } } }, - "VPCPublicSubnet2Subnet74179F39": { + "VPCPublicSubnet3Subnet631C5E25": { "Type": "AWS::EC2::Subnet", "Properties": { "CidrBlock": "10.0.64.0/19", "VpcId": { "Ref": "VPCB9E5F0B4" }, - "AvailabilityZone": "test-region-1b", + "AvailabilityZone": "test-region-1c", "MapPublicIpOnLaunch": true } }, - "VPCPublicSubnet2RouteTable6F1A15F1": { + "VPCPublicSubnet3RouteTable98AE0E14": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { @@ -133,42 +153,42 @@ } } }, - "VPCPublicSubnet2RouteTableAssociatioin766225D7": { + "VPCPublicSubnet3RouteTableAssociatioinF4E24B3B": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { - "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" }, "SubnetId": { - "Ref": "VPCPublicSubnet2Subnet74179F39" + "Ref": "VPCPublicSubnet3Subnet631C5E25" } } }, - "VPCPublicSubnet2EIP4947BC00": { + "VPCPublicSubnet3EIPAD4BC883": { "Type": "AWS::EC2::EIP", "Properties": { "Domain": "vpc" } }, - "VPCPublicSubnet2NATGateway3C070193": { + "VPCPublicSubnet3NATGatewayD3048F5C": { "Type": "AWS::EC2::NatGateway", "Properties": { "AllocationId": { "Fn::GetAtt": [ - "VPCPublicSubnet2EIP4947BC00", + "VPCPublicSubnet3EIPAD4BC883", "AllocationId" ] }, "SubnetId": { - "Ref": "VPCPublicSubnet2Subnet74179F39" + "Ref": "VPCPublicSubnet3Subnet631C5E25" } } }, - "VPCPublicSubnet2DefaultRouteB7481BBA": { + "VPCPublicSubnet3DefaultRouteA0D29D46": { "Type": "AWS::EC2::Route", "Properties": { "RouteTableId": { - "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { @@ -176,18 +196,18 @@ } } }, - "VPCPrivateSubnet2SubnetCFCDAA7A": { + "VPCPrivateSubnet1Subnet8BCA10E0": { "Type": "AWS::EC2::Subnet", "Properties": { "CidrBlock": "10.0.96.0/19", "VpcId": { "Ref": "VPCB9E5F0B4" }, - "AvailabilityZone": "test-region-1b", + "AvailabilityZone": "test-region-1a", "MapPublicIpOnLaunch": false } }, - "VPCPrivateSubnet2RouteTable0A19E10E": { + "VPCPrivateSubnet1RouteTableBE8A6027": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { @@ -195,41 +215,41 @@ } } }, - "VPCPrivateSubnet2RouteTableAssociatioinC31995B4": { + "VPCPrivateSubnet1RouteTableAssociatioin77F7CA18": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { - "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" }, "SubnetId": { - "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" } } }, - "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "VPCPrivateSubnet1DefaultRouteAE1D6490": { "Type": "AWS::EC2::Route", "Properties": { "RouteTableId": { - "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" }, "DestinationCidrBlock": "0.0.0.0/0", "NatGatewayId": { - "Ref": "VPCPublicSubnet2NATGateway3C070193" + "Ref": "VPCPublicSubnet1NATGatewayE0556630" } } }, - "VPCPublicSubnet3Subnet631C5E25": { + "VPCPrivateSubnet2SubnetCFCDAA7A": { "Type": "AWS::EC2::Subnet", "Properties": { "CidrBlock": "10.0.128.0/19", "VpcId": { "Ref": "VPCB9E5F0B4" }, - "AvailabilityZone": "test-region-1c", - "MapPublicIpOnLaunch": true + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false } }, - "VPCPublicSubnet3RouteTable98AE0E14": { + "VPCPrivateSubnet2RouteTable0A19E10E": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { @@ -237,46 +257,26 @@ } } }, - "VPCPublicSubnet3RouteTableAssociatioinF4E24B3B": { + "VPCPrivateSubnet2RouteTableAssociatioinC31995B4": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { - "Ref": "VPCPublicSubnet3RouteTable98AE0E14" - }, - "SubnetId": { - "Ref": "VPCPublicSubnet3Subnet631C5E25" - } - } - }, - "VPCPublicSubnet3EIPAD4BC883": { - "Type": "AWS::EC2::EIP", - "Properties": { - "Domain": "vpc" - } - }, - "VPCPublicSubnet3NATGatewayD3048F5C": { - "Type": "AWS::EC2::NatGateway", - "Properties": { - "AllocationId": { - "Fn::GetAtt": [ - "VPCPublicSubnet3EIPAD4BC883", - "AllocationId" - ] + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" }, "SubnetId": { - "Ref": "VPCPublicSubnet3Subnet631C5E25" + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" } } }, - "VPCPublicSubnet3DefaultRouteA0D29D46": { + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { "Type": "AWS::EC2::Route", "Properties": { "RouteTableId": { - "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" }, "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" + "NatGatewayId": { + "Ref": "VPCPublicSubnet2NATGateway3C070193" } } }, diff --git a/packages/@aws-cdk/ec2/test/integ.vpc.expected.json b/packages/@aws-cdk/ec2/test/integ.vpc.expected.json index 39d43110b2996..4bc476853450f 100644 --- a/packages/@aws-cdk/ec2/test/integ.vpc.expected.json +++ b/packages/@aws-cdk/ec2/test/integ.vpc.expected.json @@ -72,18 +72,18 @@ } } }, - "MyVpcPrivateSubnet1Subnet5057CF7E": { + "MyVpcPublicSubnet2Subnet492B6BFB": { "Type": "AWS::EC2::Subnet", "Properties": { "CidrBlock": "10.0.32.0/19", "VpcId": { "Ref": "MyVpcF9F0CA6F" }, - "AvailabilityZone": "test-region-1a", - "MapPublicIpOnLaunch": false + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true } }, - "MyVpcPrivateSubnet1RouteTable8819E6E2": { + "MyVpcPublicSubnet2RouteTable1DF17386": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { @@ -91,41 +91,61 @@ } } }, - "MyVpcPrivateSubnet1RouteTableAssociatioin90CF6BAB": { + "MyVpcPublicSubnet2RouteTableAssociatioin8E74FB35": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { - "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" }, "SubnetId": { - "Ref": "MyVpcPrivateSubnet1Subnet5057CF7E" + "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" } } }, - "MyVpcPrivateSubnet1DefaultRouteA8CDE2FA": { + "MyVpcPublicSubnet2EIP8CCBA239": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "MyVpcPublicSubnet2NATGateway91BFBEC9": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet2EIP8CCBA239", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" + } + } + }, + "MyVpcPublicSubnet2DefaultRoute052936F6": { "Type": "AWS::EC2::Route", "Properties": { "RouteTableId": { - "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" }, "DestinationCidrBlock": "0.0.0.0/0", - "NatGatewayId": { - "Ref": "MyVpcPublicSubnet1NATGatewayAD3400C1" + "GatewayId": { + "Ref": "MyVpcIGW5C4A4F63" } } }, - "MyVpcPublicSubnet2Subnet492B6BFB": { + "MyVpcPublicSubnet3Subnet57EEE236": { "Type": "AWS::EC2::Subnet", "Properties": { "CidrBlock": "10.0.64.0/19", "VpcId": { "Ref": "MyVpcF9F0CA6F" }, - "AvailabilityZone": "test-region-1b", + "AvailabilityZone": "test-region-1c", "MapPublicIpOnLaunch": true } }, - "MyVpcPublicSubnet2RouteTable1DF17386": { + "MyVpcPublicSubnet3RouteTable15028F08": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { @@ -133,42 +153,42 @@ } } }, - "MyVpcPublicSubnet2RouteTableAssociatioin8E74FB35": { + "MyVpcPublicSubnet3RouteTableAssociatioinA3FD1B71": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { - "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" + "Ref": "MyVpcPublicSubnet3RouteTable15028F08" }, "SubnetId": { - "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" + "Ref": "MyVpcPublicSubnet3Subnet57EEE236" } } }, - "MyVpcPublicSubnet2EIP8CCBA239": { + "MyVpcPublicSubnet3EIPC5ACADAB": { "Type": "AWS::EC2::EIP", "Properties": { "Domain": "vpc" } }, - "MyVpcPublicSubnet2NATGateway91BFBEC9": { + "MyVpcPublicSubnet3NATGatewayD4B50EBE": { "Type": "AWS::EC2::NatGateway", "Properties": { "AllocationId": { "Fn::GetAtt": [ - "MyVpcPublicSubnet2EIP8CCBA239", + "MyVpcPublicSubnet3EIPC5ACADAB", "AllocationId" ] }, "SubnetId": { - "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" + "Ref": "MyVpcPublicSubnet3Subnet57EEE236" } } }, - "MyVpcPublicSubnet2DefaultRoute052936F6": { + "MyVpcPublicSubnet3DefaultRoute3A83AB36": { "Type": "AWS::EC2::Route", "Properties": { "RouteTableId": { - "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" + "Ref": "MyVpcPublicSubnet3RouteTable15028F08" }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { @@ -176,18 +196,18 @@ } } }, - "MyVpcPrivateSubnet2Subnet0040C983": { + "MyVpcPrivateSubnet1Subnet5057CF7E": { "Type": "AWS::EC2::Subnet", "Properties": { "CidrBlock": "10.0.96.0/19", "VpcId": { "Ref": "MyVpcF9F0CA6F" }, - "AvailabilityZone": "test-region-1b", + "AvailabilityZone": "test-region-1a", "MapPublicIpOnLaunch": false } }, - "MyVpcPrivateSubnet2RouteTableCEDCEECE": { + "MyVpcPrivateSubnet1RouteTable8819E6E2": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { @@ -195,41 +215,41 @@ } } }, - "MyVpcPrivateSubnet2RouteTableAssociatioin803693C0": { + "MyVpcPrivateSubnet1RouteTableAssociatioin90CF6BAB": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { - "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" }, "SubnetId": { - "Ref": "MyVpcPrivateSubnet2Subnet0040C983" + "Ref": "MyVpcPrivateSubnet1Subnet5057CF7E" } } }, - "MyVpcPrivateSubnet2DefaultRoute9CE96294": { + "MyVpcPrivateSubnet1DefaultRouteA8CDE2FA": { "Type": "AWS::EC2::Route", "Properties": { "RouteTableId": { - "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" }, "DestinationCidrBlock": "0.0.0.0/0", "NatGatewayId": { - "Ref": "MyVpcPublicSubnet2NATGateway91BFBEC9" + "Ref": "MyVpcPublicSubnet1NATGatewayAD3400C1" } } }, - "MyVpcPublicSubnet3Subnet57EEE236": { + "MyVpcPrivateSubnet2Subnet0040C983": { "Type": "AWS::EC2::Subnet", "Properties": { "CidrBlock": "10.0.128.0/19", "VpcId": { "Ref": "MyVpcF9F0CA6F" }, - "AvailabilityZone": "test-region-1c", - "MapPublicIpOnLaunch": true + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false } }, - "MyVpcPublicSubnet3RouteTable15028F08": { + "MyVpcPrivateSubnet2RouteTableCEDCEECE": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { @@ -237,46 +257,26 @@ } } }, - "MyVpcPublicSubnet3RouteTableAssociatioinA3FD1B71": { + "MyVpcPrivateSubnet2RouteTableAssociatioin803693C0": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { - "Ref": "MyVpcPublicSubnet3RouteTable15028F08" - }, - "SubnetId": { - "Ref": "MyVpcPublicSubnet3Subnet57EEE236" - } - } - }, - "MyVpcPublicSubnet3EIPC5ACADAB": { - "Type": "AWS::EC2::EIP", - "Properties": { - "Domain": "vpc" - } - }, - "MyVpcPublicSubnet3NATGatewayD4B50EBE": { - "Type": "AWS::EC2::NatGateway", - "Properties": { - "AllocationId": { - "Fn::GetAtt": [ - "MyVpcPublicSubnet3EIPC5ACADAB", - "AllocationId" - ] + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" }, "SubnetId": { - "Ref": "MyVpcPublicSubnet3Subnet57EEE236" + "Ref": "MyVpcPrivateSubnet2Subnet0040C983" } } }, - "MyVpcPublicSubnet3DefaultRoute3A83AB36": { + "MyVpcPrivateSubnet2DefaultRoute9CE96294": { "Type": "AWS::EC2::Route", "Properties": { "RouteTableId": { - "Ref": "MyVpcPublicSubnet3RouteTable15028F08" + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" }, "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "MyVpcIGW5C4A4F63" + "NatGatewayId": { + "Ref": "MyVpcPublicSubnet2NATGateway91BFBEC9" } } }, diff --git a/packages/@aws-cdk/rds/test/integ.cluster.expected.json b/packages/@aws-cdk/rds/test/integ.cluster.expected.json index 538d39ad60c7f..4bd5e110dfb43 100644 --- a/packages/@aws-cdk/rds/test/integ.cluster.expected.json +++ b/packages/@aws-cdk/rds/test/integ.cluster.expected.json @@ -13,12 +13,12 @@ "VPCPublicSubnet1SubnetB4246D30": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1a", "CidrBlock": "10.0.0.0/18", - "MapPublicIpOnLaunch": true, "VpcId": { "Ref": "VPCB9E5F0B4" - } + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true } }, "VPCPublicSubnet1RouteTableFEE4B781": { @@ -63,66 +63,24 @@ "VPCPublicSubnet1DefaultRoute91CEF279": { "Type": "AWS::EC2::Route", "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { "Ref": "VPCIGWB7E252D3" - }, - "RouteTableId": { - "Ref": "VPCPublicSubnet1RouteTableFEE4B781" } } }, - "VPCPrivateSubnet1Subnet8BCA10E0": { + "VPCPublicSubnet2Subnet74179F39": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1a", "CidrBlock": "10.0.64.0/18", - "MapPublicIpOnLaunch": false, "VpcId": { "Ref": "VPCB9E5F0B4" - } - } - }, - "VPCPrivateSubnet1RouteTableBE8A6027": { - "Type": "AWS::EC2::RouteTable", - "Properties": { - "VpcId": { - "Ref": "VPCB9E5F0B4" - } - } - }, - "VPCPrivateSubnet1RouteTableAssociatioin77F7CA18": { - "Type": "AWS::EC2::SubnetRouteTableAssociation", - "Properties": { - "RouteTableId": { - "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" }, - "SubnetId": { - "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" - } - } - }, - "VPCPrivateSubnet1DefaultRouteAE1D6490": { - "Type": "AWS::EC2::Route", - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "NatGatewayId": { - "Ref": "VPCPublicSubnet1NATGatewayE0556630" - }, - "RouteTableId": { - "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" - } - } - }, - "VPCPublicSubnet2Subnet74179F39": { - "Type": "AWS::EC2::Subnet", - "Properties": { "AvailabilityZone": "test-region-1b", - "CidrBlock": "10.0.128.0/18", - "MapPublicIpOnLaunch": true, - "VpcId": { - "Ref": "VPCB9E5F0B4" - } + "MapPublicIpOnLaunch": true } }, "VPCPublicSubnet2RouteTable6F1A15F1": { @@ -167,24 +125,66 @@ "VPCPublicSubnet2DefaultRouteB7481BBA": { "Type": "AWS::EC2::Route", "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { "Ref": "VPCIGWB7E252D3" + } + } + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "VPCPrivateSubnet1RouteTableAssociatioin77F7CA18": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { "RouteTableId": { - "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" } } }, "VPCPrivateSubnet2SubnetCFCDAA7A": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1b", "CidrBlock": "10.0.192.0/18", - "MapPublicIpOnLaunch": false, "VpcId": { "Ref": "VPCB9E5F0B4" - } + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false } }, "VPCPrivateSubnet2RouteTable0A19E10E": { @@ -209,12 +209,12 @@ "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { "Type": "AWS::EC2::Route", "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, "DestinationCidrBlock": "0.0.0.0/0", "NatGatewayId": { "Ref": "VPCPublicSubnet2NATGateway3C070193" - }, - "RouteTableId": { - "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" } } }, @@ -224,11 +224,11 @@ "VPCVPCGW99B986DC": { "Type": "AWS::EC2::VPCGatewayAttachment", "Properties": { - "InternetGatewayId": { - "Ref": "VPCIGWB7E252D3" - }, "VpcId": { "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" } } }, @@ -260,6 +260,7 @@ "DatabaseSecurityGroupOpentotheworld94E9606E": { "Type": "AWS::EC2::SecurityGroupIngress", "Properties": { + "IpProtocol": "tcp", "CidrIp": "0.0.0.0/0", "Description": "Open to the world", "FromPort": { @@ -274,7 +275,6 @@ "GroupId" ] }, - "IpProtocol": "tcp", "ToPort": { "Fn::GetAtt": [ "DatabaseB269D8BB", @@ -286,12 +286,12 @@ "DatabaseB269D8BB": { "Type": "AWS::RDS::DBCluster", "Properties": { + "Engine": "aurora", "DBSubnetGroupName": { "Ref": "DatabaseSubnets56F17B9A" }, - "Engine": "aurora", - "MasterUserPassword": "7959866cacc02c2d243ecfe177464fe6", "MasterUsername": "admin", + "MasterUserPassword": "7959866cacc02c2d243ecfe177464fe6", "StorageEncrypted": false, "VpcSecurityGroupIds": [ { @@ -306,10 +306,10 @@ "DatabaseInstance1844F58FD": { "Type": "AWS::RDS::DBInstance", "Properties": { + "DBInstanceClass": "db.t2.small", "DBClusterIdentifier": { "Ref": "DatabaseB269D8BB" }, - "DBInstanceClass": "db.t2.small", "DBSubnetGroupName": { "Ref": "DatabaseSubnets56F17B9A" }, @@ -319,28 +319,16 @@ "DependsOn": [ "VPCB9E5F0B4", "VPCIGWB7E252D3", - "VPCVPCGW99B986DC", - "VPCPublicSubnet1SubnetB4246D30", - "VPCPublicSubnet1RouteTableFEE4B781", - "VPCPublicSubnet1RouteTableAssociatioin249B4093", - "VPCPrivateSubnet1Subnet8BCA10E0", - "VPCPrivateSubnet1RouteTableBE8A6027", - "VPCPrivateSubnet1RouteTableAssociatioin77F7CA18", - "VPCPublicSubnet2Subnet74179F39", - "VPCPublicSubnet2RouteTable6F1A15F1", - "VPCPublicSubnet2RouteTableAssociatioin766225D7", - "VPCPrivateSubnet2SubnetCFCDAA7A", - "VPCPrivateSubnet2RouteTable0A19E10E", - "VPCPrivateSubnet2RouteTableAssociatioinC31995B4" + "VPCVPCGW99B986DC" ] }, "DatabaseInstance2AA380DEE": { "Type": "AWS::RDS::DBInstance", "Properties": { + "DBInstanceClass": "db.t2.small", "DBClusterIdentifier": { "Ref": "DatabaseB269D8BB" }, - "DBInstanceClass": "db.t2.small", "DBSubnetGroupName": { "Ref": "DatabaseSubnets56F17B9A" }, @@ -350,19 +338,7 @@ "DependsOn": [ "VPCB9E5F0B4", "VPCIGWB7E252D3", - "VPCVPCGW99B986DC", - "VPCPublicSubnet1SubnetB4246D30", - "VPCPublicSubnet1RouteTableFEE4B781", - "VPCPublicSubnet1RouteTableAssociatioin249B4093", - "VPCPrivateSubnet1Subnet8BCA10E0", - "VPCPrivateSubnet1RouteTableBE8A6027", - "VPCPrivateSubnet1RouteTableAssociatioin77F7CA18", - "VPCPublicSubnet2Subnet74179F39", - "VPCPublicSubnet2RouteTable6F1A15F1", - "VPCPublicSubnet2RouteTableAssociatioin766225D7", - "VPCPrivateSubnet2SubnetCFCDAA7A", - "VPCPrivateSubnet2RouteTable0A19E10E", - "VPCPrivateSubnet2RouteTableAssociatioinC31995B4" + "VPCVPCGW99B986DC" ] } } From 2fbb2131d80b3bf78daa5eff0eeab54c1ef2f74c Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Wed, 18 Jul 2018 19:43:29 -0700 Subject: [PATCH 22/35] fixing route53 --- .../route53/test/integ.route53.expected.json | 190 +++++++++--------- 1 file changed, 95 insertions(+), 95 deletions(-) diff --git a/packages/@aws-cdk/route53/test/integ.route53.expected.json b/packages/@aws-cdk/route53/test/integ.route53.expected.json index e18a8e39b2be8..6383b4c17ebf5 100644 --- a/packages/@aws-cdk/route53/test/integ.route53.expected.json +++ b/packages/@aws-cdk/route53/test/integ.route53.expected.json @@ -13,12 +13,12 @@ "VPCPublicSubnet1SubnetB4246D30": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1a", "CidrBlock": "10.0.0.0/19", - "MapPublicIpOnLaunch": true, "VpcId": { "Ref": "VPCB9E5F0B4" - } + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true } }, "VPCPublicSubnet1RouteTableFEE4B781": { @@ -63,27 +63,27 @@ "VPCPublicSubnet1DefaultRoute91CEF279": { "Type": "AWS::EC2::Route", "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { "Ref": "VPCIGWB7E252D3" - }, - "RouteTableId": { - "Ref": "VPCPublicSubnet1RouteTableFEE4B781" } } }, - "VPCPrivateSubnet1Subnet8BCA10E0": { + "VPCPublicSubnet2Subnet74179F39": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1a", "CidrBlock": "10.0.32.0/19", - "MapPublicIpOnLaunch": false, "VpcId": { "Ref": "VPCB9E5F0B4" - } + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true } }, - "VPCPrivateSubnet1RouteTableBE8A6027": { + "VPCPublicSubnet2RouteTable6F1A15F1": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { @@ -91,41 +91,61 @@ } } }, - "VPCPrivateSubnet1RouteTableAssociatioin77F7CA18": { + "VPCPublicSubnet2RouteTableAssociatioin766225D7": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { - "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" }, "SubnetId": { - "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + "Ref": "VPCPublicSubnet2Subnet74179F39" } } }, - "VPCPrivateSubnet1DefaultRouteAE1D6490": { - "Type": "AWS::EC2::Route", + "VPCPublicSubnet2EIP4947BC00": { + "Type": "AWS::EC2::EIP", "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "NatGatewayId": { - "Ref": "VPCPublicSubnet1NATGatewayE0556630" + "Domain": "vpc" + } + }, + "VPCPublicSubnet2NATGateway3C070193": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet2EIP4947BC00", + "AllocationId" + ] }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { "RouteTableId": { - "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" } } }, - "VPCPublicSubnet2Subnet74179F39": { + "VPCPublicSubnet3Subnet631C5E25": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1b", "CidrBlock": "10.0.64.0/19", - "MapPublicIpOnLaunch": true, "VpcId": { "Ref": "VPCB9E5F0B4" - } + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true } }, - "VPCPublicSubnet2RouteTable6F1A15F1": { + "VPCPublicSubnet3RouteTable98AE0E14": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { @@ -133,61 +153,61 @@ } } }, - "VPCPublicSubnet2RouteTableAssociatioin766225D7": { + "VPCPublicSubnet3RouteTableAssociatioinF4E24B3B": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { - "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" }, "SubnetId": { - "Ref": "VPCPublicSubnet2Subnet74179F39" + "Ref": "VPCPublicSubnet3Subnet631C5E25" } } }, - "VPCPublicSubnet2EIP4947BC00": { + "VPCPublicSubnet3EIPAD4BC883": { "Type": "AWS::EC2::EIP", "Properties": { "Domain": "vpc" } }, - "VPCPublicSubnet2NATGateway3C070193": { + "VPCPublicSubnet3NATGatewayD3048F5C": { "Type": "AWS::EC2::NatGateway", "Properties": { "AllocationId": { "Fn::GetAtt": [ - "VPCPublicSubnet2EIP4947BC00", + "VPCPublicSubnet3EIPAD4BC883", "AllocationId" ] }, "SubnetId": { - "Ref": "VPCPublicSubnet2Subnet74179F39" + "Ref": "VPCPublicSubnet3Subnet631C5E25" } } }, - "VPCPublicSubnet2DefaultRouteB7481BBA": { + "VPCPublicSubnet3DefaultRouteA0D29D46": { "Type": "AWS::EC2::Route", "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { "Ref": "VPCIGWB7E252D3" - }, - "RouteTableId": { - "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" } } }, - "VPCPrivateSubnet2SubnetCFCDAA7A": { + "VPCPrivateSubnet1Subnet8BCA10E0": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1b", "CidrBlock": "10.0.96.0/19", - "MapPublicIpOnLaunch": false, "VpcId": { "Ref": "VPCB9E5F0B4" - } + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false } }, - "VPCPrivateSubnet2RouteTable0A19E10E": { + "VPCPrivateSubnet1RouteTableBE8A6027": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { @@ -195,41 +215,41 @@ } } }, - "VPCPrivateSubnet2RouteTableAssociatioinC31995B4": { + "VPCPrivateSubnet1RouteTableAssociatioin77F7CA18": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { - "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" }, "SubnetId": { - "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" } } }, - "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "VPCPrivateSubnet1DefaultRouteAE1D6490": { "Type": "AWS::EC2::Route", "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, "DestinationCidrBlock": "0.0.0.0/0", "NatGatewayId": { - "Ref": "VPCPublicSubnet2NATGateway3C070193" - }, - "RouteTableId": { - "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + "Ref": "VPCPublicSubnet1NATGatewayE0556630" } } }, - "VPCPublicSubnet3Subnet631C5E25": { + "VPCPrivateSubnet2SubnetCFCDAA7A": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1c", "CidrBlock": "10.0.128.0/19", - "MapPublicIpOnLaunch": true, "VpcId": { "Ref": "VPCB9E5F0B4" - } + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false } }, - "VPCPublicSubnet3RouteTable98AE0E14": { + "VPCPrivateSubnet2RouteTable0A19E10E": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { @@ -237,58 +257,38 @@ } } }, - "VPCPublicSubnet3RouteTableAssociatioinF4E24B3B": { + "VPCPrivateSubnet2RouteTableAssociatioinC31995B4": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "RouteTableId": { - "Ref": "VPCPublicSubnet3RouteTable98AE0E14" - }, - "SubnetId": { - "Ref": "VPCPublicSubnet3Subnet631C5E25" - } - } - }, - "VPCPublicSubnet3EIPAD4BC883": { - "Type": "AWS::EC2::EIP", - "Properties": { - "Domain": "vpc" - } - }, - "VPCPublicSubnet3NATGatewayD3048F5C": { - "Type": "AWS::EC2::NatGateway", - "Properties": { - "AllocationId": { - "Fn::GetAtt": [ - "VPCPublicSubnet3EIPAD4BC883", - "AllocationId" - ] + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" }, "SubnetId": { - "Ref": "VPCPublicSubnet3Subnet631C5E25" + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" } } }, - "VPCPublicSubnet3DefaultRouteA0D29D46": { + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { "Type": "AWS::EC2::Route", "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - }, "RouteTableId": { - "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet2NATGateway3C070193" } } }, "VPCPrivateSubnet3Subnet3EDCD457": { "Type": "AWS::EC2::Subnet", "Properties": { - "AvailabilityZone": "test-region-1c", "CidrBlock": "10.0.160.0/19", - "MapPublicIpOnLaunch": false, "VpcId": { "Ref": "VPCB9E5F0B4" - } + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false } }, "VPCPrivateSubnet3RouteTable192186F8": { @@ -313,12 +313,12 @@ "VPCPrivateSubnet3DefaultRoute27F311AE": { "Type": "AWS::EC2::Route", "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, "DestinationCidrBlock": "0.0.0.0/0", "NatGatewayId": { "Ref": "VPCPublicSubnet3NATGatewayD3048F5C" - }, - "RouteTableId": { - "Ref": "VPCPrivateSubnet3RouteTable192186F8" } } }, @@ -328,11 +328,11 @@ "VPCVPCGW99B986DC": { "Type": "AWS::EC2::VPCGatewayAttachment", "Properties": { - "InternetGatewayId": { - "Ref": "VPCIGWB7E252D3" - }, "VpcId": { "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" } } }, @@ -355,15 +355,15 @@ "PrivateZoneTXT83BB83CE": { "Type": "AWS::Route53::RecordSet", "Properties": { + "Name": "_foo.cdk.local.", + "Type": "TXT", "HostedZoneId": { "Ref": "PrivateZone27242E85" }, - "Name": "_foo.cdk.local.", "ResourceRecords": [ "\"Bar!\"" ], - "TTL": "60", - "Type": "TXT" + "TTL": "60" } }, "PublicZone2E1C4E34": { From 1777874771c8fc48c9034cdd087a6f15b5b289b2 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Thu, 19 Jul 2018 00:59:16 -0700 Subject: [PATCH 23/35] updating Changelog, README, and comments; reordered SubnetType to be alphabetical, added one more test case to validate examples in Readme --- CHANGELOG.md | 4 +- packages/@aws-cdk/ec2/README.md | 133 ++++++++++++++++++ packages/@aws-cdk/ec2/lib/vpc.ts | 29 ++-- .../@aws-cdk/ec2/test/test.network-utils.ts | 4 + 4 files changed, 156 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5561f5b1f22c7..5c900f6d0d04b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,14 @@ ## [UNRELEASED] - * CloudWatch: add `Metric.grantPutMetricData()` to give `PutMetricData` permissions to IAM +* CloudWatch: add `Metric.grantPutMetricData()` to give `PutMetricData` permissions to IAM identities. ([#258]) * [FIXED] `cdk docs` works but a message __Unknown command: docs__ is printed ([#256]) * Lambda (feature): add `role` parameter, making it possible to specify an externally defined execution role. +* VpcNetwork (BREAKING): add the ability customize subnet configurations ([#250]) [#258]: https://github.com/awslabs/aws-cdk/pull/258 +[#250]: https://github.com/awslabs/aws-cdk/pull/250 ## 0.7.3 - 2018-07-09 diff --git a/packages/@aws-cdk/ec2/README.md b/packages/@aws-cdk/ec2/README.md index 33dfbe0506e54..4b409be688bac 100644 --- a/packages/@aws-cdk/ec2/README.md +++ b/packages/@aws-cdk/ec2/README.md @@ -25,6 +25,139 @@ into all private subnets, and provide a parameter called `vpcPlacement` to allow you to override the placement. [Read more about subnets](https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Subnets.html). +If you require the ability to configure subnets the `VpcNetwork` can be +customized with `SubnetConfiguration` array. This is best explained by an +example: + +```ts +import { VpcNetwork } from '@aws-cdk/ec2'; + +new VpcNetwork(stack, 'TheVPC', { + cidr: '10.0.0.0/21', + subnetConfigurations: [ + { + cidrMask: 24, + name: 'Ingress', + subnetType: SubnetType.Public, + natGateway: true, + }, + { + cidrMask: 24, + name: 'Application', + subnetType: SubnetType.Private, + }, + { + cidrMask: 28, + name: 'Database', + subnetType: SubnetType.Internal, + } + ], +}); +``` + +The `VpcNetwork` from the above configuration in a Region with three +availability zones will be the following: + * IngressSubnet1: 10.0.0.0/24 + * IngressSubnet2: 10.0.1.0/24 + * IngressSubnet3: 10.0.2.0/24 + * ApplicaitonSubnet1: 10.0.3.0/24 + * ApplicaitonSubnet2: 10.0.4.0/24 + * ApplicaitonSubnet3: 10.0.5.0/24 + * DatabaseSubnet1: 10.0.6.0/28 + * DatabaseSubnet2: 10.0.6.16/28 + * DatabaseSubnet3: 10.0.6.32/28 + +There will be a NAT Gateway in each of the Ingress Subnets and each Application +Subnet will have a route to the NAT Gateway in it's respective availability +zone. The subnet numbers [1-3] will consistently map to the three availability +zones for the region. The Database Subnets will not have an outbound traffic +route and would be a good fit for managed data services like RDS, Elasticache, +and Redshift. + +Many times when you plan to build an application you don't know how many +instances of the application you will need and therefore you don't know how much +IP space to allocate. For example, you know the application will only have +Elasitc Loadbalancers in the public subnets and you know you will have 1-3 RDS +databases for your data tier, and the rest of the IP space should just be evenly +distributed for the application. This time you also want to only use two +availability zones. + +```ts +import { VpcNetwork } from '@aws-cdk/ec2'; + +new VpcNetwork(stack, 'TheVPC', { + cidr: '10.0.0.0/16', + subnetConfigurations: [ + { + cidrMask: 26, + name: 'Public', + subnetType: SubnetType.Public, + natGateway: true, + }, + { + name: 'Application', + subnetType: SubnetType.Private, + }, + { + cidrMask: 27, + name: 'Database', + subnetType: SubnetType.Internal, + } + ], +}); +``` + +The `VpcNetwork` from the above configuration in a Region with three +availability zones will be the following: + * PublicSubnet1: 10.0.0.0/26 + * PublicSubnet2: 10.0.0.64/26 + * PublicSubnet3: 10.0.2.128/26 + * DatabaseSubnet1: 10.0.0.192/27 + * DatabaseSubnet2: 10.0.0.224/27 + * DatabaseSubnet3: 10.0.1.0/24 + * ApplicaitonSubnet1: 10.0.64.0/18 + * ApplicaitonSubnet2: 10.0.128.0/18 + * ApplicaitonSubnet3: 10.0.192.0/18 + +Any subnet configuration without a `cidrMask` will be counted up and allocated +evenly across the remaining IP space. + +Teams may also become cost conscious and be willing to trade availability for +cost. For example, in your test environments perhaps you would like the same VPC +as production, but instead of 3 NAT Gateways you would like only 1. This will +save on the cost, but trade the 3 availability zone to a 1 for all egress +traffic. This can be accomplished with a single parameter configuration: + +```ts +import { VpcNetwork } from '@aws-cdk/ec2'; + +new VpcNetwork(stack, 'TheVPC', { + cidr: '10.0.0.0/16', + maxNatGateways: 1, + subnetConfigurations: [ + { + cidrMask: 26, + name: 'Public', + subnetType: SubnetType.Public, + natGateway: true, + }, + { + name: 'Application', + subnetType: SubnetType.Private, + }, + { + cidrMask: 27, + name: 'Database', + subnetType: SubnetType.Internal, + } + ], +}); +``` + +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. + ### Fleet A `Fleet` represents a number of instances on which you run your code. You diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index 5a22a1800f2ec..098318cff1fbe 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -127,14 +127,6 @@ export enum SubnetType { */ Internal = 1, - /** - * Public subnets route outbound traffic via an Internet Gateway - * - * If this is set and OutboundTrafficMode.None is configure an error - * will be thrown. - */ - Public = 2, - /** * Private subnets route outbound traffic via a NAT Gateway * @@ -143,7 +135,16 @@ export enum SubnetType { * experimental cost conscious accounts or accounts where HA outbound * traffic is not needed. */ - Private = 3 + Private = 2, + + /** + * Public subnets route outbound traffic via an Internet Gateway + * + * If this is set and OutboundTrafficMode.None is configure an error + * will be thrown. + */ + Public = 3 + } /** @@ -192,10 +193,12 @@ export class VpcNetwork extends VpcNetworkRef { public static readonly DEFAULT_CIDR_RANGE: string = '10.0.0.0/16'; /** - * Maximum Number of NAT Gateways used to control cost + * The default maximum number of NAT Gateways * * @link https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Appendix_Limits.html - * defaulting 256 is an arbitrary max that should be impracticel to reach + * defaulting 256 is an arbitrary max that should be impracticel to reach. + * This can be overriden using VpcNetworkProps when creating a VPCNetwork resource. + * e.g. new VpcResource(this, { maxNatGateways: 1 }) */ public static readonly MAX_NAT_GATEWAYS: number = 256; @@ -352,8 +355,8 @@ export class VpcNetwork extends VpcNetworkRef { } /** - * createSubnets takes a VPC, and creates a public and private subnet - * in each Availability Zone. + * createSubnets creates the subnets specified by the subnet configuration + * array or creates the `DEFAULT_SUBNETS` ocnfiguration */ private createSubnets() { const remainingSpaceSubnets: SubnetConfiguration[] = []; diff --git a/packages/@aws-cdk/ec2/test/test.network-utils.ts b/packages/@aws-cdk/ec2/test/test.network-utils.ts index ed0ad2cba8582..40ffb642d6d2a 100644 --- a/packages/@aws-cdk/ec2/test/test.network-utils.ts +++ b/packages/@aws-cdk/ec2/test/test.network-utils.ts @@ -182,6 +182,10 @@ export = { test.strictEqual(17, builder3.maskForRemainingSubnets(2)); const builder4 = new NetworkBuilder('10.0.0.0/16'); test.strictEqual(18, builder4.maskForRemainingSubnets(4)); + const builder5 = new NetworkBuilder('10.0.0.0/16'); + builder5.addSubnets(26, 3); + builder5.addSubnets(27, 3); + test.strictEqual(18, builder5.maskForRemainingSubnets(3)); test.done(); } } From 9d8067f7495a9d2c3964a5b640e47a99c9640e8e Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Thu, 19 Jul 2018 01:11:37 -0700 Subject: [PATCH 24/35] minor README in ec2 update --- packages/@aws-cdk/ec2/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/ec2/README.md b/packages/@aws-cdk/ec2/README.md index 4b409be688bac..a00eb3f232086 100644 --- a/packages/@aws-cdk/ec2/README.md +++ b/packages/@aws-cdk/ec2/README.md @@ -79,8 +79,7 @@ instances of the application you will need and therefore you don't know how much IP space to allocate. For example, you know the application will only have Elasitc Loadbalancers in the public subnets and you know you will have 1-3 RDS databases for your data tier, and the rest of the IP space should just be evenly -distributed for the application. This time you also want to only use two -availability zones. +distributed for the application. ```ts import { VpcNetwork } from '@aws-cdk/ec2'; @@ -114,7 +113,7 @@ availability zones will be the following: * PublicSubnet3: 10.0.2.128/26 * DatabaseSubnet1: 10.0.0.192/27 * DatabaseSubnet2: 10.0.0.224/27 - * DatabaseSubnet3: 10.0.1.0/24 + * DatabaseSubnet3: 10.0.1.0/27 * ApplicaitonSubnet1: 10.0.64.0/18 * ApplicaitonSubnet2: 10.0.128.0/18 * ApplicaitonSubnet3: 10.0.192.0/18 From d639de427a6ae48f961e239432e414cab4ca5f4e Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Thu, 19 Jul 2018 11:57:35 -0700 Subject: [PATCH 25/35] changes from the readme (impact to vpc.ts because the rename of the subnetConfigurations to subnetConfiguration --- packages/@aws-cdk/ec2/README.md | 35 +++++++++++++------ packages/@aws-cdk/ec2/lib/vpc.ts | 10 +++--- .../@aws-cdk/ec2/test/test.network-utils.ts | 3 +- packages/@aws-cdk/ec2/test/test.vpc.ts | 8 ++--- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/packages/@aws-cdk/ec2/README.md b/packages/@aws-cdk/ec2/README.md index a00eb3f232086..645b48d53d566 100644 --- a/packages/@aws-cdk/ec2/README.md +++ b/packages/@aws-cdk/ec2/README.md @@ -34,7 +34,7 @@ import { VpcNetwork } from '@aws-cdk/ec2'; new VpcNetwork(stack, 'TheVPC', { cidr: '10.0.0.0/21', - subnetConfigurations: [ + subnetConfiguration: [ { cidrMask: 24, name: 'Ingress', @@ -55,6 +55,9 @@ new VpcNetwork(stack, 'TheVPC', { }); ``` +The example above is one possible configuration, but the user can use the +constructs above to implement many other network configurations. + The `VpcNetwork` from the above configuration in a Region with three availability zones will be the following: * IngressSubnet1: 10.0.0.0/24 @@ -67,26 +70,36 @@ availability zones will be the following: * DatabaseSubnet2: 10.0.6.16/28 * DatabaseSubnet3: 10.0.6.32/28 -There will be a NAT Gateway in each of the Ingress Subnets and each Application -Subnet will have a route to the NAT Gateway in it's respective availability -zone. The subnet numbers [1-3] will consistently map to the three availability -zones for the region. The Database Subnets will not have an outbound traffic -route and would be a good fit for managed data services like RDS, Elasticache, -and Redshift. +Each `Public` Subnet will have a NAT Gateway. Each `Private` Subnet will have a +route to the NAT Gateway in the same availability zone. Each `Internal` subnet +will not have a route to the internet, but is routeable inside the VPC. The +numbers [1-3] will consistently map to availability zones (e.g. IngressSubnet1 +and ApplicaitonSubnet1 will be in the same avialbility zone). + +`Internal` Subnets provide simplified secure networking principles, but come at +an operational complexity. The lack of an internet route means that if you deploy +instances in this subnet you will not be able to patch from the internet, this is +commonly reffered to as +[fully baked images](https://aws.amazon.com/answers/configuration-management/aws-ami-design/). +Features such as +[cfn-signal](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-signal.html) +are also unavailable. Using these subnets for managed services (RDS, +Elasticache, Redshift) is a very practical use because the managed services do +not incur additional operational overhead. Many times when you plan to build an application you don't know how many instances of the application you will need and therefore you don't know how much IP space to allocate. For example, you know the application will only have -Elasitc Loadbalancers in the public subnets and you know you will have 1-3 RDS +Elastic Loadbalancers in the public subnets and you know you will have 1-3 RDS databases for your data tier, and the rest of the IP space should just be evenly -distributed for the application. +distributed for the application. ```ts import { VpcNetwork } from '@aws-cdk/ec2'; new VpcNetwork(stack, 'TheVPC', { cidr: '10.0.0.0/16', - subnetConfigurations: [ + subnetConfiguration: [ { cidrMask: 26, name: 'Public', @@ -133,7 +146,7 @@ import { VpcNetwork } from '@aws-cdk/ec2'; new VpcNetwork(stack, 'TheVPC', { cidr: '10.0.0.0/16', maxNatGateways: 1, - subnetConfigurations: [ + subnetConfiguration: [ { cidrMask: 26, name: 'Public', diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index 098318cff1fbe..418e999d57626 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -96,7 +96,7 @@ export interface VpcNetworkProps { * @default the VPC CIDR will be evenly divided between 1 public and 1 * private subnet per AZ */ - subnetConfigurations?: SubnetConfiguration[]; + subnetConfiguration?: SubnetConfiguration[]; } /** @@ -265,7 +265,7 @@ export class VpcNetwork extends VpcNetworkRef { /** * Subnet configurations for this VPC */ - private subnetConfigurations: SubnetConfiguration[] = []; + private subnetConfiguration: SubnetConfiguration[] = []; /** * Maximum AZs to Uses for this VPC @@ -316,10 +316,10 @@ export class VpcNetwork extends VpcNetworkRef { this.maxNatGateways = props.maxNatGateways || VpcNetwork.MAX_NAT_GATEWAYS; - this.subnetConfigurations = props.subnetConfigurations || VpcNetwork.DEFAULT_SUBNETS; + this.subnetConfiguration = props.subnetConfiguration || VpcNetwork.DEFAULT_SUBNETS; this.createSubnets(); - const allowOutbound = this.subnetConfigurations.filter( + const allowOutbound = this.subnetConfiguration.filter( (subnet) => (subnet.subnetType !== SubnetType.Internal)).length > 0; // Create an Internet Gateway and attach it (if the outbound traffic mode != None) @@ -363,7 +363,7 @@ export class VpcNetwork extends VpcNetworkRef { // Calculate number of public/private subnets based on number of AZs - for (const subnet of this.subnetConfigurations) { + for (const subnet of this.subnetConfiguration) { subnet.mapPublicIpOnLaunch = subnet.mapPublicIpOnLaunch || (subnet.subnetType === SubnetType.Public); diff --git a/packages/@aws-cdk/ec2/test/test.network-utils.ts b/packages/@aws-cdk/ec2/test/test.network-utils.ts index 40ffb642d6d2a..b17d562ebaa56 100644 --- a/packages/@aws-cdk/ec2/test/test.network-utils.ts +++ b/packages/@aws-cdk/ec2/test/test.network-utils.ts @@ -185,8 +185,7 @@ export = { const builder5 = new NetworkBuilder('10.0.0.0/16'); builder5.addSubnets(26, 3); builder5.addSubnets(27, 3); - test.strictEqual(18, builder5.maskForRemainingSubnets(3)); - test.done(); + test.strictEqual(18, builder5.maskForRemainingSubnets(3)); test.done(); } } }; diff --git a/packages/@aws-cdk/ec2/test/test.vpc.ts b/packages/@aws-cdk/ec2/test/test.vpc.ts index e3a1f7a8e7949..98f77f11ba0c2 100644 --- a/packages/@aws-cdk/ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/ec2/test/test.vpc.ts @@ -66,7 +66,7 @@ export = { "with only internal subnets, the VPC should not contain an IGW or NAT Gateways"(test: Test) { const stack = getTestStack(); new VpcNetwork(stack, 'TheVPC', { - subnetConfigurations: [ + subnetConfiguration: [ { subnetType: SubnetType.Internal, name: 'Internal', @@ -81,7 +81,7 @@ export = { "with no private subnets, the VPC should have an IGW but no NAT Gateways"(test: Test) { const stack = getTestStack(); new VpcNetwork(stack, 'TheVPC', { - subnetConfigurations: [ + subnetConfiguration: [ { subnetType: SubnetType.Public, name: 'Public', @@ -111,7 +111,7 @@ export = { const zones = new AvailabilityZoneProvider(stack).availabilityZones.length; new VpcNetwork(stack, 'TheVPC', { cidr: '10.0.0.0/21', - subnetConfigurations: [ + subnetConfiguration: [ { cidrMask: 24, name: 'ingress', @@ -151,7 +151,7 @@ export = { new VpcNetwork(stack, 'TheVPC', { cidr: '10.0.0.0/21', maxNatGateways: 2, - subnetConfigurations: [ + subnetConfiguration: [ { cidrMask: 24, name: 'ingress', From f2d4571812a110806243086d58d0d74a4e903185 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Thu, 19 Jul 2018 15:09:22 -0700 Subject: [PATCH 26/35] updating to fix `||` idiom; updating Changelog to explain breaking change --- CHANGELOG.md | 2 +- packages/@aws-cdk/ec2/lib/vpc.ts | 16 +- packages/@aws-cdk/ec2/package-lock.json | 3395 +---------------------- packages/@aws-cdk/ec2/test/test.vpc.ts | 20 + 4 files changed, 118 insertions(+), 3315 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c900f6d0d04b..ec154884e3fd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ * [FIXED] `cdk docs` works but a message __Unknown command: docs__ is printed ([#256]) * Lambda (feature): add `role` parameter, making it possible to specify an externally defined execution role. -* VpcNetwork (BREAKING): add the ability customize subnet configurations ([#250]) +* VpcNetwork (BREAKING): add the ability customize subnet configurations ([#250]). Subnet allocation was changed to improve IP space efficiency but this forces `VpcNetwork` instances to be replaced [#258]: https://github.com/awslabs/aws-cdk/pull/258 [#250]: https://github.com/awslabs/aws-cdk/pull/250 diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index 418e999d57626..1d90feabb8527 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -288,7 +288,7 @@ export class VpcNetwork extends VpcNetworkRef { throw new Error('To use DNS Hostnames, DNS Support must be enabled, however, it was explicitly disabled.'); } - const cidrBlock = props.cidr || VpcNetwork.DEFAULT_CIDR_RANGE; + const cidrBlock = ifUndefined(props.cidr, VpcNetwork.DEFAULT_CIDR_RANGE); this.networkBuilder = new NetworkBuilder(cidrBlock); const enableDnsHostnames = props.enableDnsHostnames == null ? true : props.enableDnsHostnames; @@ -314,13 +314,13 @@ export class VpcNetwork extends VpcNetworkRef { this.vpcId = this.resource.ref; this.dependencyElements.push(this.resource); - this.maxNatGateways = props.maxNatGateways || VpcNetwork.MAX_NAT_GATEWAYS; + this.maxNatGateways = ifUndefined(props.maxNatGateways, VpcNetwork.MAX_NAT_GATEWAYS); - this.subnetConfiguration = props.subnetConfiguration || VpcNetwork.DEFAULT_SUBNETS; + this.subnetConfiguration = ifUndefined(props.subnetConfiguration, VpcNetwork.DEFAULT_SUBNETS); this.createSubnets(); const allowOutbound = this.subnetConfiguration.filter( - (subnet) => (subnet.subnetType !== SubnetType.Internal)).length > 0; + subnet => (subnet.subnetType !== SubnetType.Internal)).length > 0; // Create an Internet Gateway and attach it (if the outbound traffic mode != None) if (allowOutbound) { @@ -364,8 +364,8 @@ export class VpcNetwork extends VpcNetworkRef { // Calculate number of public/private subnets based on number of AZs for (const subnet of this.subnetConfiguration) { - subnet.mapPublicIpOnLaunch = subnet.mapPublicIpOnLaunch || - (subnet.subnetType === SubnetType.Public); + subnet.mapPublicIpOnLaunch = ifUndefined(subnet.mapPublicIpOnLaunch, + (subnet.subnetType === SubnetType.Public)); if (subnet.cidrMask === undefined) { remainingSpaceSubnets.push(subnet); @@ -532,3 +532,7 @@ export class VpcPrivateSubnet extends VpcSubnet { this.addDefaultRouteToNAT(natGatewayId); } } + +function ifUndefined(value: T | undefined, defaultValue: T): T { + return value !== undefined ? value : defaultValue; +} diff --git a/packages/@aws-cdk/ec2/package-lock.json b/packages/@aws-cdk/ec2/package-lock.json index d8ee46ede1137..ee3d03594e763 100644 --- a/packages/@aws-cdk/ec2/package-lock.json +++ b/packages/@aws-cdk/ec2/package-lock.json @@ -1,3349 +1,128 @@ { - "requires": true, - "lockfileVersion": 1, - "dependencies": { - "@types/node": { - "version": "8.10.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.17.tgz", - "integrity": "sha512-3N3FRd/rA1v5glXjb90YdYUa+sOB7WrkU2rAhKZnF4TKD86Cym9swtulGuH0p9nxo7fP5woRNa8b0oFTpCO1bg==" - }, - "@types/nodeunit": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/nodeunit/-/nodeunit-0.0.30.tgz", - "integrity": "sha1-SNLCcZoRjHcjuDMGw+gAsRor9ng=" - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "1.0.3" - } - }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" - }, - "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" - }, - "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "bind-obj-methods": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bind-obj-methods/-/bind-obj-methods-1.0.0.tgz", - "integrity": "sha1-T1l5ysFXk633DkiBYeRj4gnKUJw=" - }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "requires": { - "hoek": "2.16.3" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" - }, - "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "clean-yaml-object": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz", - "integrity": "sha1-Y/sRDcLOGoTcIfbZM0h20BCui2g=" - }, - "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" - }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "requires": { - "delayed-stream": "1.0.0" - } - }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "coveralls": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-2.13.3.tgz", - "integrity": "sha512-iiAmn+l1XqRwNLXhW8Rs5qHZRFMYp9ZIPjEOVRpC/c4so6Y/f4/lFi0FfR5B9cCqgyhkJ5cZmbvcVRfP8MHchw==", - "requires": { - "js-yaml": "3.6.1", - "lcov-parse": "0.0.10", - "log-driver": "1.2.5", - "minimist": "1.2.0", - "request": "2.79.0" - }, - "dependencies": { - "js-yaml": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz", - "integrity": "sha1-bl/mfYsgXOTSL60Ft3geja3MSzA=", - "requires": { - "argparse": "1.0.10", - "esprima": "2.7.3" - } - } - } - }, - "cross-spawn": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", - "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", - "requires": { - "lru-cache": "4.1.3", - "which": "1.3.1" - } - }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "requires": { - "boom": "2.10.1" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "diff": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", - "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=" - }, - "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "ejs": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz", - "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" - }, - "events-to-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/events-to-array/-/events-to-array-1.1.2.tgz", - "integrity": "sha1-LUH1Y+H+QA7Uli/hpNXGp1Od9/Y=" - }, - "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "foreground-child": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", - "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", - "requires": { - "cross-spawn": "4.0.2", - "signal-exit": "3.0.2" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" - } - }, - "fs-exists-cached": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-exists-cached/-/fs-exists-cached-1.0.0.tgz", - "integrity": "sha1-zyVVTKBQ3EmuZla0HeQiWJidy84=" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "function-loop": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/function-loop/-/function-loop-1.0.1.tgz", - "integrity": "sha1-gHa7MF6OajzO7ikgdl8zDRkPNAw=" - }, - "generate-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", - "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=" - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "requires": { - "is-property": "1.0.2" - } - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "har-validator": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", - "requires": { - "chalk": "1.1.3", - "commander": "2.15.1", - "is-my-json-valid": "2.17.2", - "pinkie-promise": "2.0.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "2.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" - }, - "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", - "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.1", - "sshpk": "1.14.1" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "is-my-ip-valid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==" - }, - "is-my-json-valid": { - "version": "2.17.2", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz", - "integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==", - "requires": { - "generate-function": "2.0.0", - "generate-object-property": "1.2.0", - "is-my-ip-valid": "1.0.0", - "jsonpointer": "4.0.1", - "xtend": "4.0.1" - } - }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" - }, - "js-yaml": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", - "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", - "requires": { - "argparse": "1.0.10", - "esprima": "4.0.0" - }, - "dependencies": { - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==" - } - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsonpointer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } - } - }, - "lcov-parse": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", - "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=" - }, - "log-driver": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.5.tgz", - "integrity": "sha1-euTsJXMC/XkNVXyxDJcQDYV7AFY=" - }, - "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - }, - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "requires": { - "mime-db": "1.33.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "nodeunit": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/nodeunit/-/nodeunit-0.11.2.tgz", - "integrity": "sha512-rlr0Fgd66nLmWwgVFj40TZp5jo47/YqaPQtoHG78mt+DVQhaLhA8EJJYCf2lozgYplPv+jJMLt8bCP34zo05mQ==", - "requires": { - "ejs": "2.6.1", - "tap": "10.7.3" - } - }, - "nyc": { - "version": "11.8.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-11.8.0.tgz", - "integrity": "sha512-PUFq1PSsx5OinSk5g5aaZygcDdI3QQT5XUlbR9QRMihtMS6w0Gm8xj4BxmKeeAlpQXC5M2DIhH16Y+KejceivQ==", - "requires": { - "archy": "1.0.0", - "arrify": "1.0.1", - "caching-transform": "1.0.1", - "convert-source-map": "1.5.1", - "debug-log": "1.0.1", - "default-require-extensions": "1.0.0", - "find-cache-dir": "0.1.1", - "find-up": "2.1.0", - "foreground-child": "1.5.6", - "glob": "7.1.2", - "istanbul-lib-coverage": "1.2.0", - "istanbul-lib-hook": "1.1.0", - "istanbul-lib-instrument": "1.10.1", - "istanbul-lib-report": "1.1.3", - "istanbul-lib-source-maps": "1.2.3", - "istanbul-reports": "1.4.0", - "md5-hex": "1.3.0", - "merge-source-map": "1.1.0", - "micromatch": "3.1.10", - "mkdirp": "0.5.1", - "resolve-from": "2.0.0", - "rimraf": "2.6.2", - "signal-exit": "3.0.2", - "spawn-wrap": "1.4.2", - "test-exclude": "4.2.1", - "yargs": "11.1.0", - "yargs-parser": "8.1.0" - }, - "dependencies": { - "align-text": { - "version": "0.1.4", - "bundled": true, - "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" - } - }, - "amdefine": { - "version": "1.0.1", - "bundled": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true - }, - "ansi-styles": { - "version": "2.2.1", - "bundled": true - }, - "append-transform": { - "version": "0.4.0", - "bundled": true, - "requires": { - "default-require-extensions": "1.0.0" - } - }, - "archy": { - "version": "1.0.0", - "bundled": true - }, - "arr-diff": { - "version": "4.0.0", - "bundled": true - }, - "arr-flatten": { - "version": "1.1.0", - "bundled": true - }, - "arr-union": { - "version": "3.1.0", - "bundled": true - }, - "array-unique": { - "version": "0.3.2", - "bundled": true - }, - "arrify": { - "version": "1.0.1", - "bundled": true - }, - "assign-symbols": { - "version": "1.0.0", - "bundled": true - }, - "async": { - "version": "1.5.2", - "bundled": true - }, - "atob": { - "version": "2.1.1", - "bundled": true - }, - "babel-code-frame": { - "version": "6.26.0", - "bundled": true, - "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" - } - }, - "babel-generator": { - "version": "6.26.1", - "bundled": true, - "requires": { - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "detect-indent": "4.0.0", - "jsesc": "1.3.0", - "lodash": "4.17.10", - "source-map": "0.5.7", - "trim-right": "1.0.1" - } - }, - "babel-messages": { - "version": "6.23.0", - "bundled": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-runtime": { - "version": "6.26.0", - "bundled": true, - "requires": { - "core-js": "2.5.6", - "regenerator-runtime": "0.11.1" - } - }, - "babel-template": { - "version": "6.26.0", - "bundled": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "lodash": "4.17.10" - } - }, - "babel-traverse": { - "version": "6.26.0", - "bundled": true, - "requires": { - "babel-code-frame": "6.26.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "debug": "2.6.9", - "globals": "9.18.0", - "invariant": "2.2.4", - "lodash": "4.17.10" - } - }, - "babel-types": { - "version": "6.26.0", - "bundled": true, - "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.10", - "to-fast-properties": "1.0.3" - } - }, - "babylon": { - "version": "6.18.0", - "bundled": true - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "base": { - "version": "0.11.2", - "bundled": true, - "requires": { - "cache-base": "1.0.1", - "class-utils": "0.3.6", - "component-emitter": "1.2.1", - "define-property": "1.0.0", - "isobject": "3.0.1", - "mixin-deep": "1.3.1", - "pascalcase": "0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "bundled": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "bundled": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "bundled": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "bundled": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "bundled": true - }, - "kind-of": { - "version": "6.0.2", - "bundled": true - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "2.3.2", - "bundled": true, - "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "extend-shallow": "2.0.1", - "fill-range": "4.0.0", - "isobject": "3.0.1", - "repeat-element": "1.1.2", - "snapdragon": "0.8.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "bundled": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "builtin-modules": { - "version": "1.1.1", - "bundled": true - }, - "cache-base": { - "version": "1.0.1", - "bundled": true, - "requires": { - "collection-visit": "1.0.0", - "component-emitter": "1.2.1", - "get-value": "2.0.6", - "has-value": "1.0.0", - "isobject": "3.0.1", - "set-value": "2.0.0", - "to-object-path": "0.3.0", - "union-value": "1.0.0", - "unset-value": "1.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "bundled": true - } - } - }, - "caching-transform": { - "version": "1.0.1", - "bundled": true, - "requires": { - "md5-hex": "1.3.0", - "mkdirp": "0.5.1", - "write-file-atomic": "1.3.4" - } - }, - "camelcase": { - "version": "1.2.1", - "bundled": true, - "optional": true - }, - "center-align": { - "version": "0.1.3", - "bundled": true, - "optional": true, - "requires": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" - } - }, - "chalk": { - "version": "1.1.3", - "bundled": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "class-utils": { - "version": "0.3.6", - "bundled": true, - "requires": { - "arr-union": "3.1.0", - "define-property": "0.2.5", - "isobject": "3.0.1", - "static-extend": "0.1.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "bundled": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "isobject": { - "version": "3.0.1", - "bundled": true - } - } - }, - "cliui": { - "version": "2.1.0", - "bundled": true, - "optional": true, - "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", - "wordwrap": "0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.2", - "bundled": true, - "optional": true - } - } - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true - }, - "collection-visit": { - "version": "1.0.0", - "bundled": true, - "requires": { - "map-visit": "1.0.0", - "object-visit": "1.0.1" - } - }, - "commondir": { - "version": "1.0.1", - "bundled": true - }, - "component-emitter": { - "version": "1.2.1", - "bundled": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true - }, - "convert-source-map": { - "version": "1.5.1", - "bundled": true - }, - "copy-descriptor": { - "version": "0.1.1", - "bundled": true - }, - "core-js": { - "version": "2.5.6", - "bundled": true - }, - "cross-spawn": { - "version": "4.0.2", - "bundled": true, - "requires": { - "lru-cache": "4.1.3", - "which": "1.3.0" - } - }, - "debug": { - "version": "2.6.9", - "bundled": true, - "requires": { - "ms": "2.0.0" - } - }, - "debug-log": { - "version": "1.0.1", - "bundled": true - }, - "decamelize": { - "version": "1.2.0", - "bundled": true - }, - "decode-uri-component": { - "version": "0.2.0", - "bundled": true - }, - "default-require-extensions": { - "version": "1.0.0", - "bundled": true, - "requires": { - "strip-bom": "2.0.0" - } - }, - "define-property": { - "version": "2.0.2", - "bundled": true, - "requires": { - "is-descriptor": "1.0.2", - "isobject": "3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "bundled": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "bundled": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "bundled": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "bundled": true - }, - "kind-of": { - "version": "6.0.2", - "bundled": true - } - } - }, - "detect-indent": { - "version": "4.0.0", - "bundled": true, - "requires": { - "repeating": "2.0.1" - } - }, - "error-ex": { - "version": "1.3.1", - "bundled": true, - "requires": { - "is-arrayish": "0.2.1" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "bundled": true - }, - "esutils": { - "version": "2.0.2", - "bundled": true - }, - "execa": { - "version": "0.7.0", - "bundled": true, - "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "bundled": true, - "requires": { - "lru-cache": "4.1.3", - "shebang-command": "1.2.0", - "which": "1.3.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "bundled": true, - "requires": { - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "bundled": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "extend-shallow": { - "version": "2.0.1", - "bundled": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "bundled": true, - "requires": { - "assign-symbols": "1.0.0", - "is-extendable": "1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "bundled": true, - "requires": { - "is-plain-object": "2.0.4" - } - } - } - }, - "extglob": { - "version": "2.0.4", - "bundled": true, - "requires": { - "array-unique": "0.3.2", - "define-property": "1.0.0", - "expand-brackets": "2.1.4", - "extend-shallow": "2.0.1", - "fragment-cache": "0.2.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "bundled": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "extend-shallow": { - "version": "2.0.1", - "bundled": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "bundled": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "bundled": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "bundled": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - }, - "kind-of": { - "version": "6.0.2", - "bundled": true - } - } - }, - "fill-range": { - "version": "4.0.0", - "bundled": true, - "requires": { - "extend-shallow": "2.0.1", - "is-number": "3.0.0", - "repeat-string": "1.6.1", - "to-regex-range": "2.1.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "bundled": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "find-cache-dir": { - "version": "0.1.1", - "bundled": true, - "requires": { - "commondir": "1.0.1", - "mkdirp": "0.5.1", - "pkg-dir": "1.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "bundled": true, - "requires": { - "locate-path": "2.0.0" - } - }, - "for-in": { - "version": "1.0.2", - "bundled": true - }, - "foreground-child": { - "version": "1.5.6", - "bundled": true, - "requires": { - "cross-spawn": "4.0.2", - "signal-exit": "3.0.2" - } - }, - "fragment-cache": { - "version": "0.2.1", - "bundled": true, - "requires": { - "map-cache": "0.2.2" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true - }, - "get-caller-file": { - "version": "1.0.2", - "bundled": true - }, - "get-stream": { - "version": "3.0.0", - "bundled": true - }, - "get-value": { - "version": "2.0.6", - "bundled": true - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "globals": { - "version": "9.18.0", - "bundled": true - }, - "graceful-fs": { - "version": "4.1.11", - "bundled": true - }, - "handlebars": { - "version": "4.0.11", - "bundled": true, - "requires": { - "async": "1.5.2", - "optimist": "0.6.1", - "source-map": "0.4.4", - "uglify-js": "2.8.29" - }, - "dependencies": { - "source-map": { - "version": "0.4.4", - "bundled": true, - "requires": { - "amdefine": "1.0.1" - } - } - } - }, - "has-ansi": { - "version": "2.0.0", - "bundled": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "has-flag": { - "version": "1.0.0", - "bundled": true - }, - "has-value": { - "version": "1.0.0", - "bundled": true, - "requires": { - "get-value": "2.0.6", - "has-values": "1.0.0", - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "bundled": true - } - } - }, - "has-values": { - "version": "1.0.0", - "bundled": true, - "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "bundled": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "bundled": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "bundled": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "hosted-git-info": { - "version": "2.6.0", - "bundled": true - }, - "imurmurhash": { - "version": "0.1.4", - "bundled": true - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true - }, - "invariant": { - "version": "2.2.4", - "bundled": true, - "requires": { - "loose-envify": "1.3.1" - } - }, - "invert-kv": { - "version": "1.0.0", - "bundled": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "bundled": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "is-arrayish": { - "version": "0.2.1", - "bundled": true - }, - "is-buffer": { - "version": "1.1.6", - "bundled": true - }, - "is-builtin-module": { - "version": "1.0.0", - "bundled": true, - "requires": { - "builtin-modules": "1.1.1" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "bundled": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "is-descriptor": { - "version": "0.1.6", - "bundled": true, - "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "bundled": true - } - } - }, - "is-extendable": { - "version": "0.1.1", - "bundled": true - }, - "is-finite": { - "version": "1.0.2", - "bundled": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "bundled": true - }, - "is-number": { - "version": "3.0.0", - "bundled": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "is-odd": { - "version": "2.0.0", - "bundled": true, - "requires": { - "is-number": "4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "bundled": true - } - } - }, - "is-plain-object": { - "version": "2.0.4", - "bundled": true, - "requires": { - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "bundled": true - } - } - }, - "is-stream": { - "version": "1.1.0", - "bundled": true - }, - "is-utf8": { - "version": "0.2.1", - "bundled": true - }, - "is-windows": { - "version": "1.0.2", - "bundled": true - }, - "isarray": { - "version": "1.0.0", - "bundled": true - }, - "isexe": { - "version": "2.0.0", - "bundled": true - }, - "isobject": { - "version": "3.0.1", - "bundled": true - }, - "istanbul-lib-coverage": { - "version": "1.2.0", - "bundled": true - }, - "istanbul-lib-hook": { - "version": "1.1.0", - "bundled": true, - "requires": { - "append-transform": "0.4.0" - } - }, - "istanbul-lib-instrument": { - "version": "1.10.1", - "bundled": true, - "requires": { - "babel-generator": "6.26.1", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "istanbul-lib-coverage": "1.2.0", - "semver": "5.5.0" - } - }, - "istanbul-lib-report": { - "version": "1.1.3", - "bundled": true, - "requires": { - "istanbul-lib-coverage": "1.2.0", - "mkdirp": "0.5.1", - "path-parse": "1.0.5", - "supports-color": "3.2.3" - }, - "dependencies": { - "supports-color": { - "version": "3.2.3", - "bundled": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "1.2.3", - "bundled": true, - "requires": { - "debug": "3.1.0", - "istanbul-lib-coverage": "1.2.0", - "mkdirp": "0.5.1", - "rimraf": "2.6.2", - "source-map": "0.5.7" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "bundled": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "istanbul-reports": { - "version": "1.4.0", - "bundled": true, - "requires": { - "handlebars": "4.0.11" - } - }, - "js-tokens": { - "version": "3.0.2", - "bundled": true - }, - "jsesc": { - "version": "1.3.0", - "bundled": true - }, - "kind-of": { - "version": "3.2.2", - "bundled": true, - "requires": { - "is-buffer": "1.1.6" - } - }, - "lazy-cache": { - "version": "1.0.4", - "bundled": true, - "optional": true - }, - "lcid": { - "version": "1.0.0", - "bundled": true, - "requires": { - "invert-kv": "1.0.0" - } - }, - "load-json-file": { - "version": "1.1.0", - "bundled": true, - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "bundled": true, - "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" - }, - "dependencies": { - "path-exists": { - "version": "3.0.0", - "bundled": true - } - } - }, - "lodash": { - "version": "4.17.10", - "bundled": true - }, - "longest": { - "version": "1.0.1", - "bundled": true - }, - "loose-envify": { - "version": "1.3.1", - "bundled": true, - "requires": { - "js-tokens": "3.0.2" - } - }, - "lru-cache": { - "version": "4.1.3", - "bundled": true, - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - }, - "map-cache": { - "version": "0.2.2", - "bundled": true - }, - "map-visit": { - "version": "1.0.0", - "bundled": true, - "requires": { - "object-visit": "1.0.1" - } - }, - "md5-hex": { - "version": "1.3.0", - "bundled": true, - "requires": { - "md5-o-matic": "0.1.1" - } - }, - "md5-o-matic": { - "version": "0.1.1", - "bundled": true - }, - "mem": { - "version": "1.1.0", - "bundled": true, - "requires": { - "mimic-fn": "1.2.0" - } - }, - "merge-source-map": { - "version": "1.1.0", - "bundled": true, - "requires": { - "source-map": "0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "bundled": true - } - } - }, - "micromatch": { - "version": "3.1.10", - "bundled": true, - "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.2", - "nanomatch": "1.2.9", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "bundled": true - } - } - }, - "mimic-fn": { - "version": "1.2.0", - "bundled": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true - }, - "mixin-deep": { - "version": "1.3.1", - "bundled": true, - "requires": { - "for-in": "1.0.2", - "is-extendable": "1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "bundled": true, - "requires": { - "is-plain-object": "2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true - }, - "nanomatch": { - "version": "1.2.9", - "bundled": true, - "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "fragment-cache": "0.2.1", - "is-odd": "2.0.0", - "is-windows": "1.0.2", - "kind-of": "6.0.2", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "bundled": true - }, - "array-unique": { - "version": "0.3.2", - "bundled": true - }, - "kind-of": { - "version": "6.0.2", - "bundled": true - } - } - }, - "normalize-package-data": { - "version": "2.4.0", - "bundled": true, - "requires": { - "hosted-git-info": "2.6.0", - "is-builtin-module": "1.0.0", - "semver": "5.5.0", - "validate-npm-package-license": "3.0.3" - } - }, - "npm-run-path": { - "version": "2.0.2", - "bundled": true, - "requires": { - "path-key": "2.0.1" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true - }, - "object-copy": { - "version": "0.1.0", - "bundled": true, - "requires": { - "copy-descriptor": "0.1.1", - "define-property": "0.2.5", - "kind-of": "3.2.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "bundled": true, - "requires": { - "is-descriptor": "0.1.6" - } - } - } - }, - "object-visit": { - "version": "1.0.1", - "bundled": true, - "requires": { - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "bundled": true - } - } - }, - "object.pick": { - "version": "1.3.0", - "bundled": true, - "requires": { - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "bundled": true - } - } - }, - "once": { - "version": "1.4.0", - "bundled": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "optimist": { - "version": "0.6.1", - "bundled": true, - "requires": { - "minimist": "0.0.8", - "wordwrap": "0.0.3" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true - }, - "os-locale": { - "version": "2.1.0", - "bundled": true, - "requires": { - "execa": "0.7.0", - "lcid": "1.0.0", - "mem": "1.1.0" - } - }, - "p-finally": { - "version": "1.0.0", - "bundled": true - }, - "p-limit": { - "version": "1.2.0", - "bundled": true, - "requires": { - "p-try": "1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "bundled": true, - "requires": { - "p-limit": "1.2.0" - } - }, - "p-try": { - "version": "1.0.0", - "bundled": true - }, - "parse-json": { - "version": "2.2.0", - "bundled": true, - "requires": { - "error-ex": "1.3.1" - } - }, - "pascalcase": { - "version": "0.1.1", - "bundled": true - }, - "path-exists": { - "version": "2.1.0", - "bundled": true, - "requires": { - "pinkie-promise": "2.0.1" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true - }, - "path-key": { - "version": "2.0.1", - "bundled": true - }, - "path-parse": { - "version": "1.0.5", - "bundled": true - }, - "path-type": { - "version": "1.1.0", - "bundled": true, - "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - } - }, - "pify": { - "version": "2.3.0", - "bundled": true - }, - "pinkie": { - "version": "2.0.4", - "bundled": true - }, - "pinkie-promise": { - "version": "2.0.1", - "bundled": true, - "requires": { - "pinkie": "2.0.4" - } - }, - "pkg-dir": { - "version": "1.0.0", - "bundled": true, - "requires": { - "find-up": "1.1.2" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "bundled": true, - "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" - } - } - } - }, - "posix-character-classes": { - "version": "0.1.1", - "bundled": true - }, - "pseudomap": { - "version": "1.0.2", - "bundled": true - }, - "read-pkg": { - "version": "1.1.0", - "bundled": true, - "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "bundled": true, - "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "bundled": true, - "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" - } - } - } - }, - "regenerator-runtime": { - "version": "0.11.1", - "bundled": true - }, - "regex-not": { - "version": "1.0.2", - "bundled": true, - "requires": { - "extend-shallow": "3.0.2", - "safe-regex": "1.1.0" - } - }, - "repeat-element": { - "version": "1.1.2", - "bundled": true - }, - "repeat-string": { - "version": "1.6.1", - "bundled": true - }, - "repeating": { - "version": "2.0.1", - "bundled": true, - "requires": { - "is-finite": "1.0.2" - } - }, - "require-directory": { - "version": "2.1.1", - "bundled": true - }, - "require-main-filename": { - "version": "1.0.1", - "bundled": true - }, - "resolve-from": { - "version": "2.0.0", - "bundled": true - }, - "resolve-url": { - "version": "0.2.1", - "bundled": true - }, - "ret": { - "version": "0.1.15", - "bundled": true - }, - "right-align": { - "version": "0.1.3", - "bundled": true, - "optional": true, - "requires": { - "align-text": "0.1.4" - } - }, - "rimraf": { - "version": "2.6.2", - "bundled": true, - "requires": { - "glob": "7.1.2" - } - }, - "safe-regex": { - "version": "1.1.0", - "bundled": true, - "requires": { - "ret": "0.1.15" - } - }, - "semver": { - "version": "5.5.0", - "bundled": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true - }, - "set-value": { - "version": "2.0.0", - "bundled": true, - "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "split-string": "3.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "bundled": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "shebang-command": { - "version": "1.2.0", - "bundled": true, - "requires": { - "shebang-regex": "1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "bundled": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true - }, - "slide": { - "version": "1.1.6", - "bundled": true - }, - "snapdragon": { - "version": "0.8.2", - "bundled": true, - "requires": { - "base": "0.11.2", - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "map-cache": "0.2.2", - "source-map": "0.5.7", - "source-map-resolve": "0.5.1", - "use": "3.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "bundled": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "extend-shallow": { - "version": "2.0.1", - "bundled": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "bundled": true, - "requires": { - "define-property": "1.0.0", - "isobject": "3.0.1", - "snapdragon-util": "3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "bundled": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "bundled": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "bundled": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "bundled": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "bundled": true - }, - "kind-of": { - "version": "6.0.2", - "bundled": true - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "bundled": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "source-map": { - "version": "0.5.7", - "bundled": true - }, - "source-map-resolve": { - "version": "0.5.1", - "bundled": true, - "requires": { - "atob": "2.1.1", - "decode-uri-component": "0.2.0", - "resolve-url": "0.2.1", - "source-map-url": "0.4.0", - "urix": "0.1.0" - } - }, - "source-map-url": { - "version": "0.4.0", - "bundled": true - }, - "spawn-wrap": { - "version": "1.4.2", - "bundled": true, - "requires": { - "foreground-child": "1.5.6", - "mkdirp": "0.5.1", - "os-homedir": "1.0.2", - "rimraf": "2.6.2", - "signal-exit": "3.0.2", - "which": "1.3.0" - } - }, - "spdx-correct": { - "version": "3.0.0", - "bundled": true, - "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.1.0", - "bundled": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "bundled": true, - "requires": { - "spdx-exceptions": "2.1.0", - "spdx-license-ids": "3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.0", - "bundled": true - }, - "split-string": { - "version": "3.1.0", - "bundled": true, - "requires": { - "extend-shallow": "3.0.2" - } - }, - "static-extend": { - "version": "0.1.2", - "bundled": true, - "requires": { - "define-property": "0.2.5", - "object-copy": "0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "bundled": true, - "requires": { - "is-descriptor": "0.1.6" - } - } - } - }, - "string-width": { - "version": "2.1.1", - "bundled": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "bundled": true - }, - "strip-ansi": { - "version": "4.0.0", - "bundled": true, - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-bom": { - "version": "2.0.0", - "bundled": true, - "requires": { - "is-utf8": "0.2.1" - } - }, - "strip-eof": { - "version": "1.0.0", - "bundled": true - }, - "supports-color": { - "version": "2.0.0", - "bundled": true - }, - "test-exclude": { - "version": "4.2.1", - "bundled": true, - "requires": { - "arrify": "1.0.1", - "micromatch": "3.1.10", - "object-assign": "4.1.1", - "read-pkg-up": "1.0.1", - "require-main-filename": "1.0.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "bundled": true - }, - "array-unique": { - "version": "0.3.2", - "bundled": true - }, - "braces": { - "version": "2.3.2", - "bundled": true, - "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "extend-shallow": "2.0.1", - "fill-range": "4.0.0", - "isobject": "3.0.1", - "repeat-element": "1.1.2", - "snapdragon": "0.8.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "bundled": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "bundled": true, - "requires": { - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "bundled": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "extend-shallow": { - "version": "2.0.1", - "bundled": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "bundled": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "bundled": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "bundled": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "bundled": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "bundled": true, - "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - } - }, - "kind-of": { - "version": "5.1.0", - "bundled": true - } - } - }, - "extglob": { - "version": "2.0.4", - "bundled": true, - "requires": { - "array-unique": "0.3.2", - "define-property": "1.0.0", - "expand-brackets": "2.1.4", - "extend-shallow": "2.0.1", - "fragment-cache": "0.2.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "bundled": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "extend-shallow": { - "version": "2.0.1", - "bundled": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "bundled": true, - "requires": { - "extend-shallow": "2.0.1", - "is-number": "3.0.0", - "repeat-string": "1.6.1", - "to-regex-range": "2.1.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "bundled": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "bundled": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "bundled": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "bundled": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "bundled": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "bundled": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "bundled": true - }, - "kind-of": { - "version": "6.0.2", - "bundled": true - }, - "micromatch": { - "version": "3.1.10", - "bundled": true, - "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.2", - "nanomatch": "1.2.9", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - } - } - } - }, - "to-fast-properties": { - "version": "1.0.3", - "bundled": true - }, - "to-object-path": { - "version": "0.3.0", - "bundled": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "to-regex": { - "version": "3.0.2", - "bundled": true, - "requires": { - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "regex-not": "1.0.2", - "safe-regex": "1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "bundled": true, - "requires": { - "is-number": "3.0.0", - "repeat-string": "1.6.1" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "bundled": true, - "requires": { - "kind-of": "3.2.2" - } - } - } - }, - "trim-right": { - "version": "1.0.1", - "bundled": true - }, - "uglify-js": { - "version": "2.8.29", - "bundled": true, - "optional": true, - "requires": { - "source-map": "0.5.7", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" - }, - "dependencies": { - "yargs": { - "version": "3.10.0", - "bundled": true, - "optional": true, - "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", - "window-size": "0.1.0" - } - } - } - }, - "uglify-to-browserify": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "union-value": { - "version": "1.0.0", - "bundled": true, - "requires": { - "arr-union": "3.1.0", - "get-value": "2.0.6", - "is-extendable": "0.1.1", - "set-value": "0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "bundled": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "set-value": { - "version": "0.4.3", - "bundled": true, - "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "to-object-path": "0.3.0" - } - } - } - }, - "unset-value": { - "version": "1.0.0", - "bundled": true, - "requires": { - "has-value": "0.3.1", - "isobject": "3.0.1" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "bundled": true, - "requires": { - "get-value": "2.0.6", - "has-values": "0.1.4", - "isobject": "2.1.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "bundled": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "bundled": true - }, - "isobject": { - "version": "3.0.1", - "bundled": true - } - } - }, - "urix": { - "version": "0.1.0", - "bundled": true - }, - "use": { - "version": "3.1.0", - "bundled": true, - "requires": { - "kind-of": "6.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "bundled": true - } - } - }, - "validate-npm-package-license": { - "version": "3.0.3", - "bundled": true, - "requires": { - "spdx-correct": "3.0.0", - "spdx-expression-parse": "3.0.0" - } - }, - "which": { - "version": "1.3.0", - "bundled": true, - "requires": { - "isexe": "2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "bundled": true - }, - "window-size": { - "version": "0.1.0", - "bundled": true, - "optional": true - }, - "wordwrap": { - "version": "0.0.3", - "bundled": true - }, - "wrap-ansi": { - "version": "2.1.0", - "bundled": true, - "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true - }, - "write-file-atomic": { - "version": "1.3.4", - "bundled": true, - "requires": { - "graceful-fs": "4.1.11", - "imurmurhash": "0.1.4", - "slide": "1.1.6" - } - }, - "y18n": { - "version": "3.2.1", - "bundled": true - }, - "yallist": { - "version": "2.1.2", - "bundled": true - }, - "yargs": { - "version": "11.1.0", - "bundled": true, - "requires": { - "cliui": "4.1.0", - "decamelize": "1.2.0", - "find-up": "2.1.0", - "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "9.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "bundled": true - }, - "camelcase": { - "version": "4.1.0", - "bundled": true - }, - "cliui": { - "version": "4.1.0", - "bundled": true, - "requires": { - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "wrap-ansi": "2.1.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "bundled": true, - "requires": { - "ansi-regex": "3.0.0" - } - }, - "yargs-parser": { - "version": "9.0.2", - "bundled": true, - "requires": { - "camelcase": "4.1.0" - } - } - } - }, - "yargs-parser": { - "version": "8.1.0", - "bundled": true, - "requires": { - "camelcase": "4.1.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "bundled": true - } - } - } - } - }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1.0.2" - } - }, - "opener": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz", - "integrity": "sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=" - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "own-or": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/own-or/-/own-or-1.0.0.tgz", - "integrity": "sha1-Tod/vtqaLsgAD7wLyuOWRe6L+Nw=" - }, - "own-or-env": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-or-env/-/own-or-env-1.0.1.tgz", - "integrity": "sha512-y8qULRbRAlL6x2+M0vIe7jJbJx/kmUTzYonRAa2ayesR2qWLswninkVyeJe4x3IEXhdgoNodzjQRKAoEs6Fmrw==", - "requires": { - "own-or": "1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "2.0.4" - } - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "qs": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", - "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - }, - "request": { - "version": "2.79.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", - "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=", - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.7.0", - "caseless": "0.11.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "2.0.6", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "oauth-sign": "0.8.2", - "qs": "6.3.2", - "stringstream": "0.0.6", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.4.3", - "uuid": "3.2.1" - } - }, - "resolve": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", - "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", - "requires": { - "path-parse": "1.0.5" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", - "requires": { - "hoek": "2.16.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "sshpk": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", - "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } - } - }, - "stack-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.1.tgz", - "integrity": "sha1-1PM6tU6OOHeLDKXP07OvsS22hiA=" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "stringstream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", - "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==" - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "name": "@aws-cdk/ec2", + "version": "0.7.3-beta", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@aws-cdk/core": { + "version": "0.7.3-beta", + "resolved": "http://localhost:6000/@aws-cdk%2fcore/-/core-0.7.3-beta.tgz", + "integrity": "sha512-0qBmEw31mhgrJ1rVWdFBWLMHYKiEXbxnO+sfQevnvlxo/iFj2LjFzDONTuBsbLNM2PjDl3yRvCumAhh65q511Q==", "requires": { - "ansi-regex": "2.1.1" + "@aws-cdk/cx-api": "^0.7.3-beta+eb3f31a", + "js-base64": "^2.4.5", + "json-diff": "^0.3.1" } }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + "@aws-cdk/cx-api": { + "version": "0.7.3-beta", + "resolved": "http://localhost:6000/@aws-cdk%2fcx-api/-/cx-api-0.7.3-beta.tgz", + "integrity": "sha512-DqHLgvbT7V9nB1sqa1fWWNEMWYgUjFaN6BWUoRiJOEdavUEdUYQT+6lpvM5RrF1cFP6zS4ussSTI+MsRRHltOg==" }, - "tap": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/tap/-/tap-10.7.3.tgz", - "integrity": "sha512-oS/FIq+tcmxVgYn5usKtLsX+sOHNEj+G7JIQE9SBjO5mVYB1rbaEJJiDbnYp8k0ZqY2Pe4HbYEpkvzm9jfLDyw==", + "@aws-cdk/resources": { + "version": "0.7.3-beta", + "resolved": "http://localhost:6000/@aws-cdk%2fresources/-/resources-0.7.3-beta.tgz", + "integrity": "sha512-CBgq8E3ZCkcUvQB4vowwTp5KfRbQyLUA+mByCA4OPwwb8LnnlN4mb3Ho88UmJZx43mzzXgtp6sabOre6mEyhKw==", "requires": { - "bind-obj-methods": "1.0.0", - "bluebird": "3.5.1", - "clean-yaml-object": "0.1.0", - "color-support": "1.1.3", - "coveralls": "2.13.3", - "foreground-child": "1.5.6", - "fs-exists-cached": "1.0.0", - "function-loop": "1.0.1", - "glob": "7.1.2", - "isexe": "2.0.0", - "js-yaml": "3.11.0", - "nyc": "11.8.0", - "opener": "1.4.3", - "os-homedir": "1.0.2", - "own-or": "1.0.0", - "own-or-env": "1.0.1", - "readable-stream": "2.3.6", - "signal-exit": "3.0.2", - "source-map-support": "0.4.18", - "stack-utils": "1.0.1", - "tap-mocha-reporter": "3.0.7", - "tap-parser": "5.4.0", - "tmatch": "3.1.0", - "trivial-deferred": "1.0.1", - "tsame": "1.1.2", - "yapool": "1.0.0" + "@aws-cdk/core": "^0.7.3-beta+eb3f31a" }, "dependencies": { - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "@aws-cdk/core": { + "version": "0.7.3-beta", + "resolved": "http://localhost:6000/@aws-cdk%2fcore/-/core-0.7.3-beta.tgz", + "integrity": "sha512-0qBmEw31mhgrJ1rVWdFBWLMHYKiEXbxnO+sfQevnvlxo/iFj2LjFzDONTuBsbLNM2PjDl3yRvCumAhh65q511Q==", "requires": { - "source-map": "0.5.7" + "@aws-cdk/cx-api": "^0.7.3-beta+eb3f31a", + "js-base64": "^2.4.5", + "json-diff": "^0.3.1" } } } }, - "tap-mocha-reporter": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/tap-mocha-reporter/-/tap-mocha-reporter-3.0.7.tgz", - "integrity": "sha512-GHVXJ38C3oPRpM3YUc43JlGdpVZYiKeT1fmAd3HH2+J+ZWwsNAUFvRRdoGsXLw9+gU9o+zXpBqhS/oXyRQYwlA==", - "requires": { - "color-support": "1.1.3", - "debug": "2.6.9", - "diff": "1.4.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "js-yaml": "3.11.0", - "readable-stream": "2.3.6", - "tap-parser": "5.4.0", - "unicode-length": "1.0.3" - } - }, - "tap-parser": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-5.4.0.tgz", - "integrity": "sha512-BIsIaGqv7uTQgTW1KLTMNPSEQf4zDDPgYOBRdgOfuB+JFOLRBfEu6cLa/KvMvmqggu1FKXDfitjLwsq4827RvA==", - "requires": { - "events-to-array": "1.1.2", - "js-yaml": "3.11.0", - "readable-stream": "2.3.6" - } - }, - "tmatch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-3.1.0.tgz", - "integrity": "sha512-W3MSATOCN4pVu2qFxmJLIArSifeSOFqnfx9hiUaVgOmeRoI2NbU7RNga+6G+L8ojlFeQge+ZPCclWyUpQ8UeNQ==" - }, - "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", - "requires": { - "punycode": "1.4.1" - } - }, - "trivial-deferred": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trivial-deferred/-/trivial-deferred-1.0.1.tgz", - "integrity": "sha1-N21NKdlR1jaKb3oK6FwvTV4GWPM=" - }, - "tsame": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/tsame/-/tsame-1.1.2.tgz", - "integrity": "sha512-ovCs24PGjmByVPr9tSIOs/yjUX9sJl0grEmOsj9dZA/UknQkgPOKcUqM84aSCvt9awHuhc/boMzTg3BHFalxWw==" - }, - "tslib": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.1.tgz", - "integrity": "sha512-avfPS28HmGLLc2o4elcc2EIq2FcH++Yo5YxpBZi9Yw93BCTGFthI4HPE4Rpep6vSYQaK8e69PelM44tPj+RaQg==" - }, - "tslint": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.10.0.tgz", - "integrity": "sha1-EeJrzLiK+gLdDZlWyuPUVAtfVMM=", + "@aws-cdk/util": { + "version": "0.7.3-beta", + "resolved": "http://localhost:6000/@aws-cdk%2futil/-/util-0.7.3-beta.tgz", + "integrity": "sha512-8ti2+JhXYx686Aa1Yrny+FGbyqp1CPxFuWLGXvg6nza5xRcYgw2Nrobx3LiKmvEyjZj7BLozG28NXCwmO0S6zQ==", "requires": { - "babel-code-frame": "6.26.0", - "builtin-modules": "1.1.1", - "chalk": "2.4.1", - "commander": "2.15.1", - "diff": "3.5.0", - "glob": "7.1.2", - "js-yaml": "3.11.0", - "minimatch": "3.0.4", - "resolve": "1.7.1", - "semver": "5.5.0", - "tslib": "1.9.1", - "tsutils": "2.27.1" + "@aws-cdk/core": "^0.7.3-beta+eb3f31a", + "@aws-cdk/iam": "^0.7.3-beta+eb3f31a", + "@aws-cdk/resources": "^0.7.3-beta+eb3f31a" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "1.9.1" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "@aws-cdk/core": { + "version": "0.7.3-beta", + "resolved": "http://localhost:6000/@aws-cdk%2fcore/-/core-0.7.3-beta.tgz", + "integrity": "sha512-0qBmEw31mhgrJ1rVWdFBWLMHYKiEXbxnO+sfQevnvlxo/iFj2LjFzDONTuBsbLNM2PjDl3yRvCumAhh65q511Q==", "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "@aws-cdk/cx-api": "^0.7.3-beta+eb3f31a", + "js-base64": "^2.4.5", + "json-diff": "^0.3.1" } }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "@aws-cdk/iam": { + "version": "0.7.3-beta", + "resolved": "http://localhost:6000/@aws-cdk%2fiam/-/iam-0.7.3-beta.tgz", + "integrity": "sha512-ME4TLHGeCnCvEW/+ds/rRL6ZF5CzvLfE/8qtKn/Ed/AXo1u/uJxAjp8fHufvmmx9Vrugk8FKr0AUZJSALdaMEQ==", "requires": { - "has-flag": "3.0.0" + "@aws-cdk/core": "^0.7.3-beta+eb3f31a", + "@aws-cdk/resources": "^0.7.3-beta+eb3f31a" } } } }, - "tsutils": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.27.1.tgz", - "integrity": "sha512-AE/7uzp32MmaHvNNFES85hhUDHFdFZp6OAiZcd6y4ZKKIg6orJTm8keYWBhIhrJQH3a4LzNKat7ZPXZt5aTf6w==", + "cli-color": { + "version": "0.1.7", + "resolved": "http://localhost:6000/cli-color/-/cli-color-0.1.7.tgz", + "integrity": "sha1-rcMgD6RxzCEbDaf1ZrcemLnWc0c=", "requires": { - "tslib": "1.9.1" + "es5-ext": "0.8.x" } }, - "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true - }, - "unicode-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/unicode-length/-/unicode-length-1.0.3.tgz", - "integrity": "sha1-Wtp6f+1RhBpBijKM8UlHisg1irs=", + "difflib": { + "version": "0.2.4", + "resolved": "http://localhost:6000/difflib/-/difflib-0.2.4.tgz", + "integrity": "sha1-teMDYabbAjF21WKJLbhZQKcY9H4=", "requires": { - "punycode": "1.4.1", - "strip-ansi": "3.0.1" + "heap": ">= 0.2.0" } }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dreamopt": { + "version": "0.6.0", + "resolved": "http://localhost:6000/dreamopt/-/dreamopt-0.6.0.tgz", + "integrity": "sha1-2BPM2sjTnYrVJndVFKE92mZNa0s=", "requires": { - "assert-plus": "1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "1.3.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } + "wordwrap": ">=0.0.2" } }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "2.0.0" - } + "es5-ext": { + "version": "0.8.2", + "resolved": "http://localhost:6000/es5-ext/-/es5-ext-0.8.2.tgz", + "integrity": "sha1-q6jZ4ZQ6iVrJaDemKjmz9V7NlKs=" }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "heap": { + "version": "0.2.6", + "resolved": "http://localhost:6000/heap/-/heap-0.2.6.tgz", + "integrity": "sha1-CH4fELBGky/IWU3Z5tN4r8nR5aw=" }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + "js-base64": { + "version": "2.4.8", + "resolved": "http://localhost:6000/js-base64/-/js-base64-2.4.8.tgz", + "integrity": "sha512-hm2nYpDrwoO/OzBhdcqs/XGT6XjSuSSCVEpia+Kl2J6x4CYt5hISlVL/AYU1khoDXv0AQVgxtdJySb9gjAn56Q==" }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "json-diff": { + "version": "0.3.1", + "resolved": "http://localhost:6000/json-diff/-/json-diff-0.3.1.tgz", + "integrity": "sha1-bbw64tJeB1p/1xvNmHRFhmb7aBs=", + "requires": { + "cli-color": "~0.1.6", + "difflib": "~0.2.1", + "dreamopt": "~0.6.0" + } }, - "yapool": { + "wordwrap": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yapool/-/yapool-1.0.0.tgz", - "integrity": "sha1-9pPymjFbUNmp2iZGp6ZkXJaYW2o=" + "resolved": "http://localhost:6000/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" } } } diff --git a/packages/@aws-cdk/ec2/test/test.vpc.ts b/packages/@aws-cdk/ec2/test/test.vpc.ts index 98f77f11ba0c2..a08bdde18cd35 100644 --- a/packages/@aws-cdk/ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/ec2/test/test.vpc.ts @@ -194,6 +194,26 @@ export = { })); test.done(); }, + "with mapPublicIpOnLaunch false for public subnets"(test: Test) { + const stack = getTestStack(); + new VpcNetwork(stack, 'VPC', { + maxAZs: 1, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'ingress', + subnetType: SubnetType.Public, + mapPublicIpOnLaunch: false, + natGateway: true, + } + ], + }); + expect(stack).to(countResources("AWS::EC2::Subnet", 1)); + expect(stack).to(haveResource("AWS::EC2::Subnet", { + MapPublicIpOnLaunch: false + })); + test.done(); + }, "with maxAZs set to 2"(test: Test) { const stack = getTestStack(); new VpcNetwork(stack, 'VPC', { maxAZs: 2 }); From 5c1bc5da0112b7f2740a271afbd0d7b05e5817c7 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Thu, 19 Jul 2018 18:01:12 -0700 Subject: [PATCH 27/35] addressing code comments and tests for natGateway default behavior --- packages/@aws-cdk/ec2/lib/vpc.ts | 134 ++++++++++++++++--------- packages/@aws-cdk/ec2/test/test.vpc.ts | 4 +- 2 files changed, 91 insertions(+), 47 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index 1d90feabb8527..08a45878c4048 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -75,24 +75,28 @@ export interface VpcNetworkProps { * * For example if you want 1 public subnet, 1 private subnet, and 1 internal * subnet in each AZ provide the following: - * subnets: [ - * { - * cidrMask: 24, - * name: application, - * subnetType: SubnetType.Private, - * }, - * { - * cidrMask: 26, - * name: ingress, - * subnetType: SubnetType.Public, - * natGateway: true, - * }, - * { - * cidrMask: 28, - * name: rds, - * subnetType: SubnetType.Internal, - * } + * subnetConfiguration: [ + * { + * cidrMask: 24, + * name: 'ingress', + * subnetType: SubnetType.Public, + * natGateway: true, + * }, + * { + * cidrMask: 24, + * name: 'application', + * subnetType: SubnetType.Private, + * }, + * { + * cidrMask: 28, + * name: 'rds', + * subnetType: SubnetType.Internal, + * } * ] + * + * `cidrMask` is optional and if not provided the IP space in the VPC will be + * evenly divided between the requested subnets. + * * @default the VPC CIDR will be evenly divided between 1 public and 1 * private subnet per AZ */ @@ -128,9 +132,12 @@ export enum SubnetType { Internal = 1, /** - * Private subnets route outbound traffic via a NAT Gateway + * Subnet that routes to the internet, but not vice versa. + * + * Instances in a private subnet can connect to the Internet, but will not + * allow connections to be initiated from the Internet. * - * Outbound traffic will be routed via a NAT Gateways preference being in + * Outbound traffic will be routed via a NAT Gateway. Preference being in * the same AZ, but if not available will use another AZ. This is common for * experimental cost conscious accounts or accounts where HA outbound * traffic is not needed. @@ -138,10 +145,12 @@ export enum SubnetType { Private = 2, /** - * Public subnets route outbound traffic via an Internet Gateway + * Subnet connected to the Internet * - * If this is set and OutboundTrafficMode.None is configure an error - * will be thrown. + * Instances in a Public subnet can connect to the Internet and can be + * connected to from the Internet as long as they are launched with public IPs. + * + * Public subnets route outbound traffic via an Internet Gateway. */ Public = 3 @@ -151,15 +160,44 @@ export enum SubnetType { * Specify configuration parameters for a VPC to be built */ export interface SubnetConfiguration { - // the cidr mask value from 16-28 + /** + * The CIDR Mask or the number of leading 1 bits in the routing mask + * + * Valid values are 16 - 28 + */ cidrMask?: number; - // Public (IGW), Private (Nat GW), Internal (no outbound) + + /** + * The type of Subnet to configure. + * + * The Subnet type will control the ability to route and connect to the + * Internet. + */ subnetType: SubnetType; - // name that will be used to generate an AZ specific name e.g. name-2a + + /** + * The common Logical Name for the `VpcSubnet` + * + * Thi name will be suffixed with an integer correlating to a specific + * availability zone. + */ name: string; - // if true will place a NAT Gateway in this subnet, subnetType must be Public + + /** + * Controls the creation a NAT Gateway in this subnet + * + * If true will place a NAT Gateway in this subnet, subnetType must be Public. + * Defaults to true in Subnet.Public, false in Subnet.Private or Subnet.Internal. + * NAT Gateways in non-public subnets will functionally not work and are + * therefore not possible to create. + */ natGateway?: boolean; - // defaults to true in Subnet.Public, false in Subnet.Private or Subnet.Internal + + /** + * Controls if a public IP is associated to an instance at launch + * + * Defaults to true in Subnet.Public, false in Subnet.Private or Subnet.Internal. + */ mapPublicIpOnLaunch?: boolean; } @@ -193,17 +231,7 @@ export class VpcNetwork extends VpcNetworkRef { public static readonly DEFAULT_CIDR_RANGE: string = '10.0.0.0/16'; /** - * The default maximum number of NAT Gateways - * - * @link https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Appendix_Limits.html - * defaulting 256 is an arbitrary max that should be impracticel to reach. - * This can be overriden using VpcNetworkProps when creating a VPCNetwork resource. - * e.g. new VpcResource(this, { maxNatGateways: 1 }) - */ - public static readonly MAX_NAT_GATEWAYS: number = 256; - - /** - * The deafult subnet configuration + * The default subnet configuration * * 1 Public and 1 Private subnet per AZ evenly split */ @@ -243,7 +271,7 @@ export class VpcNetwork extends VpcNetworkRef { /** * Maximum Number of NAT Gateways used to control cost * - * @default {VpcNetwork.MAX_NAT_GATEWAYS} + * @default {VpcNetworkProps.maxAZs} */ private readonly maxNatGateways: number; @@ -314,7 +342,7 @@ export class VpcNetwork extends VpcNetworkRef { this.vpcId = this.resource.ref; this.dependencyElements.push(this.resource); - this.maxNatGateways = ifUndefined(props.maxNatGateways, VpcNetwork.MAX_NAT_GATEWAYS); + this.maxNatGateways = ifUndefined(props.maxNatGateways, this.availabilityZones.length); this.subnetConfiguration = ifUndefined(props.subnetConfiguration, VpcNetwork.DEFAULT_SUBNETS); this.createSubnets(); @@ -322,7 +350,7 @@ export class VpcNetwork extends VpcNetworkRef { const allowOutbound = this.subnetConfiguration.filter( subnet => (subnet.subnetType !== SubnetType.Internal)).length > 0; - // Create an Internet Gateway and attach it (if the outbound traffic mode != None) + // Create an Internet Gateway and attach it if necessary if (allowOutbound) { const igw = new cloudformation.InternetGatewayResource(this, 'IGW'); const att = new cloudformation.VPCGatewayAttachmentResource(this, 'VPCGW', { @@ -364,9 +392,6 @@ export class VpcNetwork extends VpcNetworkRef { // Calculate number of public/private subnets based on number of AZs for (const subnet of this.subnetConfiguration) { - subnet.mapPublicIpOnLaunch = ifUndefined(subnet.mapPublicIpOnLaunch, - (subnet.subnetType === SubnetType.Public)); - if (subnet.cidrMask === undefined) { remainingSpaceSubnets.push(subnet); continue; @@ -388,7 +413,8 @@ export class VpcNetwork extends VpcNetworkRef { availabilityZone: zone, vpcId: this.vpcId, cidrBlock: this.networkBuilder.addSubnet(cidrMask), - mapPublicIpOnLaunch: subnetConfig.mapPublicIpOnLaunch + mapPublicIpOnLaunch: ifUndefined(subnetConfig.mapPublicIpOnLaunch, + (subnetConfig.subnetType === SubnetType.Public)), }; switch (subnetConfig.subnetType) { @@ -419,9 +445,27 @@ export class VpcNetwork extends VpcNetworkRef { * Specify configuration parameters for a VPC subnet */ export interface VpcSubnetProps { + + /** + * The availability zone for the subnet + */ availabilityZone: string; + + /** + * The VPC which this subnet is part of + */ vpcId: Token; + + /** + * The CIDR notation for this subnet + */ cidrBlock: string; + + /** + * Controls if a public IP is associated to an instance at launch + * + * Defaults to true in Subnet.Public, false in Subnet.Private or Subnet.Internal. + */ mapPublicIpOnLaunch?: boolean; } diff --git a/packages/@aws-cdk/ec2/test/test.vpc.ts b/packages/@aws-cdk/ec2/test/test.vpc.ts index a08bdde18cd35..13cc1cbb36753 100644 --- a/packages/@aws-cdk/ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/ec2/test/test.vpc.ts @@ -203,12 +203,12 @@ export = { cidrMask: 24, name: 'ingress', subnetType: SubnetType.Public, - mapPublicIpOnLaunch: false, - natGateway: true, + mapPublicIpOnLaunch: false } ], }); expect(stack).to(countResources("AWS::EC2::Subnet", 1)); + expect(stack).notTo(haveResource("AWS::EC2::NatGateway")); expect(stack).to(haveResource("AWS::EC2::Subnet", { MapPublicIpOnLaunch: false })); From 0864bb8442e43236de248bcf5265cf18d7a2983b Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Sun, 22 Jul 2018 20:44:26 -0700 Subject: [PATCH 28/35] addressing comments for maxNatGateway -> natGateways, removing mapPublicIp, and internal -> isolated --- packages/@aws-cdk/ec2/README.md | 12 ++-- packages/@aws-cdk/ec2/lib/network-util.ts | 32 ++++++---- packages/@aws-cdk/ec2/lib/vpc.ts | 60 +++++++------------ .../@aws-cdk/ec2/test/test.network-utils.ts | 14 +++++ packages/@aws-cdk/ec2/test/test.vpc.ts | 32 +++++----- 5 files changed, 78 insertions(+), 72 deletions(-) diff --git a/packages/@aws-cdk/ec2/README.md b/packages/@aws-cdk/ec2/README.md index 645b48d53d566..afd5b74e434c1 100644 --- a/packages/@aws-cdk/ec2/README.md +++ b/packages/@aws-cdk/ec2/README.md @@ -49,7 +49,7 @@ new VpcNetwork(stack, 'TheVPC', { { cidrMask: 28, name: 'Database', - subnetType: SubnetType.Internal, + subnetType: SubnetType.Isolated, } ], }); @@ -71,12 +71,12 @@ availability zones will be the following: * DatabaseSubnet3: 10.0.6.32/28 Each `Public` Subnet will have a NAT Gateway. Each `Private` Subnet will have a -route to the NAT Gateway in the same availability zone. Each `Internal` subnet +route to the NAT Gateway in the same availability zone. Each `Isolated` subnet will not have a route to the internet, but is routeable inside the VPC. The numbers [1-3] will consistently map to availability zones (e.g. IngressSubnet1 and ApplicaitonSubnet1 will be in the same avialbility zone). -`Internal` Subnets provide simplified secure networking principles, but come at +`Isolated` Subnets provide simplified secure networking principles, but come at an operational complexity. The lack of an internet route means that if you deploy instances in this subnet you will not be able to patch from the internet, this is commonly reffered to as @@ -113,7 +113,7 @@ new VpcNetwork(stack, 'TheVPC', { { cidrMask: 27, name: 'Database', - subnetType: SubnetType.Internal, + subnetType: SubnetType.Isolated, } ], }); @@ -145,7 +145,7 @@ import { VpcNetwork } from '@aws-cdk/ec2'; new VpcNetwork(stack, 'TheVPC', { cidr: '10.0.0.0/16', - maxNatGateways: 1, + natGateways: 1, subnetConfiguration: [ { cidrMask: 26, @@ -160,7 +160,7 @@ new VpcNetwork(stack, 'TheVPC', { { cidrMask: 27, name: 'Database', - subnetType: SubnetType.Internal, + subnetType: SubnetType.Isolated, } ], }); diff --git a/packages/@aws-cdk/ec2/lib/network-util.ts b/packages/@aws-cdk/ec2/lib/network-util.ts index 39953452d22af..7fe96c35c6aaf 100644 --- a/packages/@aws-cdk/ec2/lib/network-util.ts +++ b/packages/@aws-cdk/ec2/lib/network-util.ts @@ -86,11 +86,23 @@ export class NetworkUtils { } + /** + * Validates an IPv4 string + * + * returns true of the string contains 4 numbers between 0-255 delimited by + * a `.` character + */ public static validIp(ipAddress: string): boolean { - return ipAddress.split('.').map((octet: string) => parseInt(octet, 10)). + const octets = ipAddress.split('.'); + if (octets.length !== 4) { + return false; + } + return octets.map((octet: string) => parseInt(octet, 10)). filter((octet: number) => octet >= 0 && octet <= 255).length === 4; } + /** + * Converts a string IPv4 to a number * * takes an IP Address (e.g. 174.66.173.168) and converts to a number * (e.g 2923605416); currently only supports IPv4 @@ -102,7 +114,6 @@ export class NetworkUtils { * @param {string} the IP address (e.g. 174.66.173.168) * @returns {number} the integer value of the IP address (e.g 2923605416) */ - public static ipToNum(ipAddress: string): number { if (!this.validIp(ipAddress)) { throw new Error(`${ipAddress} is not valid`); @@ -117,6 +128,7 @@ export class NetworkUtils { } /** + * Takes number and converts it to IPv4 address string * * Takes a number (e.g 2923605416) and converts it to an IPv4 address string * currently only supports IPv4 @@ -124,7 +136,6 @@ export class NetworkUtils { * @param {number} the integer value of the IP address (e.g 2923605416) * @returns {string} the IPv4 address (e.g. 174.66.173.168) */ - public static numToIp(ipNum: number): string { // this all because bitwise math is signed let remaining = ipNum; @@ -143,7 +154,6 @@ export class NetworkUtils { } return ipAddress; } - } /** @@ -152,19 +162,19 @@ export class NetworkUtils { export class NetworkBuilder { /** - * the CIDR range used when creating the network + * The CIDR range used when creating the network */ public readonly networkCidr: CidrBlock; /** - * the list of CIDR blocks for subnets within this network + * The list of CIDR blocks for subnets within this network */ - protected subnetCidrs: CidrBlock[]; + private readonly subnetCidrs: CidrBlock[] = []; /** - * the current highest allocated IP address in any subnet + * The current highest allocated IP address in any subnet */ - protected maxIpConsumed: string; + private maxIpConsumed: string; /** * Create a network using the provided CIDR block @@ -225,7 +235,6 @@ export class NetworkBuilder { NetworkUtils.ipToNum(this.maxIpConsumed); const ipsPerSubnet: number = Math.floor(remaining / subnetCount); return 32 - Math.floor(Math.log2(ipsPerSubnet)); - // return this.addSubnets(mask, subnetCount); } protected validate(): boolean { @@ -233,7 +242,6 @@ export class NetworkBuilder { (cidrBlock) => this.networkCidr.containsCidr(cidrBlock)). filter( (contains) => (contains === false)).length === 0; } - } /** @@ -255,7 +263,7 @@ export class CidrBlock { * Given an IP and CIDR mask number returns the next CIDR Block available * * For example: - * CidrBlock.fromOffsetIp('10.0.0.15', 24) returns '10.0.1.0/24' + * CidrBlock.fromOffsetIp('10.0.0.15', 24) returns new CidrBlock('10.0.1.0/24') */ public static fromOffsetIp(ipAddress: string, mask: number): CidrBlock { const initialCidr = new CidrBlock(`${ipAddress}/${mask}`); diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index 08a45878c4048..272271419db80 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -64,7 +64,7 @@ export interface VpcNetworkProps { * will route to this NAT Gateway. * @default maxAZs */ - maxNatGateways?: number; + natGateways?: number; /** * Configure the subnets to build for each AZ @@ -73,7 +73,7 @@ export interface VpcNetworkProps { * specify the configuration. The VPC details (VPC ID, specific CIDR, * specific AZ will be calculated during creation) * - * For example if you want 1 public subnet, 1 private subnet, and 1 internal + * For example if you want 1 public subnet, 1 private subnet, and 1 isolated * subnet in each AZ provide the following: * subnetConfiguration: [ * { @@ -90,7 +90,7 @@ export interface VpcNetworkProps { * { * cidrMask: 28, * name: 'rds', - * subnetType: SubnetType.Internal, + * subnetType: SubnetType.Isolated, * } * ] * @@ -124,12 +124,12 @@ export enum DefaultInstanceTenancy { export enum SubnetType { /** - * Internal Subnets do not route Outbound traffic + * Isolated Subnets do not route Outbound traffic * * This can be good for subnets with RDS or * Elasticache endpoints */ - Internal = 1, + Isolated = 1, /** * Subnet that routes to the internet, but not vice versa. @@ -182,23 +182,6 @@ export interface SubnetConfiguration { * availability zone. */ name: string; - - /** - * Controls the creation a NAT Gateway in this subnet - * - * If true will place a NAT Gateway in this subnet, subnetType must be Public. - * Defaults to true in Subnet.Public, false in Subnet.Private or Subnet.Internal. - * NAT Gateways in non-public subnets will functionally not work and are - * therefore not possible to create. - */ - natGateway?: boolean; - - /** - * Controls if a public IP is associated to an instance at launch - * - * Defaults to true in Subnet.Public, false in Subnet.Private or Subnet.Internal. - */ - mapPublicIpOnLaunch?: boolean; } /** @@ -239,12 +222,10 @@ export class VpcNetwork extends VpcNetworkRef { { subnetType: SubnetType.Public, name: 'Public', - natGateway: true }, { subnetType: SubnetType.Private, name: 'Private', - natGateway: false } ]; @@ -264,16 +245,16 @@ export class VpcNetwork extends VpcNetworkRef { public readonly privateSubnets: VpcSubnetRef[] = []; /** - * List of internal subnets in this VPC + * List of isolated subnets in this VPC */ - public readonly internalSubnets: VpcSubnetRef[] = []; + public readonly isolatedSubnets: VpcSubnetRef[] = []; /** * Maximum Number of NAT Gateways used to control cost * * @default {VpcNetworkProps.maxAZs} */ - private readonly maxNatGateways: number; + private readonly natGateways: number; /** * The VPC resource @@ -342,13 +323,17 @@ export class VpcNetwork extends VpcNetworkRef { this.vpcId = this.resource.ref; this.dependencyElements.push(this.resource); - this.maxNatGateways = ifUndefined(props.maxNatGateways, this.availabilityZones.length); - this.subnetConfiguration = ifUndefined(props.subnetConfiguration, VpcNetwork.DEFAULT_SUBNETS); + const useNatGateway = this.subnetConfiguration.filter( + subnet => (subnet.subnetType === SubnetType.Private)).length > 0; + this.natGateways = ifUndefined(props.natGateways, + useNatGateway ? this.availabilityZones.length : 0); + + // subnetConfiguration and natGateways must be set before calling createSubnets this.createSubnets(); const allowOutbound = this.subnetConfiguration.filter( - subnet => (subnet.subnetType !== SubnetType.Internal)).length > 0; + subnet => (subnet.subnetType !== SubnetType.Isolated)).length > 0; // Create an Internet Gateway and attach it if necessary if (allowOutbound) { @@ -413,16 +398,15 @@ export class VpcNetwork extends VpcNetworkRef { availabilityZone: zone, vpcId: this.vpcId, cidrBlock: this.networkBuilder.addSubnet(cidrMask), - mapPublicIpOnLaunch: ifUndefined(subnetConfig.mapPublicIpOnLaunch, - (subnetConfig.subnetType === SubnetType.Public)), + mapPublicIpOnLaunch: (subnetConfig.subnetType === SubnetType.Public), }; switch (subnetConfig.subnetType) { case SubnetType.Public: const publicSubnet = new VpcPublicSubnet(this, name, subnetProps); - if (subnetConfig.natGateway) { + if (this.natGateways > 0) { const ngwArray = Array.from(Object.values(this.natGatewayByAZ)); - if (ngwArray.length < this.maxNatGateways) { + if (ngwArray.length < this.natGateways) { this.natGatewayByAZ[zone] = publicSubnet.addNatGateway(); } } @@ -432,9 +416,9 @@ export class VpcNetwork extends VpcNetworkRef { const privateSubnet = new VpcPrivateSubnet(this, name, subnetProps); this.privateSubnets.push(privateSubnet); break; - case SubnetType.Internal: - const internalSubnet = new VpcPrivateSubnet(this, name, subnetProps); - this.internalSubnets.push(internalSubnet); + case SubnetType.Isolated: + const isolatedSubnet = new VpcPrivateSubnet(this, name, subnetProps); + this.isolatedSubnets.push(isolatedSubnet); break; } }); @@ -464,7 +448,7 @@ export interface VpcSubnetProps { /** * Controls if a public IP is associated to an instance at launch * - * Defaults to true in Subnet.Public, false in Subnet.Private or Subnet.Internal. + * Defaults to true in Subnet.Public, false in Subnet.Private or Subnet.Isolated. */ mapPublicIpOnLaunch?: boolean; } diff --git a/packages/@aws-cdk/ec2/test/test.network-utils.ts b/packages/@aws-cdk/ec2/test/test.network-utils.ts index b17d562ebaa56..ce74670af05ab 100644 --- a/packages/@aws-cdk/ec2/test/test.network-utils.ts +++ b/packages/@aws-cdk/ec2/test/test.network-utils.ts @@ -65,6 +65,20 @@ export = { }, /is not a valid/); test.done(); }, + "validIp returns true if octect is in 0-255"(test: Test) { + const invalidIps = ['255.255.0.0', '0.0.0.0', '1.2.3.4', '10.0.0.0', '255.01.01.255']; + for (const ip of invalidIps) { + test.strictEqual(true, NetworkUtils.validIp(ip)); + } + test.done(); + }, + "validIp returns false if octect is not in 0-255"(test: Test) { + const invalidIps = ['1.2.3.4.689', '-1.55.22.22', '', ' ', '255.264.1.01']; + for (const ip of invalidIps) { + test.strictEqual(false, NetworkUtils.validIp(ip)); + } + test.done(); + }, }, CidrBlock: { "should return the next valid subnet from offset IP"(test: Test) { diff --git a/packages/@aws-cdk/ec2/test/test.vpc.ts b/packages/@aws-cdk/ec2/test/test.vpc.ts index 13cc1cbb36753..4c7f2d5f30cee 100644 --- a/packages/@aws-cdk/ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/ec2/test/test.vpc.ts @@ -63,18 +63,21 @@ export = { test.done(); }, - "with only internal subnets, the VPC should not contain an IGW or NAT Gateways"(test: Test) { + "with only isolated subnets, the VPC should not contain an IGW or NAT Gateways"(test: Test) { const stack = getTestStack(); new VpcNetwork(stack, 'TheVPC', { subnetConfiguration: [ { - subnetType: SubnetType.Internal, - name: 'Internal', + subnetType: SubnetType.Isolated, + name: 'Isolated', } ] }); expect(stack).notTo(haveResource("AWS::EC2::InternetGateway")); expect(stack).notTo(haveResource("AWS::EC2::NatGateway")); + expect(stack).to(haveResource("AWS::EC2::Subnet", { + MapPublicIpOnLaunch: false + })); test.done(); }, @@ -87,8 +90,8 @@ export = { name: 'Public', }, { - subnetType: SubnetType.Internal, - name: 'Internal', + subnetType: SubnetType.Isolated, + name: 'Isolated', } ] }); @@ -116,7 +119,6 @@ export = { cidrMask: 24, name: 'ingress', subnetType: SubnetType.Public, - natGateway: true, }, { cidrMask: 24, @@ -126,7 +128,7 @@ export = { { cidrMask: 28, name: 'rds', - subnetType: SubnetType.Internal, + subnetType: SubnetType.Isolated, } ], maxAZs: 3 @@ -146,17 +148,16 @@ export = { } test.done(); }, - "with custom subents and maxNatGateway = 2 there should be only to NATGW"(test: Test) { + "with custom subents and natGateways = 2 there should be only to NATGW"(test: Test) { const stack = getTestStack(); new VpcNetwork(stack, 'TheVPC', { cidr: '10.0.0.0/21', - maxNatGateways: 2, + natGateways: 2, subnetConfiguration: [ { cidrMask: 24, name: 'ingress', subnetType: SubnetType.Public, - natGateway: true, }, { cidrMask: 24, @@ -166,7 +167,7 @@ export = { { cidrMask: 28, name: 'rds', - subnetType: SubnetType.Internal, + subnetType: SubnetType.Isolated, } ], maxAZs: 3 @@ -194,7 +195,7 @@ export = { })); test.done(); }, - "with mapPublicIpOnLaunch false for public subnets"(test: Test) { + "with public subnets MapPublicIpOnLaunch is true"(test: Test) { const stack = getTestStack(); new VpcNetwork(stack, 'VPC', { maxAZs: 1, @@ -203,14 +204,13 @@ export = { cidrMask: 24, name: 'ingress', subnetType: SubnetType.Public, - mapPublicIpOnLaunch: false } ], }); expect(stack).to(countResources("AWS::EC2::Subnet", 1)); expect(stack).notTo(haveResource("AWS::EC2::NatGateway")); expect(stack).to(haveResource("AWS::EC2::Subnet", { - MapPublicIpOnLaunch: false + MapPublicIpOnLaunch: true })); test.done(); }, @@ -230,9 +230,9 @@ export = { })); test.done(); }, - "with maxNatGateway set to 1"(test: Test) { + "with natGateway set to 1"(test: Test) { const stack = getTestStack(); - new VpcNetwork(stack, 'VPC', { maxNatGateways: 1 }); + new VpcNetwork(stack, 'VPC', { natGateways: 1 }); expect(stack).to(countResources("AWS::EC2::Subnet", 6)); expect(stack).to(countResources("AWS::EC2::Route", 6)); expect(stack).to(countResources("AWS::EC2::Subnet", 6)); From 776f1e017e969f2d5009ee6c66e6ae359f28361e Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Mon, 23 Jul 2018 00:30:55 -0700 Subject: [PATCH 29/35] refactoring cidrblock and network builder for number centric ip management --- packages/@aws-cdk/ec2/lib/network-util.ts | 102 ++++++++++-------- .../@aws-cdk/ec2/test/test.network-utils.ts | 21 ++-- 2 files changed, 74 insertions(+), 49 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/network-util.ts b/packages/@aws-cdk/ec2/lib/network-util.ts index 7fe96c35c6aaf..0b24dcf169070 100644 --- a/packages/@aws-cdk/ec2/lib/network-util.ts +++ b/packages/@aws-cdk/ec2/lib/network-util.ts @@ -172,9 +172,9 @@ export class NetworkBuilder { private readonly subnetCidrs: CidrBlock[] = []; /** - * The current highest allocated IP address in any subnet + * The next available IP address as a number */ - private maxIpConsumed: string; + private nextIp: number; /** * Create a network using the provided CIDR block @@ -186,8 +186,7 @@ export class NetworkBuilder { constructor(cidr: string) { this.networkCidr = new CidrBlock(cidr); this.subnetCidrs = []; - this.maxIpConsumed = NetworkUtils.numToIp(NetworkUtils. - ipToNum(this.networkCidr.minIp()) - 1); + this.nextIp = this.networkCidr.minAddress(); } /** @@ -200,19 +199,19 @@ export class NetworkBuilder { /** * Add {count} number of subnets to the network and update the maxIpConsumed */ - public addSubnets(mask: number, count?: number): string[] { - if (mask < 16 || mask > 28) { + public addSubnets(mask: number, count: number = 1): string[] { + if (mask < 16 || mask > 28 ) { throw new InvalidCidrRangeError(`x.x.x.x/${mask}`); } + const maxIp = this.nextIp + (CidrBlock.calculateNetsize(mask) * count); + if (this.networkCidr.maxAddress() < maxIp - 1) { + throw new Error(`${count} of /${mask} exceeds remaining space of ${this.networkCidr.cidr}`); + } const subnets: CidrBlock[] = []; - count = count || 1; for (let i = 0; i < count; i ++) { - const subnet: CidrBlock = CidrBlock.fromOffsetIp(this.maxIpConsumed, mask); - this.maxIpConsumed = subnet.maxIp(); + const subnet: CidrBlock = CidrBlock.fromOffsetIp(this.nextIp, mask); + this.nextIp = subnet.maxAddress() + 1; this.subnetCidrs.push(subnet); - if (!this.validate()) { - throw new Error(`${this.networkCidr.cidr} does not fully contain ${subnet.cidr}`); - } subnets.push(subnet); } return subnets.map((subnet) => (subnet.cidr)); @@ -221,7 +220,7 @@ export class NetworkBuilder { /** * return the CIDR notation strings for all subnets in the network */ - public get subnets(): string[] { + public get cidrStrings(): string[] { return this.subnetCidrs.map((subnet) => (subnet.cidr)); } @@ -230,18 +229,10 @@ export class NetworkBuilder { * remaining IP space */ public maskForRemainingSubnets(subnetCount: number): number { - const remaining: number = - NetworkUtils.ipToNum(this.networkCidr.maxIp()) - - NetworkUtils.ipToNum(this.maxIpConsumed); + const remaining: number = this.networkCidr.maxAddress() - this.nextIp + 1; const ipsPerSubnet: number = Math.floor(remaining / subnetCount); return 32 - Math.floor(Math.log2(ipsPerSubnet)); } - - protected validate(): boolean { - return this.subnetCidrs.map( - (cidrBlock) => this.networkCidr.containsCidr(cidrBlock)). - filter( (contains) => (contains === false)).length === 0; - } } /** @@ -259,16 +250,25 @@ export class CidrBlock { return NetworkUtils.numToIp(2 ** 32 - 2 ** (32 - mask)); } + /** + * Calculates the number IP addresses in a CIDR Mask + * + * For example: + * CidrBlock.calculateNetmask(24) returns 256 + */ + public static calculateNetsize(mask: number): number { + return 2 ** (32 - mask); + } + /** * Given an IP and CIDR mask number returns the next CIDR Block available * * For example: * CidrBlock.fromOffsetIp('10.0.0.15', 24) returns new CidrBlock('10.0.1.0/24') */ - public static fromOffsetIp(ipAddress: string, mask: number): CidrBlock { - const initialCidr = new CidrBlock(`${ipAddress}/${mask}`); - const baseIpNum = NetworkUtils.ipToNum(initialCidr.maxIp()) + 1; - return new this(`${NetworkUtils.numToIp(baseIpNum)}/${mask}`); + public static fromOffsetIp(ipAddress: number, mask: number): CidrBlock { + const ipNum = ipAddress + CidrBlock.calculateNetsize(mask) - 1; + return new this(`${NetworkUtils.numToIp(ipNum)}/${mask}`); } /* @@ -286,45 +286,63 @@ export class CidrBlock { */ public readonly mask: number; + /** + * The total number of IP addresses in the CIDR + */ + public readonly networkSize: number; + + /** + * The first network address in the CIDR as number + */ + private readonly networkAddress: number; + /* * Creates a new CidrBlock */ constructor(cidr: string) { - this.cidr = cidr; this.mask = parseInt(cidr.split('/')[1], 10); this.netmask = CidrBlock.calculateNetmask(this.mask); + this.networkSize = 2 ** (32 - this.mask); + this.networkAddress = NetworkUtils.ipToNum(cidr.split('/')[0]); + this.cidr = `${this.minIp()}/${this.mask}`; } /* * The maximum IP in the CIDR Blcok e.g. '10.0.8.255' */ public maxIp(): string { - const minIpNum = NetworkUtils.ipToNum(this.minIp()); // min + (2^(32-mask)) - 1 [zero needs to count] - return NetworkUtils.numToIp(minIpNum + 2 ** (32 - this.mask) - 1); + return NetworkUtils.numToIp(this.maxAddress()); } /* * The minimum IP in the CIDR Blcok e.g. '10.0.0.0' */ public minIp(): string { - const netmaskOct = this.netmask.split('.'); - const ipOct = this.cidr.split('/')[0].split('.'); - // tslint:disable:no-bitwise - return netmaskOct.map( - // bitwise mask each octet to ensure correct min - // (e.g. 10.0.0.2/24 -> (10 & 255).(0 & 255).(0 & 255).(2 & 0) = 10.0.0.0 - (maskOct, index) => parseInt(maskOct, 10) & parseInt(ipOct[index], 10)).join('.'); - // tslint:enable:no-bitwise + return NetworkUtils.numToIp(this.minAddress()); } /* * Returns true if this CidrBlock fully contains the provided CidrBlock */ - public containsCidr(cidr: CidrBlock): boolean { - return [ - NetworkUtils.ipToNum(this.minIp()) <= NetworkUtils.ipToNum(cidr.minIp()), - NetworkUtils.ipToNum(this.maxIp()) >= NetworkUtils.ipToNum(cidr.maxIp()), - ].filter((contains) => (contains === false)).length === 0; + public containsCidr(other: CidrBlock): boolean { + return (this.maxAddress() >= other.maxAddress()) && + (this.minAddress() <= other.minAddress()); + } + + /* + * Returns the number representation for the minimum IPv4 address + */ + public minAddress(): number { + const div = this.networkAddress % this.networkSize; + return this.networkAddress - div; + } + + /* + * Returns the number representation for the maximum IPv4 address + */ + public maxAddress(): number { + // min + (2^(32-mask)) - 1 [zero needs to count] + return this.minAddress() + this.networkSize - 1; } } diff --git a/packages/@aws-cdk/ec2/test/test.network-utils.ts b/packages/@aws-cdk/ec2/test/test.network-utils.ts index ce74670af05ab..c224b48e43dd6 100644 --- a/packages/@aws-cdk/ec2/test/test.network-utils.ts +++ b/packages/@aws-cdk/ec2/test/test.network-utils.ts @@ -82,13 +82,20 @@ export = { }, CidrBlock: { "should return the next valid subnet from offset IP"(test: Test) { - const newBlock = CidrBlock.fromOffsetIp('10.0.1.10', 24); + const num = NetworkUtils.ipToNum('10.0.1.10'); + const newBlock = CidrBlock.fromOffsetIp(num, 24); test.strictEqual(newBlock.cidr, '10.0.2.0/24'); test.done(); }, "maxIp() should return the last usable IP from the CidrBlock"(test: Test) { - const block = new CidrBlock('10.0.3.0/24'); - test.strictEqual(block.maxIp(), '10.0.3.255'); + const testValues = [ + ['10.0.3.0/28', '10.0.3.15'], + ['10.0.3.3/28', '10.0.3.15'], + ]; + for (const value of testValues) { + const block = new CidrBlock(value[0]); + test.strictEqual(block.maxIp(), value[1]); + } test.done(); }, "minIp() should return the first usable IP from the CidrBlock"(test: Test) { @@ -153,8 +160,8 @@ export = { efficient.addSubnets(25, 3); efficient.addSubnets(28, 3); const expected = [ - wasteful.subnets.sort(), - efficient.subnets.sort() + wasteful.cidrStrings.sort(), + efficient.cidrStrings.sort() ]; for (let i = 0; i < answers.length; i++) { test.deepEqual(answers[i].sort(), expected[i]); @@ -179,10 +186,10 @@ export = { test.throws(() => { builder.addSubnet(19); builder.addSubnet(28); - }, /does not fully contain/); + }, /exceeds remaining space/); test.throws(() => { builder2.addSubnet(28); - }, /does not fully contain/); + }, /exceeds remaining space/); test.done(); }, "maskForRemainingSubnets calcs mask for even split of remaining"(test: Test) { From 82246aee1014f160ca1581bceac2545d556b1ad4 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Mon, 23 Jul 2018 01:22:21 -0700 Subject: [PATCH 30/35] refactoring CidrBlock for constructor overloading, renaming nextIp ot nextAvailableIp, adding in nextBlock() method --- packages/@aws-cdk/ec2/lib/network-util.ts | 84 ++++++++++--------- .../@aws-cdk/ec2/test/test.network-utils.ts | 13 ++- 2 files changed, 57 insertions(+), 40 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/network-util.ts b/packages/@aws-cdk/ec2/lib/network-util.ts index 0b24dcf169070..3de3215e93395 100644 --- a/packages/@aws-cdk/ec2/lib/network-util.ts +++ b/packages/@aws-cdk/ec2/lib/network-util.ts @@ -174,7 +174,7 @@ export class NetworkBuilder { /** * The next available IP address as a number */ - private nextIp: number; + private nextAvailableIp: number; /** * Create a network using the provided CIDR block @@ -186,7 +186,7 @@ export class NetworkBuilder { constructor(cidr: string) { this.networkCidr = new CidrBlock(cidr); this.subnetCidrs = []; - this.nextIp = this.networkCidr.minAddress(); + this.nextAvailableIp = this.networkCidr.minAddress(); } /** @@ -203,14 +203,14 @@ export class NetworkBuilder { if (mask < 16 || mask > 28 ) { throw new InvalidCidrRangeError(`x.x.x.x/${mask}`); } - const maxIp = this.nextIp + (CidrBlock.calculateNetsize(mask) * count); + const maxIp = this.nextAvailableIp + (CidrBlock.calculateNetsize(mask) * count); if (this.networkCidr.maxAddress() < maxIp - 1) { throw new Error(`${count} of /${mask} exceeds remaining space of ${this.networkCidr.cidr}`); } const subnets: CidrBlock[] = []; for (let i = 0; i < count; i ++) { - const subnet: CidrBlock = CidrBlock.fromOffsetIp(this.nextIp, mask); - this.nextIp = subnet.maxAddress() + 1; + const subnet: CidrBlock = new CidrBlock(this.nextAvailableIp, mask); + this.nextAvailableIp = subnet.nextBlock().minAddress(); this.subnetCidrs.push(subnet); subnets.push(subnet); } @@ -229,14 +229,14 @@ export class NetworkBuilder { * remaining IP space */ public maskForRemainingSubnets(subnetCount: number): number { - const remaining: number = this.networkCidr.maxAddress() - this.nextIp + 1; + const remaining: number = this.networkCidr.maxAddress() - this.nextAvailableIp + 1; const ipsPerSubnet: number = Math.floor(remaining / subnetCount); return 32 - Math.floor(Math.log2(ipsPerSubnet)); } } /** - * Creates a CIDR Block + * A block of IP address space with a given bit prefix */ export class CidrBlock { @@ -254,56 +254,55 @@ export class CidrBlock { * Calculates the number IP addresses in a CIDR Mask * * For example: - * CidrBlock.calculateNetmask(24) returns 256 + * CidrBlock.calculateNetsize(24) returns 256 */ public static calculateNetsize(mask: number): number { return 2 ** (32 - mask); } - /** - * Given an IP and CIDR mask number returns the next CIDR Block available - * - * For example: - * CidrBlock.fromOffsetIp('10.0.0.15', 24) returns new CidrBlock('10.0.1.0/24') - */ - public static fromOffsetIp(ipAddress: number, mask: number): CidrBlock { - const ipNum = ipAddress + CidrBlock.calculateNetsize(mask) - 1; - return new this(`${NetworkUtils.numToIp(ipNum)}/${mask}`); - } - /* * The CIDR Block represented as a string e.g. '10.0.0.0/21' */ public readonly cidr: string; - /* - * The netmask for the CIDR represented as a string e.g. '255.255.255.0' - */ - public readonly netmask: string; - /* * The CIDR mask e.g. for CIDR '10.0.0.0/21' returns 21 */ public readonly mask: number; - /** + /* * The total number of IP addresses in the CIDR */ public readonly networkSize: number; - /** - * The first network address in the CIDR as number + /* + * The network address provided in CIDR creation + * + * This is not the first or last IP in the CIDR just the IP that initialized + * this CIDR class. */ private readonly networkAddress: number; /* * Creates a new CidrBlock */ - constructor(cidr: string) { - this.mask = parseInt(cidr.split('/')[1], 10); - this.netmask = CidrBlock.calculateNetmask(this.mask); + constructor(cidr: string) + constructor(ipAddress: number, mask: number) + constructor(ipAddressOrCidr: string | number, mask?: number) { + if (typeof ipAddressOrCidr === 'string') { + this.mask = parseInt(ipAddressOrCidr.split('/')[1], 10); + this.networkAddress = NetworkUtils.ipToNum(ipAddressOrCidr.split('/')[0]); + } else { + if (typeof mask === 'number') { + this.mask = mask; + } else { + // this should be impossible + this.mask = 16; + } + this.networkAddress = ipAddressOrCidr + CidrBlock.calculateNetsize(this.mask) - 1; + this.networkSize = 2 ** (32 - this.mask); + } this.networkSize = 2 ** (32 - this.mask); - this.networkAddress = NetworkUtils.ipToNum(cidr.split('/')[0]); this.cidr = `${this.minIp()}/${this.mask}`; } @@ -322,14 +321,6 @@ export class CidrBlock { return NetworkUtils.numToIp(this.minAddress()); } - /* - * Returns true if this CidrBlock fully contains the provided CidrBlock - */ - public containsCidr(other: CidrBlock): boolean { - return (this.maxAddress() >= other.maxAddress()) && - (this.minAddress() <= other.minAddress()); - } - /* * Returns the number representation for the minimum IPv4 address */ @@ -345,4 +336,19 @@ export class CidrBlock { // min + (2^(32-mask)) - 1 [zero needs to count] return this.minAddress() + this.networkSize - 1; } + + /* + * Returns the next CIDR Block of the same mask size + */ + public nextBlock(): CidrBlock { + return new CidrBlock(this.maxAddress() + 1, this.mask); + } + + /* + * Returns true if this CidrBlock fully contains the provided CidrBlock + */ + public containsCidr(other: CidrBlock): boolean { + return (this.maxAddress() >= other.maxAddress()) && + (this.minAddress() <= other.minAddress()); + } } diff --git a/packages/@aws-cdk/ec2/test/test.network-utils.ts b/packages/@aws-cdk/ec2/test/test.network-utils.ts index c224b48e43dd6..f7fcfe30c151c 100644 --- a/packages/@aws-cdk/ec2/test/test.network-utils.ts +++ b/packages/@aws-cdk/ec2/test/test.network-utils.ts @@ -83,10 +83,21 @@ export = { CidrBlock: { "should return the next valid subnet from offset IP"(test: Test) { const num = NetworkUtils.ipToNum('10.0.1.10'); - const newBlock = CidrBlock.fromOffsetIp(num, 24); + const newBlock = new CidrBlock(num, 24); test.strictEqual(newBlock.cidr, '10.0.2.0/24'); test.done(); }, + "nextBlock() returns the next higher CIDR space"(test: Test) { + const testValues = [ + ['192.168.0.0/24', '192.168.1.0/24'], + ['10.85.7.0/28', '10.85.7.16/28'], + ]; + for (const value of testValues) { + const block = new CidrBlock(value[0]); + test.strictEqual(block.nextBlock().cidr, value[1]); + } + test.done(); + }, "maxIp() should return the last usable IP from the CidrBlock"(test: Test) { const testValues = [ ['10.0.3.0/28', '10.0.3.15'], From 864a34151abf7fdc75d382bb6fd5bc6f114f2c9f Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Mon, 23 Jul 2018 01:55:14 -0700 Subject: [PATCH 31/35] a couple of comment clarifications and making the CidrBlock constructor consistent --- packages/@aws-cdk/ec2/lib/network-util.ts | 20 +++++++++++++------ packages/@aws-cdk/ec2/lib/vpc.ts | 1 - .../@aws-cdk/ec2/test/test.network-utils.ts | 5 +++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/@aws-cdk/ec2/lib/network-util.ts b/packages/@aws-cdk/ec2/lib/network-util.ts index 3de3215e93395..2f49192156aa1 100644 --- a/packages/@aws-cdk/ec2/lib/network-util.ts +++ b/packages/@aws-cdk/ec2/lib/network-util.ts @@ -276,22 +276,30 @@ export class CidrBlock { public readonly networkSize: number; /* - * The network address provided in CIDR creation - * - * This is not the first or last IP in the CIDR just the IP that initialized - * this CIDR class. + * The network address provided in CIDR creation offset by the Netsize -1 */ private readonly networkAddress: number; /* - * Creates a new CidrBlock + * Parses either CIDR notation String or two numbers representing the IP + * space + * + * cidr expects a string '10.0.0.0/16' + * ipAddress expects a number + * mask expects a number + * + * If the given `cidr` or `ipAddress` is not the beginning of the block, + * then the next avaiable block will be returned. For example, if + * `10.0.3.1/28` is given the returned block will represent `10.0.3.16/28`. + * */ constructor(cidr: string) constructor(ipAddress: number, mask: number) constructor(ipAddressOrCidr: string | number, mask?: number) { if (typeof ipAddressOrCidr === 'string') { this.mask = parseInt(ipAddressOrCidr.split('/')[1], 10); - this.networkAddress = NetworkUtils.ipToNum(ipAddressOrCidr.split('/')[0]); + this.networkAddress = NetworkUtils.ipToNum(ipAddressOrCidr.split('/')[0]) + + CidrBlock.calculateNetsize(this.mask) - 1; } else { if (typeof mask === 'number') { this.mask = mask; diff --git a/packages/@aws-cdk/ec2/lib/vpc.ts b/packages/@aws-cdk/ec2/lib/vpc.ts index 272271419db80..ebdbf60e2bf6a 100644 --- a/packages/@aws-cdk/ec2/lib/vpc.ts +++ b/packages/@aws-cdk/ec2/lib/vpc.ts @@ -80,7 +80,6 @@ export interface VpcNetworkProps { * cidrMask: 24, * name: 'ingress', * subnetType: SubnetType.Public, - * natGateway: true, * }, * { * cidrMask: 24, diff --git a/packages/@aws-cdk/ec2/test/test.network-utils.ts b/packages/@aws-cdk/ec2/test/test.network-utils.ts index f7fcfe30c151c..0b613f816d004 100644 --- a/packages/@aws-cdk/ec2/test/test.network-utils.ts +++ b/packages/@aws-cdk/ec2/test/test.network-utils.ts @@ -82,7 +82,7 @@ export = { }, CidrBlock: { "should return the next valid subnet from offset IP"(test: Test) { - const num = NetworkUtils.ipToNum('10.0.1.10'); + const num = NetworkUtils.ipToNum('10.0.1.255'); const newBlock = new CidrBlock(num, 24); test.strictEqual(newBlock.cidr, '10.0.2.0/24'); test.done(); @@ -101,7 +101,8 @@ export = { "maxIp() should return the last usable IP from the CidrBlock"(test: Test) { const testValues = [ ['10.0.3.0/28', '10.0.3.15'], - ['10.0.3.3/28', '10.0.3.15'], + ['10.0.3.1/28', '10.0.3.31'], + ['10.0.2.254/28', '10.0.3.15'], ]; for (const value of testValues) { const block = new CidrBlock(value[0]); From 9722cdcfe00a1326de4e463d2cd4e4b6bf10c60e Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Mon, 23 Jul 2018 10:03:03 -0700 Subject: [PATCH 32/35] missed one README typo --- packages/@aws-cdk/ec2/README.md | 14 +++++++------- packages/@aws-cdk/ec2/lib/network-util.ts | 1 - 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/ec2/README.md b/packages/@aws-cdk/ec2/README.md index afd5b74e434c1..4321d3a220ba1 100644 --- a/packages/@aws-cdk/ec2/README.md +++ b/packages/@aws-cdk/ec2/README.md @@ -63,9 +63,9 @@ availability zones will be the following: * IngressSubnet1: 10.0.0.0/24 * IngressSubnet2: 10.0.1.0/24 * IngressSubnet3: 10.0.2.0/24 - * ApplicaitonSubnet1: 10.0.3.0/24 - * ApplicaitonSubnet2: 10.0.4.0/24 - * ApplicaitonSubnet3: 10.0.5.0/24 + * ApplicationSubnet1: 10.0.3.0/24 + * ApplicationSubnet2: 10.0.4.0/24 + * ApplicationSubnet3: 10.0.5.0/24 * DatabaseSubnet1: 10.0.6.0/28 * DatabaseSubnet2: 10.0.6.16/28 * DatabaseSubnet3: 10.0.6.32/28 @@ -74,7 +74,7 @@ Each `Public` Subnet will have a NAT Gateway. Each `Private` Subnet will have a route to the NAT Gateway in the same availability zone. Each `Isolated` subnet will not have a route to the internet, but is routeable inside the VPC. The numbers [1-3] will consistently map to availability zones (e.g. IngressSubnet1 -and ApplicaitonSubnet1 will be in the same avialbility zone). +and ApplicationSubnet1 will be in the same avialbility zone). `Isolated` Subnets provide simplified secure networking principles, but come at an operational complexity. The lack of an internet route means that if you deploy @@ -127,9 +127,9 @@ availability zones will be the following: * DatabaseSubnet1: 10.0.0.192/27 * DatabaseSubnet2: 10.0.0.224/27 * DatabaseSubnet3: 10.0.1.0/27 - * ApplicaitonSubnet1: 10.0.64.0/18 - * ApplicaitonSubnet2: 10.0.128.0/18 - * ApplicaitonSubnet3: 10.0.192.0/18 + * ApplicationSubnet1: 10.0.64.0/18 + * ApplicationSubnet2: 10.0.128.0/18 + * ApplicationSubnet3: 10.0.192.0/18 Any subnet configuration without a `cidrMask` will be counted up and allocated evenly across the remaining IP space. diff --git a/packages/@aws-cdk/ec2/lib/network-util.ts b/packages/@aws-cdk/ec2/lib/network-util.ts index 2f49192156aa1..82f47db5548ee 100644 --- a/packages/@aws-cdk/ec2/lib/network-util.ts +++ b/packages/@aws-cdk/ec2/lib/network-util.ts @@ -291,7 +291,6 @@ export class CidrBlock { * If the given `cidr` or `ipAddress` is not the beginning of the block, * then the next avaiable block will be returned. For example, if * `10.0.3.1/28` is given the returned block will represent `10.0.3.16/28`. - * */ constructor(cidr: string) constructor(ipAddress: number, mask: number) From bba5e67c08998631c7947f721edf8d63ed580c6a Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Mon, 23 Jul 2018 10:06:11 -0700 Subject: [PATCH 33/35] final README (ec2) correction to create Advanced Subnet Configuration --- packages/@aws-cdk/ec2/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/@aws-cdk/ec2/README.md b/packages/@aws-cdk/ec2/README.md index 4321d3a220ba1..e1a9ce544181c 100644 --- a/packages/@aws-cdk/ec2/README.md +++ b/packages/@aws-cdk/ec2/README.md @@ -25,6 +25,8 @@ into all private subnets, and provide a parameter called `vpcPlacement` to allow you to override the placement. [Read more about subnets](https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Subnets.html). + +#### Advanced Subnet Configuration If you require the ability to configure subnets the `VpcNetwork` can be customized with `SubnetConfiguration` array. This is best explained by an example: From 31057b22abab3720cc4d4dc97f91fe990f48235f Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Tue, 24 Jul 2018 13:06:18 -0700 Subject: [PATCH 34/35] minor tweaks for renames from master merge --- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 4 ++-- packages/@aws-cdk/aws-ec2/test/test.vpc.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 37aba8fff57cc..17c2c33a368a1 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -268,7 +268,7 @@ export class VpcNetwork extends VpcNetworkRef { /** * Mapping of NatGateway by AZ */ - private natGatewayByAZ: Obj = {}; + private natGatewayByAZ: Obj = {}; /** * Subnet configurations for this VPC @@ -313,7 +313,7 @@ export class VpcNetwork extends VpcNetworkRef { tags }); - this.availabilityZones = new AvailabilityZoneProvider(this).availabilityZones; + this.availabilityZones = new cdk.AvailabilityZoneProvider(this).availabilityZones; this.availabilityZones.sort(); if (props.maxAZs != null) { this.availabilityZones = this.availabilityZones.slice(0, props.maxAZs); diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts index 38b74c7fbb161..81c51eeba65c9 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts @@ -1,5 +1,5 @@ import { countResources, expect, haveResource } from '@aws-cdk/assert'; -import { AvailabilityZoneProvider, Stack } from '@aws-cdk/cdk'; +import { AvailabilityZoneProvider, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { DefaultInstanceTenancy, SubnetType, VpcNetwork } from '../lib'; From f3f54284a5452b4921a3e0375fceba5f4a405297 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Tue, 24 Jul 2018 13:14:40 -0700 Subject: [PATCH 35/35] README modification to match `import ec2 = require('@aws-cdk/aws-ec2')` --- packages/@aws-cdk/aws-ec2/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 7fa6a15bf1b00..33b47d8d2bed2 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -32,9 +32,9 @@ customized with `SubnetConfiguration` array. This is best explained by an example: ```ts -import { VpcNetwork } from '@aws-cdk/ec2'; +import ec2 = require('@aws-cdk/aws-ec2'); -new VpcNetwork(stack, 'TheVPC', { +const vpc = new ec2.VpcNetwork(stack, 'TheVPC', { cidr: '10.0.0.0/21', subnetConfiguration: [ { @@ -97,9 +97,9 @@ databases for your data tier, and the rest of the IP space should just be evenly distributed for the application. ```ts -import { VpcNetwork } from '@aws-cdk/ec2'; +import ec2 = require('@aws-cdk/aws-ec2'); -new VpcNetwork(stack, 'TheVPC', { +const vpc = new ec2.VpcNetwork(stack, 'TheVPC', { cidr: '10.0.0.0/16', subnetConfiguration: [ { @@ -143,9 +143,9 @@ save on the cost, but trade the 3 availability zone to a 1 for all egress traffic. This can be accomplished with a single parameter configuration: ```ts -import { VpcNetwork } from '@aws-cdk/ec2'; +import ec2 = require('@aws-cdk/aws-ec2'); -new VpcNetwork(stack, 'TheVPC', { +const vpc = new ec2.VpcNetwork(stack, 'TheVPC', { cidr: '10.0.0.0/16', natGateways: 1, subnetConfiguration: [