diff --git a/Makefile b/Makefile index 470cc83bc..e18eade5e 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ JAVA_GEN_VERSION := v0.9.7 AWSX_SRC := $(wildcard awsx/*.*) $(wildcard awsx/*/*.ts) AWSX_CLASSIC_SRC:= $(wildcard awsx-classic/*.*) $(wildcard awsx-classic/*/*.ts) -CODEGEN_SRC := $(wildcard schemagen/go.*) $(wildcard schemagen/pkg/*/*.go) $(wildcard schemagen/pkg/cmd/${CODEGEN}/*.go) +CODEGEN_SRC := $(wildcard schemagen/go.*) $(wildcard schemagen/pkg/*/*) $(wildcard schemagen/pkg/cmd/${CODEGEN}/*.go) WORKING_DIR := $(shell pwd) diff --git a/awsx/ec2/subnetDistributor.test.ts b/awsx/ec2/subnetDistributorLegacy.test.ts similarity index 76% rename from awsx/ec2/subnetDistributor.test.ts rename to awsx/ec2/subnetDistributorLegacy.test.ts index b4bc920c5..d47cdeb13 100644 --- a/awsx/ec2/subnetDistributor.test.ts +++ b/awsx/ec2/subnetDistributorLegacy.test.ts @@ -14,8 +14,11 @@ import fc from "fast-check"; import { SubnetSpecInputs, SubnetTypeInputs } from "../schema-types"; -import { getSubnetSpecs, SubnetSpec, validateRanges } from "./subnetDistributor"; +import { getSubnetSpecsLegacy, SubnetSpec, validateRanges } from "./subnetDistributorLegacy"; import { knownWorkingSubnets } from "./knownWorkingSubnets"; +import { extractSubnetSpecInputFromLegacyLayout } from "./vpc"; +import { getSubnetSpecs } from "./subnetDistributorNew"; +import { Netmask } from "netmask"; function cidrMask(args?: { min?: number; max?: number }): fc.Arbitrary { return fc.integer({ min: args?.min ?? 16, max: args?.max ?? 27 }); @@ -52,7 +55,7 @@ describe("default subnet layout", () => { ({ vpcCidrMask, azs, subnetSpecs }) => { const vpcCidr = `10.0.0.0/${vpcCidrMask}`; - const result = getSubnetSpecs("vpcName", vpcCidr, azs, subnetSpecs); + const result = getSubnetSpecsLegacy("vpcName", vpcCidr, azs, subnetSpecs); for (const subnet of result) { const subnetMask = getCidrMask(subnet.cidrBlock); @@ -74,7 +77,7 @@ describe("default subnet layout", () => { ({ vpcCidrMask, subnetSpec }) => { const vpcCidr = `10.0.0.0/${vpcCidrMask}`; - const result = getSubnetSpecs("vpcName", vpcCidr, ["us-east-1a"], [subnetSpec]); + const result = getSubnetSpecsLegacy("vpcName", vpcCidr, ["us-east-1a"], [subnetSpec]); expect(result.length).toBe(1); expect(result[0].cidrBlock).toBe(vpcCidr); @@ -93,7 +96,7 @@ describe("default subnet layout", () => { ({ vpcCidrMask }) => { const vpcCidr = `10.0.0.0/${vpcCidrMask}`; - const result = getSubnetSpecs( + const result = getSubnetSpecsLegacy( "vpcName", vpcCidr, ["us-east-1a"], @@ -144,7 +147,7 @@ describe("default subnet layout", () => { ({ vpcCidrMask, subnetSpecs }) => { const vpcCidr = `10.0.0.0/${vpcCidrMask}`; - const result = getSubnetSpecs("vpcName", vpcCidr, ["us-east-1a"], subnetSpecs); + const result = getSubnetSpecsLegacy("vpcName", vpcCidr, ["us-east-1a"], subnetSpecs); validateRanges(result); }, @@ -154,10 +157,11 @@ describe("default subnet layout", () => { describe("known working subnets", () => { for (const knownCase of knownWorkingSubnets) { - it(`should work for ${knownCase.vpcCidr} with subnets ${knownCase.subnetSpecs + const specDescription = knownCase.subnetSpecs .map((s) => `${s.type}:${s.cidrMask}`) - .join(", ")}`, () => { - const result = getSubnetSpecs( + .join(", "); + it(`should work for ${knownCase.vpcCidr} with subnets ${specDescription}`, () => { + const result = getSubnetSpecsLegacy( "vpcName", knownCase.vpcCidr, ["us-east-1a"], @@ -167,8 +171,57 @@ describe("default subnet layout", () => { expect(actual).toEqual(knownCase.result); }); + it(`should be convertible to new format (${knownCase.vpcCidr}, ${specDescription})`, () => { + const vpcName = "vpcName"; + const availabilityZones = ["us-east-1a"]; + const legacyResult = getSubnetSpecsLegacy( + vpcName, + knownCase.vpcCidr, + availabilityZones, + knownCase.subnetSpecs, + ); + const extracted = extractSubnetSpecInputFromLegacyLayout( + legacyResult, + vpcName, + availabilityZones, + ); + + try { + const autoResult = getSubnetSpecs( + vpcName, + knownCase.vpcCidr, + availabilityZones, + extracted, + ); + const normalizedAutoResult = autoResult + .filter((s) => s.type !== "Unused") + .map((s) => s.cidrBlock); + // Legacy sometimes returns odd netmasks like 10.0.1.128/24 which should actually be 10.0.1.0/24 + const normalizedLegacyNetmasks = legacyResult.map((s) => + new Netmask(s.cidrBlock).toString(), + ); + expect(normalizedAutoResult).toEqual(normalizedLegacyNetmasks); + } catch (err: any) { + // Some cases don't actually fit inside their VPCs. + if (!err.message.includes("Subnets are too large for VPC")) { + throw err; + } + } + }); } }); + it("can override az cidr mask", () => { + const vpcCidr = "10.0.0.0/16"; + const result = getSubnetSpecsLegacy( + "vpcName", + vpcCidr, + ["us-east-1a"], + [{ type: "Public" }], + 21, + ); + // Would default to /20 as that's the hard coded max size for a public subnet + expect(result[0].cidrBlock).toBe("10.0.0.0/21"); + }); }); function getCidrMask(cidrBlock: string): number { @@ -181,7 +234,7 @@ describe("getSubnetSpecs", () => { const vpcName = "vpcname"; it("should return the default subnets with no parameters and 3 AZs", () => { - const result = getSubnetSpecs(vpcName, vpcCidr, azs); + const result = getSubnetSpecsLegacy(vpcName, vpcCidr, azs); const expected: SubnetSpec[] = [ { type: "Private", @@ -271,7 +324,7 @@ describe("getSubnetSpecs", () => { }, ]; - expect(getSubnetSpecs(vpcName, vpcCidr, azs, inputs)).toEqual(expected); + expect(getSubnetSpecsLegacy(vpcName, vpcCidr, azs, inputs)).toEqual(expected); }, ); @@ -332,6 +385,6 @@ describe("getSubnetSpecs", () => { }, ]; - expect(getSubnetSpecs(vpcName, vpcCidr, azs, inputs)).toEqual(expected); + expect(getSubnetSpecsLegacy(vpcName, vpcCidr, azs, inputs)).toEqual(expected); }); }); diff --git a/awsx/ec2/subnetDistributor.ts b/awsx/ec2/subnetDistributorLegacy.ts similarity index 98% rename from awsx/ec2/subnetDistributor.ts rename to awsx/ec2/subnetDistributorLegacy.ts index 21e78684c..c0aadb69e 100644 --- a/awsx/ec2/subnetDistributor.ts +++ b/awsx/ec2/subnetDistributorLegacy.ts @@ -30,11 +30,12 @@ export interface SubnetSpec { }>; } -export function getSubnetSpecs( +export function getSubnetSpecsLegacy( vpcName: string, vpcCidr: string, azNames: string[], subnetInputs?: SubnetSpecInputs[], + azCidrMask?: number, ): SubnetSpec[] { // Design: // 1. Split the VPC CIDR block evenly between the AZs. @@ -46,7 +47,10 @@ export function getSubnetSpecs( // 1. Attempt to use the legacy method (cidrSubnetV4) to get the next block. // 2. Check if the generated next block overlaps with the previous block. // 3. If there's overlap, use the new method (nextBlock) to get the next block. - const newBitsPerAZ = Math.log2(nextPow2(azNames.length)); + const newBitsPerAZ = + azCidrMask !== undefined + ? azCidrMask - new ipAddress.Address4(vpcCidr).subnetMask + : Math.log2(nextPow2(azNames.length)); const azBases: string[] = []; for (let i = 0; i < azNames.length; i++) { diff --git a/awsx/ec2/subnetDistributorNew.test.ts b/awsx/ec2/subnetDistributorNew.test.ts new file mode 100644 index 000000000..b4970ea07 --- /dev/null +++ b/awsx/ec2/subnetDistributorNew.test.ts @@ -0,0 +1,414 @@ +// Copyright 2016-2022, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import fc from "fast-check"; +import { SubnetSpecInputs, SubnetTypeInputs } from "../schema-types"; +import { + defaultSubnetInputs, + getSubnetSpecs, + getSubnetSpecsExplicit, + nextNetmask, + validSubnetSizes, + validateAndNormalizeSubnetInputs, +} from "./subnetDistributorNew"; +import { Netmask } from "netmask"; +import { getOverlappingSubnets, validateNoGaps, validateSubnets } from "./vpc"; +import { getSubnetSpecsLegacy } from "./subnetDistributorLegacy"; + +function cidrMask(args?: { min?: number; max?: number }): fc.Arbitrary { + return fc.integer({ min: args?.min ?? 16, max: args?.max ?? 27 }); +} + +function subnetSpecNoMask() { + return fc.constantFrom("Private", "Public", "Isolated").map( + (type): SubnetSpecInputs => ({ + type: type as SubnetTypeInputs, + }), + ); +} + +function subnetSpec() { + return fc + .record({ type: fc.constantFrom("Private", "Public", "Isolated"), cidrMask: cidrMask() }) + .map( + ({ type, cidrMask }): SubnetSpecInputs => ({ + type: type as SubnetTypeInputs, + cidrMask, + }), + ); +} + +describe("default subnet layout", () => { + it("should have smaller subnets than the vpc", () => { + fc.assert( + fc.property( + fc.record({ + vpcCidrMask: cidrMask(), + azs: fc.array(fc.string({ size: "xsmall" }), { minLength: 2, maxLength: 4 }), + subnetSpecs: fc.array(subnetSpecNoMask(), { minLength: 1, maxLength: 5 }), + }), + ({ vpcCidrMask, azs, subnetSpecs }) => { + const vpcCidr = `10.0.0.0/${vpcCidrMask}`; + + const result = getSubnetSpecs("vpcName", vpcCidr, azs, subnetSpecs); + + for (const subnet of result) { + const subnetMask = getCidrMask(subnet.cidrBlock); + // Larger mask means smaller subnet + expect(subnetMask).toBeGreaterThan(vpcCidrMask); + } + }, + ), + ); + }); + + it("should use whole space if there's only one subnet and VPC is small", () => { + fc.assert( + fc.property( + fc.record({ + vpcCidrMask: cidrMask({ min: 24 }), + subnetSpec: subnetSpecNoMask(), + }), + ({ vpcCidrMask, subnetSpec }) => { + const vpcCidr = `10.0.0.0/${vpcCidrMask}`; + + const result = getSubnetSpecs("vpcName", vpcCidr, ["us-east-1a"], [subnetSpec]); + + expect(result.length).toBe(1); + expect(result[0].cidrBlock).toBe(vpcCidr); + }, + ), + ); + }); + + it("should not have overlapping ranges", () => { + fc.assert( + fc.property( + fc + .record({ + vpcCidrMask: cidrMask(), + // We're just focusing on the logic of choosing the next range, so we don't need large numbers of specs. + // Similarly, we only need a single AZ because AZs are always split evenly. + subnetSpecs: fc.array(subnetSpec(), { minLength: 1, maxLength: 2 }), + }) + .filter(({ vpcCidrMask, subnetSpecs }) => { + for (const subnetSpec of subnetSpecs) { + // Requite subnet to be smaller than VPC + if (subnetSpec.cidrMask! <= vpcCidrMask) { + return false; + } + } + return true; + }), + ({ vpcCidrMask, subnetSpecs }) => { + const vpcCidr = `10.0.0.0/${vpcCidrMask}`; + + const result = getSubnetSpecs("vpcName", vpcCidr, ["us-east-1a"], subnetSpecs); + + validateSubnets(result, getOverlappingSubnets); + }, + ), + ); + }); + it("can override az cidr mask", () => { + const vpcCidr = "10.0.0.0/16"; + const result = getSubnetSpecs("vpcName", vpcCidr, ["us-east-1a"], [{ type: "Public" }], 19); + expect(result[0].cidrBlock).toBe("10.0.0.0/19"); // Would default to /16 as only a single AZ and single subnet + }); +}); + +function getCidrMask(cidrBlock: string): number { + return parseInt(cidrBlock.split("/")[1], 10); +} + +describe("nextNetmask", () => { + it("handles /16 -> /17", () => { + const previous = new Netmask("10.0.0.0/16"); + const next = nextNetmask(previous, 17); + expect(next.toString()).toBe("10.1.0.0/17"); + }); + + it("handles /17 -> /16", () => { + // This finishes at 10.0.127.255 but we can't use 10.0.128.0/16 + // because that's actually the same as 10.0.0.0/16 which overlaps with the previous block. + const previous = new Netmask("10.0.0.0/17"); + const next = nextNetmask(previous, 16); + expect(next.toString()).toBe("10.1.0.0/16"); + }); +}); + +describe("validating exact layouts", () => { + it("should pass for a single subnet", () => { + validateNoGaps("10.0.0.0/16", [ + { + azName: "az", + type: "Public", + cidrBlock: "10.0.0.0/16", + subnetName: "sub1", + }, + ]); + }); + + it("should highlight a gap", () => { + expect(() => + validateNoGaps("10.0.0.0/16", [ + { + azName: "", + type: "Public", + cidrBlock: "10.0.0.0/18", + subnetName: "sub1", + }, + // "10.0.1.0/16" is missing + { + azName: "", + type: "Public", + cidrBlock: "10.0.128.0/17", + subnetName: "sub2", + }, + ]), + ).toThrowError( + "There are gaps in the subnet ranges. Please fix the following gaps: sub1 (10.0.0.0/18) <=> sub2 (10.0.128.0/17)", + ); + }); + + it("highlights gaps at end of VPC", () => { + const vpcCidr = "10.0.0.0/16"; + const result = getSubnetSpecs( + "vpcName", + vpcCidr, + ["us-east-1a"], + // 3 subnets to leave a gap at the end when dividing evenly. + [{ type: "Public" }, { type: "Private" }, { type: "Isolated" }], + ); + expect(() => { + validateNoGaps(vpcCidr, result); + }).toThrowError( + "Please fix the following gaps: vpcName-isolated-1 (ending 10.0.191.254) ends before VPC ends (at 10.0.255.254})", + ); + }); + + it("highlights a gap at the start of the VPC", () => { + expect(() => + validateNoGaps("10.0.0.0/16", [ + { + azName: "", + type: "Public", + cidrBlock: "10.0.128.0/17", + subnetName: "sub1", + }, + ]), + ).toThrowError( + "Please fix the following gaps: sub1 (10.0.128.0/17) does not start at the beginning of the VPC (10.0.0.0/16)", + ); + }); + + it("Has same default layout as legacy", () => { + fc.assert( + fc.property( + fc.record({ + // Allow space for 2 bits to be used for AZs + vpcCidrMask: cidrMask({ max: 24 }), + azs: fc.array(fc.string({ size: "xsmall" }), { minLength: 1, maxLength: 4 }), + }), + ({ vpcCidrMask, azs }) => { + const vpcCidr = `10.0.0.0/${vpcCidrMask}`; + + const newResult = getSubnetSpecs("vpcName", vpcCidr, azs, undefined); + const legacyResult = getSubnetSpecsLegacy("vpcName", vpcCidr, azs, undefined); + + expect(newResult).toEqual(expect.arrayContaining(legacyResult)); + }, + ), + ); + }); + + describe("Default subnet layouts", () => { + it("has fixed size for /16 or larger", () => { + fc.assert( + fc.property(cidrMask({ max: 16 }), (azCidrMask) => { + const result = defaultSubnetInputs(azCidrMask); + expect(result).toEqual([ + { + type: "Private", + cidrMask: 17, + }, + { + type: "Public", + cidrMask: 18, + }, + ]); + }), + ); + }); + it("has relative size for /16 to /26", () => { + fc.assert( + fc.property(cidrMask({ min: 16, max: 26 }), (azCidrMask) => { + const result = defaultSubnetInputs(azCidrMask); + expect(result).toEqual([ + { + type: "Private", + cidrMask: azCidrMask + 1, + }, + { + type: "Public", + cidrMask: azCidrMask + 2, + }, + ]); + }), + ); + }); + it("fails for larger than /26", () => { + fc.assert( + fc.property(cidrMask({ min: 27 }), (azCidrMask) => { + expect(() => { + defaultSubnetInputs(azCidrMask); + }).toThrowError(); + }), + ); + }); + }); +}); + +describe("explicit subnet layouts", () => { + it("should produce specified subnets", () => { + const result = getSubnetSpecsExplicit( + "vpcName", + ["us-east-1a", "us-east-1b"], + [ + { type: "Public", cidrBlocks: ["10.0.0.0/18", "10.0.64.0/19"] }, + { type: "Private", cidrBlocks: ["10.0.96.0/19", "10.0.128.0/20"] }, + ], + ); + expect(result).toEqual([ + { + azName: "us-east-1a", + cidrBlock: "10.0.0.0/18", + subnetName: "vpcName-public-1", + type: "Public", + }, + { + azName: "us-east-1a", + cidrBlock: "10.0.96.0/19", + subnetName: "vpcName-private-1", + type: "Private", + }, + { + azName: "us-east-1b", + cidrBlock: "10.0.64.0/19", + subnetName: "vpcName-public-2", + type: "Public", + }, + { + azName: "us-east-1b", + cidrBlock: "10.0.128.0/20", + subnetName: "vpcName-private-2", + type: "Private", + }, + ]); + }); +}); + +describe("valid subnet sizes", () => { + const sizes = validSubnetSizes; + expect(sizes.length).toBe(31); + for (let index = 0; index < sizes.length; index++) { + const size = sizes[index]; + // Index is also the netmask + expect(size).toEqual(4294967296 / 2 ** index); + } +}); + +describe("validating and normalizing inputs", () => { + it("detects invalid sizes", () => { + expect(() => validateAndNormalizeSubnetInputs([{ type: "Public", size: 100 }], 1)).toThrowError( + "The following subnet sizes are invalid: 100. Valid sizes are: ", + ); + }); + it("detects mismatched size and netmask", () => { + expect(() => + validateAndNormalizeSubnetInputs([{ type: "Public", size: 4096, cidrMask: 21 }], 1), + ).toThrowError("Subnet size 4096 does not match the expected size for a /21 subnet (2048)."); + }); + it("allows size only", () => { + const result = validateAndNormalizeSubnetInputs([{ type: "Public", size: 1024 }], 1); + expect(result!.normalizedSpecs).toEqual([{ type: "Public", size: 1024, cidrMask: 22 }]); + }); + it("allows cidrMask only", () => { + const result = validateAndNormalizeSubnetInputs([{ type: "Public", cidrMask: 23 }], 1); + expect(result!.normalizedSpecs).toEqual([{ type: "Public", size: 512, cidrMask: 23 }]); + }); + it("allows cidrMask and size when matching", () => { + const result = validateAndNormalizeSubnetInputs( + [{ type: "Public", cidrMask: 24, size: 256 }], + 1, + ); + expect(result!.normalizedSpecs).toEqual([{ type: "Public", size: 256, cidrMask: 24 }]); + }); + describe("explicit layouts", () => { + it("detects block count mismatching AZ count", () => { + expect(() => + validateAndNormalizeSubnetInputs([{ type: "Public", cidrBlocks: ["10.0.0.0/16"] }], 2), + ).toThrowError( + "The number of CIDR blocks in subnetSpecs[0] must match the number of availability zones (2).", + ); + }); + it("detects partially specified blocks", () => { + expect(() => + validateAndNormalizeSubnetInputs( + [{ type: "Public", cidrBlocks: ["10.0.0.0/16"] }, { type: "Private" }], + 1, + ), + ).toThrowError( + "If any subnet spec has explicit cidrBlocks, all subnets must have explicit cidrBlocks.", + ); + }); + it("detects cidr blocks with mismatched netmask", () => { + expect(() => + validateAndNormalizeSubnetInputs( + [{ type: "Public", cidrBlocks: ["10.0.0.0/16"], cidrMask: 17 }], + 1, + ), + ).toThrowError( + "The cidrMask in subnetSpecs[0] must match all cidrBlocks or be left undefined.", + ); + }); + it("detects cidr blocks with mismatched netmask", () => { + expect(() => + validateAndNormalizeSubnetInputs( + [{ type: "Public", cidrBlocks: ["10.0.0.0/16"], size: 1024 }], + 1, + ), + ).toThrowError("The size in subnetSpecs[0] must match all cidrBlocks or be left undefined."); + }); + it("allows all argument to be in agreement", () => { + validateAndNormalizeSubnetInputs( + [ + { + type: "Public", + cidrBlocks: ["10.0.0.0/20", "10.0.16.0/20"], + size: 4096, + cidrMask: 20, + }, + { + type: "Public", + cidrBlocks: ["10.0.32.0/21", "10.0.40.0/21"], + size: 2048, + cidrMask: 21, + }, + ], + 2, + ); + }); + }); +}); diff --git a/awsx/ec2/subnetDistributorNew.ts b/awsx/ec2/subnetDistributorNew.ts new file mode 100644 index 000000000..1af452e66 --- /dev/null +++ b/awsx/ec2/subnetDistributorNew.ts @@ -0,0 +1,284 @@ +// Copyright 2016-2022, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code adapted from https://github.com/jen20/pulumi-aws-vpc/blob/master/nodejs/src/subnetDistributor.ts +// and used in accordance with MPL v2.0 license + +import * as pulumi from "@pulumi/pulumi"; +import { SubnetSpecInputs, SubnetTypeInputs } from "../schema-types"; +import { Netmask } from "netmask"; + +export interface SubnetSpec { + cidrBlock: string; + type: SubnetTypeInputs; + azName: string; + subnetName: string; + tags?: pulumi.Input<{ + [key: string]: pulumi.Input; + }>; +} + +export function getSubnetSpecs( + vpcName: string, + vpcCidr: string, + azNames: string[], + subnetInputs: SubnetSpecInputs[] | undefined, + azCidrMask?: number, +): SubnetSpec[] { + const vpcNetmask = new Netmask(vpcCidr); + const azBitmask = azCidrMask ?? vpcNetmask.bitmask + newBits(azNames.length); + + const subnetSpecs = subnetInputs ?? defaultSubnetInputs(azBitmask); + if (subnetSpecs.length === 0) { + throw new Error("No subnets specified"); + } + + const defaultNewBitsPerSubnet = newBits(subnetSpecs.length); + const defaultSubnetBitmask = azBitmask + defaultNewBitsPerSubnet; + + const azSize = new Netmask(vpcNetmask.base, azBitmask).size; + const totalSubnetSize = subnetSpecs.reduce( + (acc, spec) => acc + new Netmask(vpcNetmask.base, spec.cidrMask ?? defaultSubnetBitmask).size, + 0, + ); + + if (totalSubnetSize > azSize) { + throw new Error( + `Subnets are too large for VPC. VPC has ${azSize} addresses, but subnets require ${totalSubnetSize} addresses.`, + ); + } + + let currentAzNetmask = new Netmask(`${vpcNetmask.base}/${azBitmask}`); + const subnets: SubnetSpec[] = []; + for (let azIndex = 0; azIndex < azNames.length; azIndex++) { + const azName = azNames[azIndex]; + const azNum = azIndex + 1; + let currentSubnetNetmask: Netmask | undefined; + for (const subnetSpec of subnetSpecs) { + if (currentSubnetNetmask === undefined) { + currentSubnetNetmask = new Netmask( + currentAzNetmask.base, + subnetSpec.cidrMask ?? defaultSubnetBitmask, + ); + } else { + currentSubnetNetmask = nextNetmask( + currentSubnetNetmask, + subnetSpec.cidrMask ?? defaultSubnetBitmask, + ); + } + const subnetCidr = currentSubnetNetmask.toString(); + subnets.push({ + cidrBlock: subnetCidr, + type: subnetSpec.type, + azName, + subnetName: subnetName(vpcName, subnetSpec, azNum), + tags: subnetSpec.tags, + }); + } + + currentAzNetmask = currentAzNetmask.next(); + } + + return subnets; +} + +export function defaultSubnetInputs(azBitmask: number): SubnetSpecInputs[] { + // For a large VPC (up to /17), the layout will be: + // Private: /19 (10.0.0.0 - 10.0.31.255), 8190 addresses + // Public: /20 (10.0.32.0 - 10.0.47.255), 4094 addresses + // Isolated: /24 (10.0.48.0 - 10.0.48.255), 254 addresses + // Unallocated: (10.0.49.0 - 10.0.127.255) + + // Don't allow default subnets to go smaller than a /28. + if (azBitmask > 26) { + throw new Error( + `Automatic subnet creation requires a VPC with at least /26 available per AZ, but only /${azBitmask} is available.` + + `If you do need very small subnets, please specify them explicitly.`, + ); + } + // Even if we've got more than /16, only use the first /16 for the default subnets. + // Leave the rest for the user to add later if needed. + const maxBitmask = Math.max(azBitmask, 16); + return [ + { + type: "Private", + cidrMask: maxBitmask + 1, + }, + { + type: "Public", + cidrMask: maxBitmask + 2, + }, + ]; +} + +export function nextNetmask(previous: Netmask, nextBitmask: number): Netmask { + // 1. Get the last address in the previous block. + const lastAddress = previous.last; + // 2. Change the size of the block to the new size in case the block is wider and overlaps. + const lastAddressInNewSizeBlock = new Netmask(lastAddress, nextBitmask); + return lastAddressInNewSizeBlock.next(); +} + +export function newBits(count: number): number { + if (count === 0) { + return 0; + } + return Math.ceil(Math.log2(count)); +} + +export type ExplicitSubnetSpecInputs = SubnetSpecInputs & + Required>; + +export function getSubnetSpecsExplicit( + vpcName: string, + azNames: string[], + subnetInputs: ExplicitSubnetSpecInputs[], +): SubnetSpec[] { + const subnets: SubnetSpec[] = []; + for (let azIndex = 0; azIndex < azNames.length; azIndex++) { + const azName = azNames[azIndex]; + const azNum = azIndex + 1; + for (const subnetSpec of subnetInputs) { + const subnetCidr = subnetSpec.cidrBlocks[azIndex]; + subnets.push({ + cidrBlock: subnetCidr, + type: subnetSpec.type, + azName, + subnetName: subnetName(vpcName, subnetSpec, azNum), + }); + } + } + + return subnets; +} + +function subnetName(vpcName: string, subnet: SubnetSpecInputs, azNum: number): string { + const specName = subnet.name ?? subnet.type.toLowerCase(); + return `${vpcName}-${specName}-${azNum}`; +} + +/* Ensure all inputs are consistent and fill in missing values with defaults + * Ensure any specified, netmask, size or blocks are in agreement. + */ +export function validateAndNormalizeSubnetInputs( + subnetArgs: SubnetSpecInputs[] | undefined, + availabilityZoneCount: number, +): + | { normalizedSpecs: SubnetSpecInputs[]; isExplicitLayout: false } + | { normalizedSpecs: ExplicitSubnetSpecInputs[]; isExplicitLayout: true } + | undefined { + if (subnetArgs === undefined) { + return undefined; + } + + const issues: string[] = []; + + // All sizes must be valid. + const invalidSizes = subnetArgs.filter( + (spec) => spec.size !== undefined && !validSubnetSizes.includes(spec.size), + ); + if (invalidSizes.length > 0) { + issues.push( + `The following subnet sizes are invalid: ${invalidSizes + .map((spec) => spec.size) + .join(", ")}. Valid sizes are: ${validSubnetSizes.join(", ")}.`, + ); + } + + const hasExplicitLayouts = subnetArgs.some((subnet) => subnet.cidrBlocks !== undefined); + if (hasExplicitLayouts) { + const explicitSpecs: ExplicitSubnetSpecInputs[] = []; + + for (let specIndex = 0; specIndex < subnetArgs.length; specIndex++) { + const spec = subnetArgs[specIndex]; + const cidrBlocks = spec.cidrBlocks; + + // If any subnet spec has explicit cidrBlocks, all subnets must have explicit cidrBlocks. + if (cidrBlocks === undefined) { + issues.push( + "If any subnet spec has explicit cidrBlocks, all subnets must have explicit cidrBlocks.", + ); + continue; + } + + // Number of cidrBlocks must match the number of availability zones. + if (cidrBlocks.length !== availabilityZoneCount) { + issues.push( + `The number of CIDR blocks in subnetSpecs[${specIndex}] must match the number of availability zones (${availabilityZoneCount}).`, + ); + continue; + } + + // Any cidrMask specified must be in agreement with the cidrBlocks. + const blockMasks = spec.cidrBlocks!.map((b) => new Netmask(b)); + if (spec.cidrMask !== undefined) { + if (!blockMasks.every((b) => b.bitmask === spec.cidrMask)) { + issues.push( + `The cidrMask in subnetSpecs[${specIndex}] must match all cidrBlocks or be left undefined.`, + ); + continue; + } + } + + // Any size specified must be in agreement with the cidrBlocks. + if (spec.size !== undefined) { + if (!blockMasks.every((b) => b.size === spec.size!)) { + issues.push( + `The size in subnetSpecs[${specIndex}] must match all cidrBlocks or be left undefined.`, + ); + continue; + } + } + explicitSpecs.push({ + ...spec, + cidrBlocks, + }); + } + + if (issues.length === 0) { + return { normalizedSpecs: explicitSpecs, isExplicitLayout: true }; + } + } else { + const normalizedSpecs = subnetArgs.map((spec) => { + // Ensure size and cidrMask are in agreement. + const cidrMask = + spec.cidrMask ?? (spec.size ? validSubnetSizes.indexOf(spec.size!) : undefined); + const expectedSize = cidrMask ? validSubnetSizes[cidrMask] : undefined; + if (spec.size !== undefined && expectedSize !== undefined && expectedSize !== spec.size) { + issues.push( + `Subnet size ${spec.size} does not match the expected size for a /${cidrMask} subnet (${expectedSize}).`, + ); + } + // Set both cidrMask and size to the resolved values, if either was provided. + return { + ...spec, + cidrMask: cidrMask, + size: expectedSize, + }; + }); + + if (issues.length === 0) { + return { normalizedSpecs, isExplicitLayout: false }; + } + } + + throw new Error(`Invalid subnet specifications:\n - ${issues.join("\n - ")}`); +} + +// The index of the array is the corresponding netmask. +export const validSubnetSizes: readonly number[] = [ + 4294967296, 2147483648, 1073741824, 536870912, 268435456, 134217728, 67108864, 33554432, 16777216, + 8388608, 4194304, 2097152, 1048576, 524288, 262144, 131072, 65536, 32768, 16384, 8192, 4096, 2048, + 1024, 512, 256, 128, 64, 32, 16, 8, 4, +]; diff --git a/awsx/ec2/subnetsIntegration.test.ts b/awsx/ec2/subnetsIntegration.test.ts index 633f4a11b..2bcc667d3 100644 --- a/awsx/ec2/subnetsIntegration.test.ts +++ b/awsx/ec2/subnetsIntegration.test.ts @@ -13,12 +13,12 @@ // limitations under the License. import { SubnetSpecInputs } from "../schema-types"; -import { getSubnetSpecs } from "./subnetDistributor"; +import { getSubnetSpecsLegacy } from "./subnetDistributorLegacy"; import { getOverlappingSubnets } from "./vpc"; describe("subnet creation integration tests", () => { it("should return no overlapping subnets for the default subnet specs for this component", () => { - const defaultSpecs = getSubnetSpecs("dummy", "10.0.0.0/16", [ + const defaultSpecs = getSubnetSpecsLegacy("dummy", "10.0.0.0/16", [ "us-east-1a", "us-east-1b", "us-east-1c", @@ -39,7 +39,7 @@ describe("subnet creation integration tests", () => { }, ]; - const subnetSpecs = getSubnetSpecs( + const subnetSpecs = getSubnetSpecsLegacy( "dummy", "10.0.0.0/16", ["us-east-1a", "us-east-1b", "us-east-1c"], @@ -62,7 +62,7 @@ describe("subnet creation integration tests", () => { }, ]; - const subnetSpecs = getSubnetSpecs( + const subnetSpecs = getSubnetSpecsLegacy( "dummy", "10.0.0.0/16", ["us-east-1a", "us-east-1b", "us-east-1c"], @@ -84,7 +84,7 @@ describe("subnet creation integration tests", () => { }, ]; - const subnetSpecs = getSubnetSpecs( + const subnetSpecs = getSubnetSpecsLegacy( "dummy", "10.0.0.0/16", ["us-east-1a", "us-east-1b", "us-east-1c"], @@ -106,7 +106,7 @@ describe("subnet creation integration tests", () => { }, ]; - const subnetSpecs = getSubnetSpecs( + const subnetSpecs = getSubnetSpecsLegacy( "dummy", "10.0.0.0/16", ["us-east-1a", "us-east-1b", "us-east-1c"], @@ -132,7 +132,7 @@ describe("subnet creation integration tests", () => { }, ]; - const subnetSpecs = getSubnetSpecs( + const subnetSpecs = getSubnetSpecsLegacy( "dummy", "10.0.0.0/16", ["us-east-1a", "us-east-1b", "us-east-1c"], @@ -158,7 +158,7 @@ describe("subnet creation integration tests", () => { }, ]; - const subnetSpecs = getSubnetSpecs( + const subnetSpecs = getSubnetSpecsLegacy( "dummy", "10.0.0.0/16", ["us-east-1a", "us-east-1b", "us-east-1c"], @@ -196,7 +196,7 @@ describe("subnet creation integration tests", () => { }, ]; - const subnetSpecs = getSubnetSpecs( + const subnetSpecs = getSubnetSpecsLegacy( "dummy", "10.0.0.0/16", ["us-east-1a", "us-east-1b", "us-east-1c"], diff --git a/awsx/ec2/vpc.test.ts b/awsx/ec2/vpc.test.ts index a60df361d..f9c17aa28 100644 --- a/awsx/ec2/vpc.test.ts +++ b/awsx/ec2/vpc.test.ts @@ -12,17 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. +import fc from "fast-check"; import { getOverlappingSubnets } from "."; import { NatGatewayStrategyInputs, SubnetTypeInputs } from "../schema-types"; -import { getSubnetSpecs } from "./subnetDistributor"; +import { getSubnetSpecsLegacy } from "./subnetDistributorLegacy"; import { compareSubnetSpecs, + findSubnetGap, OverlappingSubnet, shouldCreateNatGateway, validateEips, validateNatGatewayStrategy, validateSubnets, } from "./vpc"; +import { Netmask, long2ip, ip2long } from "netmask"; describe("validateEips", () => { it("should not throw an exception if NAT Gateway strategy is Single and no EIPs are supplied", () => { @@ -263,3 +266,28 @@ describe("validateSubnets", () => { expect(() => validateSubnets(subnets, mockFunc)).toThrow(msg); }); }); + +describe("finding subnet gaps", () => { + it("comparison between any subnets", () => { + const vpcSpace = new Netmask("10.0.0.0", 7); + const minIpLong = ip2long(vpcSpace.first); + const maxIpLong = ip2long(vpcSpace.last); + fc.assert( + fc.property( + fc.record({ + fromIpLong: fc.integer({ min: minIpLong, max: maxIpLong }), + fromCidrMask: fc.integer({ min: 8, max: 32 }), + toIpLong: fc.integer({ min: minIpLong, max: maxIpLong }), + toCidrMask: fc.integer({ min: 8, max: 32 }), + }), + ({ fromIpLong: fromIp, fromCidrMask, toIpLong: toIp, toCidrMask }) => { + const from = new Netmask(long2ip(fromIp), fromCidrMask); + const to = new Netmask(long2ip(toIp), toCidrMask); + const gap = findSubnetGap(from, to); + expect(gap.length).toBeGreaterThanOrEqual(0); + expect(gap.length).toBeLessThan(32); + }, + ), + ); + }); +}); diff --git a/awsx/ec2/vpc.ts b/awsx/ec2/vpc.ts index 368a60c9a..b71931e11 100644 --- a/awsx/ec2/vpc.ts +++ b/awsx/ec2/vpc.ts @@ -15,7 +15,13 @@ import * as aws from "@pulumi/aws"; import * as pulumi from "@pulumi/pulumi"; import * as schema from "../schema-types"; -import { getSubnetSpecs, SubnetSpec } from "./subnetDistributor"; +import { getSubnetSpecsLegacy, SubnetSpec } from "./subnetDistributorLegacy"; +import { + getSubnetSpecs, + getSubnetSpecsExplicit, + validateAndNormalizeSubnetInputs, +} from "./subnetDistributorNew"; +import { Netmask } from "netmask"; interface VpcData { vpc: aws.ec2.Vpc; @@ -27,6 +33,7 @@ interface VpcData { igw: aws.ec2.InternetGateway; natGateways: aws.ec2.NatGateway[]; eips: aws.ec2.Eip[]; + subnetLayout: schema.ResolvedSubnetSpecInputs[]; publicSubnetIds: pulumi.Output[]; privateSubnetIds: pulumi.Output[]; isolatedSubnetIds: pulumi.Output[]; @@ -47,6 +54,7 @@ export class Vpc extends schema.Vpc { this.internetGateway = data.igw; this.natGateways = data.natGateways; this.eips = data.eips; + this.subnetLayout = data.subnetLayout; this.privateSubnetIds = data.privateSubnetIds; this.publicSubnetIds = data.publicSubnetIds; @@ -79,9 +87,61 @@ export class Vpc extends schema.Vpc { const cidrBlock = args.cidrBlock ?? "10.0.0.0/16"; - const subnetSpecs = getSubnetSpecs(name, cidrBlock, availabilityZones, args.subnetSpecs); + const parsedSpecs = validateAndNormalizeSubnetInputs( + args.subnetSpecs, + availabilityZones.length, + ); + + const subnetStrategy = args.subnetStrategy ?? "Legacy"; + const subnetSpecs = (() => { + if (parsedSpecs === undefined || subnetStrategy === "Legacy") { + const legacySubnetSpecs = getSubnetSpecsLegacy( + name, + cidrBlock, + availabilityZones, + parsedSpecs?.normalizedSpecs, + ); + return legacySubnetSpecs; + } + + if (parsedSpecs.isExplicitLayout) { + return getSubnetSpecsExplicit(name, availabilityZones, parsedSpecs.normalizedSpecs); + } + return getSubnetSpecs( + name, + cidrBlock, + availabilityZones, + parsedSpecs.normalizedSpecs, + args.availabilityZoneCidrMask, + ); + })(); + + let subnetLayout = parsedSpecs?.normalizedSpecs; + if (subnetStrategy === "Legacy" || subnetLayout === undefined) { + subnetLayout = extractSubnetSpecInputFromLegacyLayout(subnetSpecs, name, availabilityZones); + } + // Only warn if they're using a custom, non-explicit layout and haven't specified a strategy. + if ( + args.subnetStrategy === undefined && + parsedSpecs !== undefined && + parsedSpecs.isExplicitLayout === false + ) { + pulumi.log.warn( + `The default subnetStrategy will change from "Legacy" to "Auto" in the next major version. Please specify the subnetStrategy explicitly. The current subnet layout can be specified via "Auto" as:\n\n${JSON.stringify( + subnetLayout, + undefined, + 2, + )}`, + this, + ); + } + validateSubnets(subnetSpecs, getOverlappingSubnets); + if (subnetStrategy === "Exact") { + validateNoGaps(cidrBlock, subnetSpecs); + } + validateNatGatewayStrategy(natGatewayStrategy, subnetSpecs); const sharedTags = { Name: name, ...args.tags }; @@ -146,7 +206,7 @@ export class Vpc extends schema.Vpc { for (let i = 0; i < availabilityZones.length; i++) { subnetSpecs - .filter((x) => x.azName === availabilityZones[i]) + .filter((x) => x.azName === availabilityZones[i] && x.type !== "Unused") .sort(compareSubnetSpecs) .forEach((spec) => { const subnet = new aws.ec2.Subnet( @@ -278,6 +338,7 @@ export class Vpc extends schema.Vpc { routes, natGateways, eips, + subnetLayout: subnetLayout, privateSubnetIds, publicSubnetIds, isolatedSubnetIds, @@ -297,6 +358,71 @@ export class Vpc extends schema.Vpc { } } +export function extractSubnetSpecInputFromLegacyLayout( + subnetSpecs: SubnetSpec[], + vpcName: string, + availabilityZones: string[], +) { + const singleAzLength = subnetSpecs.length / availabilityZones.length; + function extractName(subnetName: string, type: schema.SubnetTypeInputs) { + const withoutVpcPrefix = subnetName.replace(`${vpcName}-`, ""); + const subnetSpecName = withoutVpcPrefix.replace(/-\d+$/, ""); + // If the spec name is the same as the type, it doesn't need to be specified. + if (subnetSpecName === type.toLowerCase()) { + return {}; + } + return { subnetName: subnetSpecName }; + } + const subnetSpecInputs: schema.SubnetSpecInputs[] = []; + // Just look at the first AZ's subnets, since they're all the same pattern. + let previousNetmask: Netmask | undefined; + const singleAzSubnets = subnetSpecs.slice(0, singleAzLength); + for (const subnet of singleAzSubnets) { + const netmask = new Netmask(subnet.cidrBlock); + if (previousNetmask !== undefined) { + const gaps = findSubnetGap(previousNetmask, netmask); + for (const gap of gaps) { + subnetSpecInputs.push({ + type: "Unused", + cidrMask: gap.bitmask, + }); + } + } + subnetSpecInputs.push({ + type: subnet.type, + ...extractName(subnet.subnetName, subnet.type), + cidrMask: netmask.bitmask, + ...(subnet.tags ? { tags: subnet.tags } : {}), + }); + previousNetmask = netmask; + } + return subnetSpecInputs; +} + +/** Find the subnets required to fill the gap between two subnets. */ +export function findSubnetGap(a: Netmask, b: Netmask): Netmask[] { + // Normalise start to be before the end. + const [start, end] = a.netLong < b.netLong ? [a, b] : [b, a]; + // Where the start and end differ by more than 1 bit there may be multiple subnets required to fill the gap. + const gaps: Netmask[] = []; + let previous = a; + let next = start.next(); + while (next.netLong < end.netLong) { + // Try to find widest possible gap that doesn't overlap the start or end subnet. + while (true) { + const nextWiderGap = new Netmask(`${next.base}/${next.bitmask - 1}`); + if (nextWiderGap.contains(previous.last) || nextWiderGap.contains(end.first)) { + break; + } + next = nextWiderGap; + } + gaps.push(next); + previous = next; + next = next.next(); + } + return gaps; +} + export function validateEips( natGatewayStrategy: schema.NatGatewayStrategyInputs, eips: pulumi.Input[] | undefined, @@ -433,3 +559,43 @@ export function validateSubnets( throw new Error(msg); } } +export function validateNoGaps(vpcCidr: string, subnetSpecs: SubnetSpec[]) { + const vpcNetmask = new Netmask(vpcCidr); + const gaps: string[] = []; + let current: SubnetSpec | undefined; + for (const spec of subnetSpecs) { + const prev = current; + current = spec; + const currentNetmask = new Netmask(current.cidrBlock); + if (prev === undefined) { + // Check the first subnet against the VPC CIDR + if (currentNetmask.base !== vpcNetmask.base) { + gaps.push( + `${spec.subnetName} (${spec.cidrBlock}) does not start at the beginning of the VPC (${vpcCidr})`, + ); + } + continue; + } + const prevNetmask = new Netmask(prev.cidrBlock); + const expectedNext = prevNetmask.next(); + if (currentNetmask.base !== expectedNext.base) { + gaps.push( + `${prev.subnetName} (${prev.cidrBlock}) <=> ${spec.subnetName} (${spec.cidrBlock})`, + ); + } + } + const lastBlockNetmask = new Netmask(current!.cidrBlock); + if (lastBlockNetmask.last !== vpcNetmask.last) { + gaps.push( + `${current!.subnetName} (ending ${lastBlockNetmask.last}) ends before VPC ends (at ${ + vpcNetmask.last + }})`, + ); + } + if (gaps.length === 0) { + return; + } + throw new Error( + `There are gaps in the subnet ranges. Please fix the following gaps: ${gaps.join(", ")}`, + ); +} diff --git a/awsx/index.ts b/awsx/index.ts index 7df9fb56d..32d4b1d11 100644 --- a/awsx/index.ts +++ b/awsx/index.ts @@ -73,6 +73,10 @@ function main(args: string[]) { encoding: "utf-8", }); let version: string = require("./package.json").version; + if (version === "${VERSION}" && require("inspector")?.url() !== undefined) { + // We're running in debug mode so just use a valid placeholder version. + version = "0.0.0"; + } // Node allows for the version to be prefixed by a "v", // while semver doesn't. If there is a v, strip it off. if (version.startsWith("v")) { diff --git a/awsx/package.json b/awsx/package.json index 026916885..b91b0957e 100644 --- a/awsx/package.json +++ b/awsx/package.json @@ -17,7 +17,8 @@ "format": "prettier --list-different --write .", "lint": "tslint -c tslint.json -p tsconfig.json", "test": "jest", - "tsc": "tsc --incremental" + "tsc": "tsc --incremental", + "start": "ts-node index.ts" }, "//": "Pulumi sub-provider dependencies must be pinned at an exact version because we extract this value to generate the correct dependency in the schema", "dependencies": { @@ -38,6 +39,7 @@ "@types/jest": "^29.0.0", "@types/jsbn": "^1.2.32", "@types/mime": "^3.0.0", + "@types/netmask": "^2.0.5", "@types/node": "^18", "babel-jest": "^29.0.0", "fast-check": "^3.13.2", diff --git a/awsx/schema-types.ts b/awsx/schema-types.ts index ce8509743..cc37674a2 100644 --- a/awsx/schema-types.ts +++ b/awsx/schema-types.ts @@ -69,16 +69,18 @@ export abstract class Vpc extends (pulumi.ComponentResource) public routeTableAssociations!: aws.ec2.RouteTableAssociation[] | pulumi.Output; public routeTables!: aws.ec2.RouteTable[] | pulumi.Output; public routes!: aws.ec2.Route[] | pulumi.Output; + public subnetLayout!: ResolvedSubnetSpecOutputs[] | pulumi.Output; public subnets!: aws.ec2.Subnet[] | pulumi.Output; public vpc!: aws.ec2.Vpc | pulumi.Output; public vpcEndpoints!: aws.ec2.VpcEndpoint[] | pulumi.Output; public vpcId!: string | pulumi.Output; constructor(name: string, args: pulumi.Inputs, opts: pulumi.ComponentResourceOptions = {}) { - super("awsx:ec2:Vpc", name, opts.urn ? { eips: undefined, internetGateway: undefined, isolatedSubnetIds: undefined, natGateways: undefined, privateSubnetIds: undefined, publicSubnetIds: undefined, routeTableAssociations: undefined, routeTables: undefined, routes: undefined, subnets: undefined, vpc: undefined, vpcEndpoints: undefined, vpcId: undefined } : { name, args, opts }, opts); + super("awsx:ec2:Vpc", name, opts.urn ? { eips: undefined, internetGateway: undefined, isolatedSubnetIds: undefined, natGateways: undefined, privateSubnetIds: undefined, publicSubnetIds: undefined, routeTableAssociations: undefined, routeTables: undefined, routes: undefined, subnetLayout: undefined, subnets: undefined, vpc: undefined, vpcEndpoints: undefined, vpcId: undefined } : { name, args, opts }, opts); } } export interface VpcArgs { readonly assignGeneratedIpv6CidrBlock?: pulumi.Input; + readonly availabilityZoneCidrMask?: number; readonly availabilityZoneNames?: string[]; readonly cidrBlock?: string; readonly enableDnsHostnames?: pulumi.Input; @@ -94,6 +96,7 @@ export interface VpcArgs { readonly natGateways?: NatGatewayConfigurationInputs; readonly numberOfAvailabilityZones?: number; readonly subnetSpecs?: SubnetSpecInputs[]; + readonly subnetStrategy?: SubnetAllocationStrategyInputs; readonly tags?: pulumi.Input>>; readonly vpcEndpointSpecs?: VpcEndpointSpecInputs[]; } @@ -568,20 +571,40 @@ export interface NatGatewayConfigurationOutputs { } export type NatGatewayStrategyInputs = "None" | "Single" | "OnePerAz"; export type NatGatewayStrategyOutputs = "None" | "Single" | "OnePerAz"; +export interface ResolvedSubnetSpecInputs { + readonly cidrBlocks?: string[]; + readonly cidrMask?: number; + readonly name?: string; + readonly size?: number; + readonly type: SubnetTypeInputs; +} +export interface ResolvedSubnetSpecOutputs { + readonly cidrBlocks?: string[]; + readonly cidrMask?: number; + readonly name?: string; + readonly size?: number; + readonly type: SubnetTypeOutputs; +} +export type SubnetAllocationStrategyInputs = "Legacy" | "Auto" | "Exact"; +export type SubnetAllocationStrategyOutputs = "Legacy" | "Auto" | "Exact"; export interface SubnetSpecInputs { + readonly cidrBlocks?: string[]; readonly cidrMask?: number; readonly name?: string; + readonly size?: number; readonly tags?: pulumi.Input>>; readonly type: SubnetTypeInputs; } export interface SubnetSpecOutputs { + readonly cidrBlocks?: string[]; readonly cidrMask?: number; readonly name?: string; + readonly size?: number; readonly tags?: pulumi.Output>; readonly type: SubnetTypeOutputs; } -export type SubnetTypeInputs = "Public" | "Private" | "Isolated"; -export type SubnetTypeOutputs = "Public" | "Private" | "Isolated"; +export type SubnetTypeInputs = "Public" | "Private" | "Isolated" | "Unused"; +export type SubnetTypeOutputs = "Public" | "Private" | "Isolated" | "Unused"; export interface VpcEndpointSpecInputs { readonly autoAccept?: boolean; readonly dnsOptions?: pulumi.Input; diff --git a/awsx/schema.json b/awsx/schema.json index 14129e357..b8adb91ac 100644 --- a/awsx/schema.json +++ b/awsx/schema.json @@ -563,19 +563,89 @@ } ] }, + "awsx:ec2:ResolvedSubnetSpec": { + "description": "Configuration for a VPC subnet spec.", + "properties": { + "cidrBlocks": { + "type": "array", + "items": { + "type": "string", + "plain": true + }, + "plain": true, + "description": "An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs." + }, + "cidrMask": { + "type": "integer", + "plain": true, + "description": "The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone." + }, + "name": { + "type": "string", + "plain": true, + "description": "The subnet's name. Will be templated upon creation." + }, + "size": { + "type": "integer", + "plain": true, + "description": "Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone." + }, + "type": { + "$ref": "#/types/awsx:ec2:SubnetType", + "plain": true, + "description": "The type of subnet." + } + }, + "type": "object", + "required": [ + "type" + ] + }, + "awsx:ec2:SubnetAllocationStrategy": { + "description": "Strategy for calculating subnet ranges from the subnet specifications.", + "type": "string", + "enum": [ + { + "description": "Group private subnets first, followed by public subnets, followed by isolated subnets.", + "value": "Legacy" + }, + { + "description": "Order remains as specified by specs, allowing gaps where required.", + "value": "Auto" + }, + { + "description": "Whole range of VPC must be accounted for, using \"Unused\" spec types for deliberate gaps.", + "value": "Exact" + } + ] + }, "awsx:ec2:SubnetSpec": { "description": "Configuration for a VPC subnet.", "properties": { + "cidrBlocks": { + "type": "array", + "items": { + "type": "string", + "plain": true + }, + "plain": true, + "description": "An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs." + }, "cidrMask": { "type": "integer", "plain": true, - "description": "The bitmask for the subnet's CIDR block." + "description": "The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone." }, "name": { "type": "string", "plain": true, "description": "The subnet's name. Will be templated upon creation." }, + "size": { + "type": "integer", + "plain": true, + "description": "Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone." + }, "tags": { "type": "object", "additionalProperties": { @@ -609,6 +679,10 @@ { "description": "A subnet whose hosts have no connectivity with the internet.", "value": "Isolated" + }, + { + "description": "A subnet range which is reserved, but no subnet will be created.", + "value": "Unused" } ] }, @@ -1822,6 +1896,7 @@ "isComponent": true }, "awsx:ec2:Vpc": { + "description": "The VPC component provides a VPC with configured subnets and NAT gateways.\n\n{{% examples %}}\n\n## Example Usage\n\n{{% example %}}\n\nBasic usage:\n\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as awsx from \"@pulumi/awsx\";\n\nconst vpc = new awsx.ec2.Vpc(\"vpc\", {});\nexport const vpcId = vpc.vpcId;\nexport const vpcPrivateSubnetIds = vpc.privateSubnetIds;\nexport const vpcPublicSubnetIds = vpc.publicSubnetIds;\n```\n\n```python\nimport pulumi\nimport pulumi_awsx as awsx\n\nvpc = awsx.ec2.Vpc(\"vpc\")\npulumi.export(\"vpcId\", vpc.vpc_id)\npulumi.export(\"vpcPrivateSubnetIds\", vpc.private_subnet_ids)\npulumi.export(\"vpcPublicSubnetIds\", vpc.public_subnet_ids)\n```\n\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Awsx = Pulumi.Awsx;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var vpc = new Awsx.Ec2.Vpc(\"vpc\");\n\n return new Dictionary\u003cstring, object?\u003e\n {\n [\"vpcId\"] = vpc.VpcId,\n [\"vpcPrivateSubnetIds\"] = vpc.PrivateSubnetIds,\n [\"vpcPublicSubnetIds\"] = vpc.PublicSubnetIds,\n };\n});\n```\n\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/ec2\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\tvpc, err := ec2.NewVpc(ctx, \"vpc\", nil)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tctx.Export(\"vpcId\", vpc.VpcId)\n\t\tctx.Export(\"vpcPrivateSubnetIds\", vpc.PrivateSubnetIds)\n\t\tctx.Export(\"vpcPublicSubnetIds\", vpc.PublicSubnetIds)\n\t\treturn nil\n\t})\n}\n```\n\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.awsx.ec2.Vpc;\nimport java.util.List;\nimport java.util.ArrayList;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var vpc = new Vpc(\"vpc\");\n\n ctx.export(\"vpcId\", vpc.vpcId());\n ctx.export(\"vpcPrivateSubnetIds\", vpc.privateSubnetIds());\n ctx.export(\"vpcPublicSubnetIds\", vpc.publicSubnetIds());\n }\n}\n```\n\n```yaml\nresources:\n vpc:\n type: awsx:ec2:Vpc\noutputs:\n vpcId: ${vpc.vpcId}\n vpcPrivateSubnetIds: ${vpc.privateSubnetIds}\n vpcPublicSubnetIds: ${vpc.publicSubnetIds}\n```\n\n{{% /example %}}\n{{% /examples %}}\n\n## Subnet Layout Strategies\n\nIf no subnet arguments are passed, then a public and private subnet will be created in each AZ with default sizing. The layout of these subnets can be customised by specifying additional arguments.\n\nAll strategies are designed to help build a uniform layout of subnets each each availability zone.\n\nIf no strategy is specified, \"Legacy\" will be used for backward compatibility reasons. In the next major version this will change to defaulting to \"Auto\".\n\n### Auto\n\nThe \"Auto\" strategy divides the VPC space evenly between the availability zones. Within each availability zone it allocates each subnet in the order they were specified. If a CIDR mask or size was not specified it will default to an even division of the availability zone range. If subnets have different sizes, spaces will be automatically added to ensure subnets don't overlap (e.g. where a previous subnet is smaller than the next).\n\n### Exact\n\nThe \"Exact\" strategy is the same as \"Auto\" with the additional requirement to explicitly specify what the whole of each zone's range will be used for. Where you expect to have a gap between or after subnets, these must be passed using the subnet specification type \"Unused\" to show all space has been properly accounted for.\n\n### Explicit CIDR Blocks\n\nIf you prefer to do your CIDR block calculations yourself, you can specify a list of CIDR blocks for each subnet spec which it will be allocated for in each availability zone. If using explicit layouts, all subnet specs must be declared with explicit CIDR blocks. Each list of CIDR blocks must have the same length as the number of availability zones for the VPC.\n\n### Legacy\n\nThe \"Legacy\" works similarly to the \"Auto\" strategy except that within each availability zone it allocates the private subnet first, followed by the private subnets, and lastly the isolated subnets. The order of subnet specifications of the same type can be changed, but the ordering of private, public, isolated is not overridable. For more flexibility we recommend moving to the \"Auto\" strategy. The output property `subnetLayout` shows the configuration required if specifying the \"Auto\" strategy to maintain the current layout.\n", "properties": { "eips": { "type": "array", @@ -1880,6 +1955,13 @@ }, "description": "The Routes for the VPC." }, + "subnetLayout": { + "type": "array", + "items": { + "$ref": "#/types/awsx:ec2:ResolvedSubnetSpec" + }, + "description": "The resolved subnet specs layout deployed to each availability zone." + }, "subnets": { "type": "array", "items": { @@ -1916,6 +1998,7 @@ "internetGateway", "natGateways", "eips", + "subnetLayout", "publicSubnetIds", "privateSubnetIds", "isolatedSubnetIds", @@ -1927,6 +2010,11 @@ "type": "boolean", "description": "Requests an Amazon-provided IPv6 CIDR block with a /56 prefix length for the VPC. You cannot specify the range of IP addresses, or the size of the CIDR block. Default is `false`. Conflicts with `ipv6_ipam_pool_id`\n" }, + "availabilityZoneCidrMask": { + "type": "integer", + "plain": true, + "description": "The netmask for each available zone to be aligned to. This is optional, the default value is inferred based on an even distribution of available space from the VPC's CIDR block after being divided evenly by the number of availability zones." + }, "availabilityZoneNames": { "type": "array", "items": { @@ -2002,6 +2090,11 @@ "plain": true, "description": "A list of subnet specs that should be deployed to each AZ specified in availabilityZoneNames. Optional. Defaults to a (smaller) public subnet and a (larger) private subnet based on the size of the CIDR block for the VPC. Private subnets are allocated CIDR block ranges first, followed by Private subnets, and Isolated subnets are allocated last." }, + "subnetStrategy": { + "$ref": "#/types/awsx:ec2:SubnetAllocationStrategy", + "plain": true, + "description": "The strategy to use when allocating subnets for the VPC. Optional. Defaults to `Legacy`." + }, "tags": { "type": "object", "additionalProperties": { diff --git a/awsx/yarn.lock b/awsx/yarn.lock index f3a069086..0eb68e00b 100644 --- a/awsx/yarn.lock +++ b/awsx/yarn.lock @@ -1762,6 +1762,11 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== +"@types/netmask@^2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/netmask/-/netmask-2.0.5.tgz#8eeedf87a08e993c14d70fae9c17837c868f4ca3" + integrity sha512-9Q5iw9+pHZBVLDG700dlQSWWHTYvOb8KfPjfQTNckCYky4IyjV2xh81+RgC1CCwqv92bYLpz1cVKyJav0B88uQ== + "@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0": version "20.6.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.0.tgz#9d7daa855d33d4efec8aea88cd66db1c2f0ebe16" diff --git a/schemagen/pkg/gen/ec2-vpc.md b/schemagen/pkg/gen/ec2-vpc.md new file mode 100644 index 000000000..de0991b50 --- /dev/null +++ b/schemagen/pkg/gen/ec2-vpc.md @@ -0,0 +1,136 @@ +The VPC component provides a VPC with configured subnets and NAT gateways. + +{{% examples %}} + +## Example Usage + +{{% example %}} + +Basic usage: + +```typescript +import * as pulumi from "@pulumi/pulumi"; +import * as awsx from "@pulumi/awsx"; + +const vpc = new awsx.ec2.Vpc("vpc", {}); +export const vpcId = vpc.vpcId; +export const vpcPrivateSubnetIds = vpc.privateSubnetIds; +export const vpcPublicSubnetIds = vpc.publicSubnetIds; +``` + +```python +import pulumi +import pulumi_awsx as awsx + +vpc = awsx.ec2.Vpc("vpc") +pulumi.export("vpcId", vpc.vpc_id) +pulumi.export("vpcPrivateSubnetIds", vpc.private_subnet_ids) +pulumi.export("vpcPublicSubnetIds", vpc.public_subnet_ids) +``` + +```csharp +using System.Collections.Generic; +using System.Linq; +using Pulumi; +using Awsx = Pulumi.Awsx; + +return await Deployment.RunAsync(() => +{ + var vpc = new Awsx.Ec2.Vpc("vpc"); + + return new Dictionary + { + ["vpcId"] = vpc.VpcId, + ["vpcPrivateSubnetIds"] = vpc.PrivateSubnetIds, + ["vpcPublicSubnetIds"] = vpc.PublicSubnetIds, + }; +}); +``` + +```go +package main + +import ( + "github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/ec2" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +func main() { + pulumi.Run(func(ctx *pulumi.Context) error { + vpc, err := ec2.NewVpc(ctx, "vpc", nil) + if err != nil { + return err + } + ctx.Export("vpcId", vpc.VpcId) + ctx.Export("vpcPrivateSubnetIds", vpc.PrivateSubnetIds) + ctx.Export("vpcPublicSubnetIds", vpc.PublicSubnetIds) + return nil + }) +} +``` + +```java +package generated_program; + +import com.pulumi.Context; +import com.pulumi.Pulumi; +import com.pulumi.core.Output; +import com.pulumi.awsx.ec2.Vpc; +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class App { + public static void main(String[] args) { + Pulumi.run(App::stack); + } + + public static void stack(Context ctx) { + var vpc = new Vpc("vpc"); + + ctx.export("vpcId", vpc.vpcId()); + ctx.export("vpcPrivateSubnetIds", vpc.privateSubnetIds()); + ctx.export("vpcPublicSubnetIds", vpc.publicSubnetIds()); + } +} +``` + +```yaml +resources: + vpc: + type: awsx:ec2:Vpc +outputs: + vpcId: ${vpc.vpcId} + vpcPrivateSubnetIds: ${vpc.privateSubnetIds} + vpcPublicSubnetIds: ${vpc.publicSubnetIds} +``` + +{{% /example %}} +{{% /examples %}} + +## Subnet Layout Strategies + +If no subnet arguments are passed, then a public and private subnet will be created in each AZ with default sizing. The layout of these subnets can be customised by specifying additional arguments. + +All strategies are designed to help build a uniform layout of subnets each each availability zone. + +If no strategy is specified, "Legacy" will be used for backward compatibility reasons. In the next major version this will change to defaulting to "Auto". + +### Auto + +The "Auto" strategy divides the VPC space evenly between the availability zones. Within each availability zone it allocates each subnet in the order they were specified. If a CIDR mask or size was not specified it will default to an even division of the availability zone range. If subnets have different sizes, spaces will be automatically added to ensure subnets don't overlap (e.g. where a previous subnet is smaller than the next). + +### Exact + +The "Exact" strategy is the same as "Auto" with the additional requirement to explicitly specify what the whole of each zone's range will be used for. Where you expect to have a gap between or after subnets, these must be passed using the subnet specification type "Unused" to show all space has been properly accounted for. + +### Explicit CIDR Blocks + +If you prefer to do your CIDR block calculations yourself, you can specify a list of CIDR blocks for each subnet spec which it will be allocated for in each availability zone. If using explicit layouts, all subnet specs must be declared with explicit CIDR blocks. Each list of CIDR blocks must have the same length as the number of availability zones for the VPC. + +### Legacy + +The "Legacy" works similarly to the "Auto" strategy except that within each availability zone it allocates the private subnet first, followed by the private subnets, and lastly the isolated subnets. The order of subnet specifications of the same type can be changed, but the ordering of private, public, isolated is not overridable. For more flexibility we recommend moving to the "Auto" strategy. The output property `subnetLayout` shows the configuration required if specifying the "Auto" strategy to maintain the current layout. diff --git a/schemagen/pkg/gen/ec2.go b/schemagen/pkg/gen/ec2.go index f7fbe22c3..5fa2e331c 100644 --- a/schemagen/pkg/gen/ec2.go +++ b/schemagen/pkg/gen/ec2.go @@ -15,6 +15,7 @@ package gen import ( + _ "embed" "fmt" "strings" @@ -28,13 +29,15 @@ func generateEc2(awsSpec schema.PackageSpec) schema.PackageSpec { "awsx:ec2:DefaultVpc": defaultVpcResource(awsSpec), }, Types: map[string]schema.ComplexTypeSpec{ - "awsx:awsx:DefaultSecurityGroup": defaultSecurityGroupArgs(awsSpec), - "awsx:awsx:SecurityGroup": securityGroupArgs(awsSpec), - "awsx:ec2:NatGatewayStrategy": natGatewayStrategyType(), - "awsx:ec2:NatGatewayConfiguration": natGatewayConfigurationType(), - "awsx:ec2:SubnetType": subnetType(), - "awsx:ec2:SubnetSpec": subnetSpecType(), - "awsx:ec2:VpcEndpointSpec": vpcEndpointSpec(awsSpec), + "awsx:awsx:DefaultSecurityGroup": defaultSecurityGroupArgs(awsSpec), + "awsx:awsx:SecurityGroup": securityGroupArgs(awsSpec), + "awsx:ec2:NatGatewayStrategy": natGatewayStrategyType(), + "awsx:ec2:NatGatewayConfiguration": natGatewayConfigurationType(), + "awsx:ec2:SubnetType": subnetType(), + "awsx:ec2:SubnetAllocationStrategy": subnetAllocationStrategy(), + "awsx:ec2:SubnetSpec": subnetSpecType(), + "awsx:ec2:ResolvedSubnetSpec": resolvedSubnetSpecType(), + "awsx:ec2:VpcEndpointSpec": vpcEndpointSpec(awsSpec), }, Functions: map[string]schema.FunctionSpec{ "awsx:ec2:getDefaultVpc": defaultVpcArgs(), @@ -90,6 +93,9 @@ func securityGroupArgs(awsSpec schema.PackageSpec) schema.ComplexTypeSpec { } } +//go:embed ec2-vpc.md +var vpcDocs string + func vpcResource(awsSpec schema.PackageSpec) schema.ResourceSpec { awsVpcResource := awsSpec.Resources["aws:ec2/vpc:Vpc"] inputProperties := map[string]schema.PropertySpec{ @@ -99,10 +105,11 @@ func vpcResource(awsSpec schema.PackageSpec) schema.ResourceSpec { }, "numberOfAvailabilityZones": { Description: fmt.Sprintf("A number of availability zones to which the subnets defined in %v will be deployed. Optional, defaults to the first 3 AZs in the current region.", subnetSpecs), - TypeSpec: schema.TypeSpec{ - Type: "integer", - Plain: true, - }, + TypeSpec: plainInt(), + }, + "availabilityZoneCidrMask": { + Description: "The netmask for each available zone to be aligned to. This is optional, the default value is inferred based on an even distribution of available space from the VPC's CIDR block after being divided evenly by the number of availability zones.", + TypeSpec: plainInt(), }, "cidrBlock": { Description: "The CIDR block for the VPC. Optional. Defaults to 10.0.0.0/16.", @@ -115,6 +122,13 @@ func vpcResource(awsSpec schema.PackageSpec) schema.ResourceSpec { Plain: true, }, }, + "subnetStrategy": { + Description: "The strategy to use when allocating subnets for the VPC. Optional. Defaults to `Legacy`.", + TypeSpec: schema.TypeSpec{ + Ref: localRef("ec2", "SubnetAllocationStrategy"), + Plain: true, + }, + }, subnetSpecs: { Description: fmt.Sprintf("A list of subnet specs that should be deployed to each AZ specified in %s. Optional. Defaults to a (smaller) public subnet and a (larger) private subnet based on the size of the CIDR block for the VPC. Private subnets are allocated CIDR block ranges first, followed by Private subnets, and Isolated subnets are allocated last.", availabilityZoneNames), TypeSpec: plainArrayOfPlainComplexType("SubnetSpec"), @@ -138,6 +152,7 @@ func vpcResource(awsSpec schema.PackageSpec) schema.ResourceSpec { return schema.ResourceSpec{ IsComponent: true, ObjectTypeSpec: schema.ObjectTypeSpec{ + Description: vpcDocs, Properties: map[string]schema.PropertySpec{ "vpc": { Description: "The VPC.", @@ -180,6 +195,13 @@ func vpcResource(awsSpec schema.PackageSpec) schema.ResourceSpec { Description: "The EIPs for any NAT Gateways for the VPC. If no NAT Gateways are specified, this will be an empty list.", TypeSpec: arrayOfAwsType(awsSpec, "ec2", "eip"), }, + "subnetLayout": { + Description: "The resolved subnet specs layout deployed to each availability zone.", + TypeSpec: schema.TypeSpec{ + Type: "array", + Items: &schema.TypeSpec{Ref: localRef("ec2", "ResolvedSubnetSpec")}, + }, + }, "publicSubnetIds": { TypeSpec: schema.TypeSpec{ Type: "array", @@ -212,7 +234,7 @@ func vpcResource(awsSpec schema.PackageSpec) schema.ResourceSpec { }, Required: []string{ "vpc", "subnets", "routeTables", "routeTableAssociations", "routes", "internetGateway", "natGateways", - "eips", "publicSubnetIds", "privateSubnetIds", "isolatedSubnetIds", "vpcId", "vpcEndpoints", + "eips", "subnetLayout", "publicSubnetIds", "privateSubnetIds", "isolatedSubnetIds", "vpcId", "vpcEndpoints", }, }, InputProperties: inputProperties, @@ -311,7 +333,15 @@ func subnetSpecType() schema.ComplexTypeSpec { "cidrMask": { // The validation rules are too difficult to concisely describe here, so we'll leave that job to any // error messages generated from the component itself. - Description: "The bitmask for the subnet's CIDR block.", + Description: "The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone.", + TypeSpec: plainInt(), + }, + "cidrBlocks": { + Description: "An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs.", + TypeSpec: plainArrayOfPlainStrings(), + }, + "size": { + Description: "Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone.", TypeSpec: plainInt(), }, "tags": { @@ -329,6 +359,45 @@ func subnetSpecType() schema.ComplexTypeSpec { } } +func resolvedSubnetSpecType() schema.ComplexTypeSpec { + return schema.ComplexTypeSpec{ + ObjectTypeSpec: schema.ObjectTypeSpec{ + Type: "object", + Description: "Configuration for a VPC subnet spec.", + Properties: map[string]schema.PropertySpec{ + "type": { + Description: "The type of subnet.", + TypeSpec: schema.TypeSpec{ + Ref: localRef("ec2", "SubnetType"), + Plain: true, + }, + }, + "name": { + Description: "The subnet's name. Will be templated upon creation.", + TypeSpec: plainString(), + }, + "cidrMask": { + // The validation rules are too difficult to concisely describe here, so we'll leave that job to any + // error messages generated from the component itself. + Description: "The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone.", + TypeSpec: plainInt(), + }, + "cidrBlocks": { + Description: "An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs.", + TypeSpec: plainArrayOfPlainStrings(), + }, + "size": { + Description: "Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone.", + TypeSpec: plainInt(), + }, + }, + Required: []string{ + "type", + }, + }, + } +} + func subnetType() schema.ComplexTypeSpec { return schema.ComplexTypeSpec{ ObjectTypeSpec: schema.ObjectTypeSpec{ @@ -348,6 +417,33 @@ func subnetType() schema.ComplexTypeSpec { Value: "Isolated", Description: "A subnet whose hosts have no connectivity with the internet.", }, + { + Value: "Unused", + Description: "A subnet range which is reserved, but no subnet will be created.", + }, + }, + } +} + +func subnetAllocationStrategy() schema.ComplexTypeSpec { + return schema.ComplexTypeSpec{ + ObjectTypeSpec: schema.ObjectTypeSpec{ + Type: "string", + Description: "Strategy for calculating subnet ranges from the subnet specifications.", + }, + Enum: []schema.EnumValueSpec{ + { + Value: "Legacy", + Description: "Group private subnets first, followed by public subnets, followed by isolated subnets.", + }, + { + Value: "Auto", + Description: "Order remains as specified by specs, allowing gaps where required.", + }, + { + Value: "Exact", + Description: "Whole range of VPC must be accounted for, using \"Unused\" spec types for deliberate gaps.", + }, }, } } diff --git a/sdk/dotnet/Ec2/Enums.cs b/sdk/dotnet/Ec2/Enums.cs index 7e809f51c..700ab0625 100644 --- a/sdk/dotnet/Ec2/Enums.cs +++ b/sdk/dotnet/Ec2/Enums.cs @@ -48,6 +48,47 @@ private NatGatewayStrategy(string value) public override string ToString() => _value; } + /// + /// Strategy for calculating subnet ranges from the subnet specifications. + /// + [EnumType] + public readonly struct SubnetAllocationStrategy : IEquatable + { + private readonly string _value; + + private SubnetAllocationStrategy(string value) + { + _value = value ?? throw new ArgumentNullException(nameof(value)); + } + + /// + /// Group private subnets first, followed by public subnets, followed by isolated subnets. + /// + public static SubnetAllocationStrategy Legacy { get; } = new SubnetAllocationStrategy("Legacy"); + /// + /// Order remains as specified by specs, allowing gaps where required. + /// + public static SubnetAllocationStrategy Auto { get; } = new SubnetAllocationStrategy("Auto"); + /// + /// Whole range of VPC must be accounted for, using "Unused" spec types for deliberate gaps. + /// + public static SubnetAllocationStrategy Exact { get; } = new SubnetAllocationStrategy("Exact"); + + public static bool operator ==(SubnetAllocationStrategy left, SubnetAllocationStrategy right) => left.Equals(right); + public static bool operator !=(SubnetAllocationStrategy left, SubnetAllocationStrategy right) => !left.Equals(right); + + public static explicit operator string(SubnetAllocationStrategy value) => value._value; + + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => obj is SubnetAllocationStrategy other && Equals(other); + public bool Equals(SubnetAllocationStrategy other) => string.Equals(_value, other._value, StringComparison.Ordinal); + + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => _value?.GetHashCode() ?? 0; + + public override string ToString() => _value; + } + /// /// A type of subnet within a VPC. /// @@ -73,6 +114,10 @@ private SubnetType(string value) /// A subnet whose hosts have no connectivity with the internet. /// public static SubnetType Isolated { get; } = new SubnetType("Isolated"); + /// + /// A subnet range which is reserved, but no subnet will be created. + /// + public static SubnetType Unused { get; } = new SubnetType("Unused"); public static bool operator ==(SubnetType left, SubnetType right) => left.Equals(right); public static bool operator !=(SubnetType left, SubnetType right) => !left.Equals(right); diff --git a/sdk/dotnet/Ec2/Inputs/SubnetSpecArgs.cs b/sdk/dotnet/Ec2/Inputs/SubnetSpecArgs.cs index 3ada509fb..399d71264 100644 --- a/sdk/dotnet/Ec2/Inputs/SubnetSpecArgs.cs +++ b/sdk/dotnet/Ec2/Inputs/SubnetSpecArgs.cs @@ -15,8 +15,20 @@ namespace Pulumi.Awsx.Ec2.Inputs /// public sealed class SubnetSpecArgs : global::Pulumi.ResourceArgs { + [Input("cidrBlocks")] + private List? _cidrBlocks; + /// - /// The bitmask for the subnet's CIDR block. + /// An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. + /// + public List CidrBlocks + { + get => _cidrBlocks ?? (_cidrBlocks = new List()); + set => _cidrBlocks = value; + } + + /// + /// The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. /// [Input("cidrMask")] public int? CidrMask { get; set; } @@ -27,6 +39,12 @@ public sealed class SubnetSpecArgs : global::Pulumi.ResourceArgs [Input("name")] public string? Name { get; set; } + /// + /// Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + /// + [Input("size")] + public int? Size { get; set; } + [Input("tags")] private InputMap? _tags; diff --git a/sdk/dotnet/Ec2/Outputs/ResolvedSubnetSpec.cs b/sdk/dotnet/Ec2/Outputs/ResolvedSubnetSpec.cs new file mode 100644 index 000000000..9cfa52fb6 --- /dev/null +++ b/sdk/dotnet/Ec2/Outputs/ResolvedSubnetSpec.cs @@ -0,0 +1,59 @@ +// *** WARNING: this file was generated by pulumi-gen-awsx. *** +// *** Do not edit by hand unless you're certain you know what you are doing! *** + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Pulumi.Serialization; + +namespace Pulumi.Awsx.Ec2.Outputs +{ + + /// + /// Configuration for a VPC subnet spec. + /// + [OutputType] + public sealed class ResolvedSubnetSpec + { + /// + /// An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. + /// + public readonly ImmutableArray CidrBlocks; + /// + /// The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + /// + public readonly int? CidrMask; + /// + /// The subnet's name. Will be templated upon creation. + /// + public readonly string? Name; + /// + /// Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + /// + public readonly int? Size; + /// + /// The type of subnet. + /// + public readonly Pulumi.Awsx.Ec2.SubnetType Type; + + [OutputConstructor] + private ResolvedSubnetSpec( + ImmutableArray cidrBlocks, + + int? cidrMask, + + string? name, + + int? size, + + Pulumi.Awsx.Ec2.SubnetType type) + { + CidrBlocks = cidrBlocks; + CidrMask = cidrMask; + Name = name; + Size = size; + Type = type; + } + } +} diff --git a/sdk/dotnet/Ec2/Vpc.cs b/sdk/dotnet/Ec2/Vpc.cs index 5ba4d7511..2400e8a63 100644 --- a/sdk/dotnet/Ec2/Vpc.cs +++ b/sdk/dotnet/Ec2/Vpc.cs @@ -9,6 +9,56 @@ namespace Pulumi.Awsx.Ec2 { + /// + /// The VPC component provides a VPC with configured subnets and NAT gateways. + /// + /// ## Example Usage + /// + /// Basic usage: + /// + /// ```csharp + /// using System.Collections.Generic; + /// using System.Linq; + /// using Pulumi; + /// using Awsx = Pulumi.Awsx; + /// + /// return await Deployment.RunAsync(() => + /// { + /// var vpc = new Awsx.Ec2.Vpc("vpc"); + /// + /// return new Dictionary<string, object?> + /// { + /// ["vpcId"] = vpc.VpcId, + /// ["vpcPrivateSubnetIds"] = vpc.PrivateSubnetIds, + /// ["vpcPublicSubnetIds"] = vpc.PublicSubnetIds, + /// }; + /// }); + /// ``` + /// + /// ## Subnet Layout Strategies + /// + /// If no subnet arguments are passed, then a public and private subnet will be created in each AZ with default sizing. The layout of these subnets can be customised by specifying additional arguments. + /// + /// All strategies are designed to help build a uniform layout of subnets each each availability zone. + /// + /// If no strategy is specified, "Legacy" will be used for backward compatibility reasons. In the next major version this will change to defaulting to "Auto". + /// + /// ### Auto + /// + /// The "Auto" strategy divides the VPC space evenly between the availability zones. Within each availability zone it allocates each subnet in the order they were specified. If a CIDR mask or size was not specified it will default to an even division of the availability zone range. If subnets have different sizes, spaces will be automatically added to ensure subnets don't overlap (e.g. where a previous subnet is smaller than the next). + /// + /// ### Exact + /// + /// The "Exact" strategy is the same as "Auto" with the additional requirement to explicitly specify what the whole of each zone's range will be used for. Where you expect to have a gap between or after subnets, these must be passed using the subnet specification type "Unused" to show all space has been properly accounted for. + /// + /// ### Explicit CIDR Blocks + /// + /// If you prefer to do your CIDR block calculations yourself, you can specify a list of CIDR blocks for each subnet spec which it will be allocated for in each availability zone. If using explicit layouts, all subnet specs must be declared with explicit CIDR blocks. Each list of CIDR blocks must have the same length as the number of availability zones for the VPC. + /// + /// ### Legacy + /// + /// The "Legacy" works similarly to the "Auto" strategy except that within each availability zone it allocates the private subnet first, followed by the private subnets, and lastly the isolated subnets. The order of subnet specifications of the same type can be changed, but the ordering of private, public, isolated is not overridable. For more flexibility we recommend moving to the "Auto" strategy. The output property `subnetLayout` shows the configuration required if specifying the "Auto" strategy to maintain the current layout. + /// [AwsxResourceType("awsx:ec2:Vpc")] public partial class Vpc : global::Pulumi.ComponentResource { @@ -57,6 +107,12 @@ public partial class Vpc : global::Pulumi.ComponentResource [Output("routes")] public Output> Routes { get; private set; } = null!; + /// + /// The resolved subnet specs layout deployed to each availability zone. + /// + [Output("subnetLayout")] + public Output> SubnetLayout { get; private set; } = null!; + /// /// The VPC's subnets. /// @@ -112,6 +168,12 @@ public sealed class VpcArgs : global::Pulumi.ResourceArgs [Input("assignGeneratedIpv6CidrBlock")] public Input? AssignGeneratedIpv6CidrBlock { get; set; } + /// + /// The netmask for each available zone to be aligned to. This is optional, the default value is inferred based on an even distribution of available space from the VPC's CIDR block after being divided evenly by the number of availability zones. + /// + [Input("availabilityZoneCidrMask")] + public int? AvailabilityZoneCidrMask { get; set; } + [Input("availabilityZoneNames")] private List? _availabilityZoneNames; @@ -214,6 +276,12 @@ public List SubnetSpecs set => _subnetSpecs = value; } + /// + /// The strategy to use when allocating subnets for the VPC. Optional. Defaults to `Legacy`. + /// + [Input("subnetStrategy")] + public Pulumi.Awsx.Ec2.SubnetAllocationStrategy? SubnetStrategy { get; set; } + [Input("tags")] private InputMap? _tags; diff --git a/sdk/go/awsx/ec2/pulumiEnums.go b/sdk/go/awsx/ec2/pulumiEnums.go index 60b9642bf..db186dd86 100644 --- a/sdk/go/awsx/ec2/pulumiEnums.go +++ b/sdk/go/awsx/ec2/pulumiEnums.go @@ -198,6 +198,193 @@ func (in *natGatewayStrategyPtr) ToOutput(ctx context.Context) pulumix.Output[*N } } +// Strategy for calculating subnet ranges from the subnet specifications. +type SubnetAllocationStrategy string + +const ( + // Group private subnets first, followed by public subnets, followed by isolated subnets. + SubnetAllocationStrategyLegacy = SubnetAllocationStrategy("Legacy") + // Order remains as specified by specs, allowing gaps where required. + SubnetAllocationStrategyAuto = SubnetAllocationStrategy("Auto") + // Whole range of VPC must be accounted for, using "Unused" spec types for deliberate gaps. + SubnetAllocationStrategyExact = SubnetAllocationStrategy("Exact") +) + +func (SubnetAllocationStrategy) ElementType() reflect.Type { + return reflect.TypeOf((*SubnetAllocationStrategy)(nil)).Elem() +} + +func (e SubnetAllocationStrategy) ToSubnetAllocationStrategyOutput() SubnetAllocationStrategyOutput { + return pulumi.ToOutput(e).(SubnetAllocationStrategyOutput) +} + +func (e SubnetAllocationStrategy) ToSubnetAllocationStrategyOutputWithContext(ctx context.Context) SubnetAllocationStrategyOutput { + return pulumi.ToOutputWithContext(ctx, e).(SubnetAllocationStrategyOutput) +} + +func (e SubnetAllocationStrategy) ToSubnetAllocationStrategyPtrOutput() SubnetAllocationStrategyPtrOutput { + return e.ToSubnetAllocationStrategyPtrOutputWithContext(context.Background()) +} + +func (e SubnetAllocationStrategy) ToSubnetAllocationStrategyPtrOutputWithContext(ctx context.Context) SubnetAllocationStrategyPtrOutput { + return SubnetAllocationStrategy(e).ToSubnetAllocationStrategyOutputWithContext(ctx).ToSubnetAllocationStrategyPtrOutputWithContext(ctx) +} + +func (e SubnetAllocationStrategy) ToStringOutput() pulumi.StringOutput { + return pulumi.ToOutput(pulumi.String(e)).(pulumi.StringOutput) +} + +func (e SubnetAllocationStrategy) ToStringOutputWithContext(ctx context.Context) pulumi.StringOutput { + return pulumi.ToOutputWithContext(ctx, pulumi.String(e)).(pulumi.StringOutput) +} + +func (e SubnetAllocationStrategy) ToStringPtrOutput() pulumi.StringPtrOutput { + return pulumi.String(e).ToStringPtrOutputWithContext(context.Background()) +} + +func (e SubnetAllocationStrategy) ToStringPtrOutputWithContext(ctx context.Context) pulumi.StringPtrOutput { + return pulumi.String(e).ToStringOutputWithContext(ctx).ToStringPtrOutputWithContext(ctx) +} + +type SubnetAllocationStrategyOutput struct{ *pulumi.OutputState } + +func (SubnetAllocationStrategyOutput) ElementType() reflect.Type { + return reflect.TypeOf((*SubnetAllocationStrategy)(nil)).Elem() +} + +func (o SubnetAllocationStrategyOutput) ToSubnetAllocationStrategyOutput() SubnetAllocationStrategyOutput { + return o +} + +func (o SubnetAllocationStrategyOutput) ToSubnetAllocationStrategyOutputWithContext(ctx context.Context) SubnetAllocationStrategyOutput { + return o +} + +func (o SubnetAllocationStrategyOutput) ToSubnetAllocationStrategyPtrOutput() SubnetAllocationStrategyPtrOutput { + return o.ToSubnetAllocationStrategyPtrOutputWithContext(context.Background()) +} + +func (o SubnetAllocationStrategyOutput) ToSubnetAllocationStrategyPtrOutputWithContext(ctx context.Context) SubnetAllocationStrategyPtrOutput { + return o.ApplyTWithContext(ctx, func(_ context.Context, v SubnetAllocationStrategy) *SubnetAllocationStrategy { + return &v + }).(SubnetAllocationStrategyPtrOutput) +} + +func (o SubnetAllocationStrategyOutput) ToOutput(ctx context.Context) pulumix.Output[SubnetAllocationStrategy] { + return pulumix.Output[SubnetAllocationStrategy]{ + OutputState: o.OutputState, + } +} + +func (o SubnetAllocationStrategyOutput) ToStringOutput() pulumi.StringOutput { + return o.ToStringOutputWithContext(context.Background()) +} + +func (o SubnetAllocationStrategyOutput) ToStringOutputWithContext(ctx context.Context) pulumi.StringOutput { + return o.ApplyTWithContext(ctx, func(_ context.Context, e SubnetAllocationStrategy) string { + return string(e) + }).(pulumi.StringOutput) +} + +func (o SubnetAllocationStrategyOutput) ToStringPtrOutput() pulumi.StringPtrOutput { + return o.ToStringPtrOutputWithContext(context.Background()) +} + +func (o SubnetAllocationStrategyOutput) ToStringPtrOutputWithContext(ctx context.Context) pulumi.StringPtrOutput { + return o.ApplyTWithContext(ctx, func(_ context.Context, e SubnetAllocationStrategy) *string { + v := string(e) + return &v + }).(pulumi.StringPtrOutput) +} + +type SubnetAllocationStrategyPtrOutput struct{ *pulumi.OutputState } + +func (SubnetAllocationStrategyPtrOutput) ElementType() reflect.Type { + return reflect.TypeOf((**SubnetAllocationStrategy)(nil)).Elem() +} + +func (o SubnetAllocationStrategyPtrOutput) ToSubnetAllocationStrategyPtrOutput() SubnetAllocationStrategyPtrOutput { + return o +} + +func (o SubnetAllocationStrategyPtrOutput) ToSubnetAllocationStrategyPtrOutputWithContext(ctx context.Context) SubnetAllocationStrategyPtrOutput { + return o +} + +func (o SubnetAllocationStrategyPtrOutput) ToOutput(ctx context.Context) pulumix.Output[*SubnetAllocationStrategy] { + return pulumix.Output[*SubnetAllocationStrategy]{ + OutputState: o.OutputState, + } +} + +func (o SubnetAllocationStrategyPtrOutput) Elem() SubnetAllocationStrategyOutput { + return o.ApplyT(func(v *SubnetAllocationStrategy) SubnetAllocationStrategy { + if v != nil { + return *v + } + var ret SubnetAllocationStrategy + return ret + }).(SubnetAllocationStrategyOutput) +} + +func (o SubnetAllocationStrategyPtrOutput) ToStringPtrOutput() pulumi.StringPtrOutput { + return o.ToStringPtrOutputWithContext(context.Background()) +} + +func (o SubnetAllocationStrategyPtrOutput) ToStringPtrOutputWithContext(ctx context.Context) pulumi.StringPtrOutput { + return o.ApplyTWithContext(ctx, func(_ context.Context, e *SubnetAllocationStrategy) *string { + if e == nil { + return nil + } + v := string(*e) + return &v + }).(pulumi.StringPtrOutput) +} + +// SubnetAllocationStrategyInput is an input type that accepts SubnetAllocationStrategyArgs and SubnetAllocationStrategyOutput values. +// You can construct a concrete instance of `SubnetAllocationStrategyInput` via: +// +// SubnetAllocationStrategyArgs{...} +type SubnetAllocationStrategyInput interface { + pulumi.Input + + ToSubnetAllocationStrategyOutput() SubnetAllocationStrategyOutput + ToSubnetAllocationStrategyOutputWithContext(context.Context) SubnetAllocationStrategyOutput +} + +var subnetAllocationStrategyPtrType = reflect.TypeOf((**SubnetAllocationStrategy)(nil)).Elem() + +type SubnetAllocationStrategyPtrInput interface { + pulumi.Input + + ToSubnetAllocationStrategyPtrOutput() SubnetAllocationStrategyPtrOutput + ToSubnetAllocationStrategyPtrOutputWithContext(context.Context) SubnetAllocationStrategyPtrOutput +} + +type subnetAllocationStrategyPtr string + +func SubnetAllocationStrategyPtr(v string) SubnetAllocationStrategyPtrInput { + return (*subnetAllocationStrategyPtr)(&v) +} + +func (*subnetAllocationStrategyPtr) ElementType() reflect.Type { + return subnetAllocationStrategyPtrType +} + +func (in *subnetAllocationStrategyPtr) ToSubnetAllocationStrategyPtrOutput() SubnetAllocationStrategyPtrOutput { + return pulumi.ToOutput(in).(SubnetAllocationStrategyPtrOutput) +} + +func (in *subnetAllocationStrategyPtr) ToSubnetAllocationStrategyPtrOutputWithContext(ctx context.Context) SubnetAllocationStrategyPtrOutput { + return pulumi.ToOutputWithContext(ctx, in).(SubnetAllocationStrategyPtrOutput) +} + +func (in *subnetAllocationStrategyPtr) ToOutput(ctx context.Context) pulumix.Output[*SubnetAllocationStrategy] { + return pulumix.Output[*SubnetAllocationStrategy]{ + OutputState: in.ToSubnetAllocationStrategyPtrOutputWithContext(ctx).OutputState, + } +} + // A type of subnet within a VPC. type SubnetType string @@ -208,6 +395,8 @@ const ( SubnetTypePrivate = SubnetType("Private") // A subnet whose hosts have no connectivity with the internet. SubnetTypeIsolated = SubnetType("Isolated") + // A subnet range which is reserved, but no subnet will be created. + SubnetTypeUnused = SubnetType("Unused") ) func (SubnetType) ElementType() reflect.Type { @@ -388,10 +577,14 @@ func (in *subnetTypePtr) ToOutput(ctx context.Context) pulumix.Output[*SubnetTyp func init() { pulumi.RegisterInputType(reflect.TypeOf((*NatGatewayStrategyInput)(nil)).Elem(), NatGatewayStrategy("None")) pulumi.RegisterInputType(reflect.TypeOf((*NatGatewayStrategyPtrInput)(nil)).Elem(), NatGatewayStrategy("None")) + pulumi.RegisterInputType(reflect.TypeOf((*SubnetAllocationStrategyInput)(nil)).Elem(), SubnetAllocationStrategy("Legacy")) + pulumi.RegisterInputType(reflect.TypeOf((*SubnetAllocationStrategyPtrInput)(nil)).Elem(), SubnetAllocationStrategy("Legacy")) pulumi.RegisterInputType(reflect.TypeOf((*SubnetTypeInput)(nil)).Elem(), SubnetType("Public")) pulumi.RegisterInputType(reflect.TypeOf((*SubnetTypePtrInput)(nil)).Elem(), SubnetType("Public")) pulumi.RegisterOutputType(NatGatewayStrategyOutput{}) pulumi.RegisterOutputType(NatGatewayStrategyPtrOutput{}) + pulumi.RegisterOutputType(SubnetAllocationStrategyOutput{}) + pulumi.RegisterOutputType(SubnetAllocationStrategyPtrOutput{}) pulumi.RegisterOutputType(SubnetTypeOutput{}) pulumi.RegisterOutputType(SubnetTypePtrOutput{}) } diff --git a/sdk/go/awsx/ec2/pulumiTypes.go b/sdk/go/awsx/ec2/pulumiTypes.go index b0aa8777f..671288467 100644 --- a/sdk/go/awsx/ec2/pulumiTypes.go +++ b/sdk/go/awsx/ec2/pulumiTypes.go @@ -198,12 +198,102 @@ func (o NatGatewayConfigurationPtrOutput) Strategy() NatGatewayStrategyPtrOutput }).(NatGatewayStrategyPtrOutput) } +// Configuration for a VPC subnet spec. +type ResolvedSubnetSpec struct { + // An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. + CidrBlocks []string `pulumi:"cidrBlocks"` + // The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + CidrMask *int `pulumi:"cidrMask"` + // The subnet's name. Will be templated upon creation. + Name *string `pulumi:"name"` + // Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + Size *int `pulumi:"size"` + // The type of subnet. + Type SubnetType `pulumi:"type"` +} + +// Configuration for a VPC subnet spec. +type ResolvedSubnetSpecOutput struct{ *pulumi.OutputState } + +func (ResolvedSubnetSpecOutput) ElementType() reflect.Type { + return reflect.TypeOf((*ResolvedSubnetSpec)(nil)).Elem() +} + +func (o ResolvedSubnetSpecOutput) ToResolvedSubnetSpecOutput() ResolvedSubnetSpecOutput { + return o +} + +func (o ResolvedSubnetSpecOutput) ToResolvedSubnetSpecOutputWithContext(ctx context.Context) ResolvedSubnetSpecOutput { + return o +} + +func (o ResolvedSubnetSpecOutput) ToOutput(ctx context.Context) pulumix.Output[ResolvedSubnetSpec] { + return pulumix.Output[ResolvedSubnetSpec]{ + OutputState: o.OutputState, + } +} + +// An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. +func (o ResolvedSubnetSpecOutput) CidrBlocks() pulumi.StringArrayOutput { + return o.ApplyT(func(v ResolvedSubnetSpec) []string { return v.CidrBlocks }).(pulumi.StringArrayOutput) +} + +// The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. +func (o ResolvedSubnetSpecOutput) CidrMask() pulumi.IntPtrOutput { + return o.ApplyT(func(v ResolvedSubnetSpec) *int { return v.CidrMask }).(pulumi.IntPtrOutput) +} + +// The subnet's name. Will be templated upon creation. +func (o ResolvedSubnetSpecOutput) Name() pulumi.StringPtrOutput { + return o.ApplyT(func(v ResolvedSubnetSpec) *string { return v.Name }).(pulumi.StringPtrOutput) +} + +// Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. +func (o ResolvedSubnetSpecOutput) Size() pulumi.IntPtrOutput { + return o.ApplyT(func(v ResolvedSubnetSpec) *int { return v.Size }).(pulumi.IntPtrOutput) +} + +// The type of subnet. +func (o ResolvedSubnetSpecOutput) Type() SubnetTypeOutput { + return o.ApplyT(func(v ResolvedSubnetSpec) SubnetType { return v.Type }).(SubnetTypeOutput) +} + +type ResolvedSubnetSpecArrayOutput struct{ *pulumi.OutputState } + +func (ResolvedSubnetSpecArrayOutput) ElementType() reflect.Type { + return reflect.TypeOf((*[]ResolvedSubnetSpec)(nil)).Elem() +} + +func (o ResolvedSubnetSpecArrayOutput) ToResolvedSubnetSpecArrayOutput() ResolvedSubnetSpecArrayOutput { + return o +} + +func (o ResolvedSubnetSpecArrayOutput) ToResolvedSubnetSpecArrayOutputWithContext(ctx context.Context) ResolvedSubnetSpecArrayOutput { + return o +} + +func (o ResolvedSubnetSpecArrayOutput) ToOutput(ctx context.Context) pulumix.Output[[]ResolvedSubnetSpec] { + return pulumix.Output[[]ResolvedSubnetSpec]{ + OutputState: o.OutputState, + } +} + +func (o ResolvedSubnetSpecArrayOutput) Index(i pulumi.IntInput) ResolvedSubnetSpecOutput { + return pulumi.All(o, i).ApplyT(func(vs []interface{}) ResolvedSubnetSpec { + return vs[0].([]ResolvedSubnetSpec)[vs[1].(int)] + }).(ResolvedSubnetSpecOutput) +} + // Configuration for a VPC subnet. type SubnetSpec struct { - // The bitmask for the subnet's CIDR block. + // An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. + CidrBlocks []string `pulumi:"cidrBlocks"` + // The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. CidrMask *int `pulumi:"cidrMask"` // The subnet's name. Will be templated upon creation. Name *string `pulumi:"name"` + // Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + Size *int `pulumi:"size"` // A map of tags to assign to the resource. Tags map[string]string `pulumi:"tags"` // The type of subnet. @@ -223,10 +313,14 @@ type SubnetSpecInput interface { // Configuration for a VPC subnet. type SubnetSpecArgs struct { - // The bitmask for the subnet's CIDR block. + // An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. + CidrBlocks []string `pulumi:"cidrBlocks"` + // The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. CidrMask *int `pulumi:"cidrMask"` // The subnet's name. Will be templated upon creation. Name *string `pulumi:"name"` + // Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + Size *int `pulumi:"size"` // A map of tags to assign to the resource. Tags pulumi.StringMapInput `pulumi:"tags"` // The type of subnet. @@ -303,7 +397,12 @@ func (o SubnetSpecOutput) ToOutput(ctx context.Context) pulumix.Output[SubnetSpe } } -// The bitmask for the subnet's CIDR block. +// An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. +func (o SubnetSpecOutput) CidrBlocks() pulumi.StringArrayOutput { + return o.ApplyT(func(v SubnetSpec) []string { return v.CidrBlocks }).(pulumi.StringArrayOutput) +} + +// The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. func (o SubnetSpecOutput) CidrMask() pulumi.IntPtrOutput { return o.ApplyT(func(v SubnetSpec) *int { return v.CidrMask }).(pulumi.IntPtrOutput) } @@ -313,6 +412,11 @@ func (o SubnetSpecOutput) Name() pulumi.StringPtrOutput { return o.ApplyT(func(v SubnetSpec) *string { return v.Name }).(pulumi.StringPtrOutput) } +// Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. +func (o SubnetSpecOutput) Size() pulumi.IntPtrOutput { + return o.ApplyT(func(v SubnetSpec) *int { return v.Size }).(pulumi.IntPtrOutput) +} + // A map of tags to assign to the resource. func (o SubnetSpecOutput) Tags() pulumi.StringMapOutput { return o.ApplyT(func(v SubnetSpec) map[string]string { return v.Tags }).(pulumi.StringMapOutput) @@ -1022,6 +1126,8 @@ func init() { pulumi.RegisterInputType(reflect.TypeOf((*VpcEndpointSpecArrayInput)(nil)).Elem(), VpcEndpointSpecArray{}) pulumi.RegisterOutputType(NatGatewayConfigurationOutput{}) pulumi.RegisterOutputType(NatGatewayConfigurationPtrOutput{}) + pulumi.RegisterOutputType(ResolvedSubnetSpecOutput{}) + pulumi.RegisterOutputType(ResolvedSubnetSpecArrayOutput{}) pulumi.RegisterOutputType(SubnetSpecOutput{}) pulumi.RegisterOutputType(SubnetSpecArrayOutput{}) pulumi.RegisterOutputType(VpcEndpointSpecOutput{}) diff --git a/sdk/go/awsx/ec2/vpc.go b/sdk/go/awsx/ec2/vpc.go index 6b2bf5960..084e1cb59 100644 --- a/sdk/go/awsx/ec2/vpc.go +++ b/sdk/go/awsx/ec2/vpc.go @@ -13,6 +13,60 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/pulumix" ) +// The VPC component provides a VPC with configured subnets and NAT gateways. +// +// ## Example Usage +// +// Basic usage: +// +// ```go +// package main +// +// import ( +// +// "github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/ec2" +// "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +// +// ) +// +// func main() { +// pulumi.Run(func(ctx *pulumi.Context) error { +// vpc, err := ec2.NewVpc(ctx, "vpc", nil) +// if err != nil { +// return err +// } +// ctx.Export("vpcId", vpc.VpcId) +// ctx.Export("vpcPrivateSubnetIds", vpc.PrivateSubnetIds) +// ctx.Export("vpcPublicSubnetIds", vpc.PublicSubnetIds) +// return nil +// }) +// } +// +// ``` +// +// ## Subnet Layout Strategies +// +// If no subnet arguments are passed, then a public and private subnet will be created in each AZ with default sizing. The layout of these subnets can be customised by specifying additional arguments. +// +// All strategies are designed to help build a uniform layout of subnets each each availability zone. +// +// If no strategy is specified, "Legacy" will be used for backward compatibility reasons. In the next major version this will change to defaulting to "Auto". +// +// ### Auto +// +// The "Auto" strategy divides the VPC space evenly between the availability zones. Within each availability zone it allocates each subnet in the order they were specified. If a CIDR mask or size was not specified it will default to an even division of the availability zone range. If subnets have different sizes, spaces will be automatically added to ensure subnets don't overlap (e.g. where a previous subnet is smaller than the next). +// +// ### Exact +// +// The "Exact" strategy is the same as "Auto" with the additional requirement to explicitly specify what the whole of each zone's range will be used for. Where you expect to have a gap between or after subnets, these must be passed using the subnet specification type "Unused" to show all space has been properly accounted for. +// +// ### Explicit CIDR Blocks +// +// If you prefer to do your CIDR block calculations yourself, you can specify a list of CIDR blocks for each subnet spec which it will be allocated for in each availability zone. If using explicit layouts, all subnet specs must be declared with explicit CIDR blocks. Each list of CIDR blocks must have the same length as the number of availability zones for the VPC. +// +// ### Legacy +// +// The "Legacy" works similarly to the "Auto" strategy except that within each availability zone it allocates the private subnet first, followed by the private subnets, and lastly the isolated subnets. The order of subnet specifications of the same type can be changed, but the ordering of private, public, isolated is not overridable. For more flexibility we recommend moving to the "Auto" strategy. The output property `subnetLayout` shows the configuration required if specifying the "Auto" strategy to maintain the current layout. type Vpc struct { pulumi.ResourceState @@ -31,6 +85,8 @@ type Vpc struct { RouteTables ec2.RouteTableArrayOutput `pulumi:"routeTables"` // The Routes for the VPC. Routes ec2.RouteArrayOutput `pulumi:"routes"` + // The resolved subnet specs layout deployed to each availability zone. + SubnetLayout ResolvedSubnetSpecArrayOutput `pulumi:"subnetLayout"` // The VPC's subnets. Subnets ec2.SubnetArrayOutput `pulumi:"subnets"` // The VPC. @@ -59,6 +115,8 @@ func NewVpc(ctx *pulumi.Context, type vpcArgs struct { // Requests an Amazon-provided IPv6 CIDR block with a /56 prefix length for the VPC. You cannot specify the range of IP addresses, or the size of the CIDR block. Default is `false`. Conflicts with `ipv6_ipam_pool_id` AssignGeneratedIpv6CidrBlock *bool `pulumi:"assignGeneratedIpv6CidrBlock"` + // The netmask for each available zone to be aligned to. This is optional, the default value is inferred based on an even distribution of available space from the VPC's CIDR block after being divided evenly by the number of availability zones. + AvailabilityZoneCidrMask *int `pulumi:"availabilityZoneCidrMask"` // A list of availability zone names to which the subnets defined in subnetSpecs will be deployed. Optional, defaults to the first 3 AZs in the current region. AvailabilityZoneNames []string `pulumi:"availabilityZoneNames"` // The CIDR block for the VPC. Optional. Defaults to 10.0.0.0/16. @@ -89,6 +147,8 @@ type vpcArgs struct { NumberOfAvailabilityZones *int `pulumi:"numberOfAvailabilityZones"` // A list of subnet specs that should be deployed to each AZ specified in availabilityZoneNames. Optional. Defaults to a (smaller) public subnet and a (larger) private subnet based on the size of the CIDR block for the VPC. Private subnets are allocated CIDR block ranges first, followed by Private subnets, and Isolated subnets are allocated last. SubnetSpecs []SubnetSpec `pulumi:"subnetSpecs"` + // The strategy to use when allocating subnets for the VPC. Optional. Defaults to `Legacy`. + SubnetStrategy *SubnetAllocationStrategy `pulumi:"subnetStrategy"` // A map of tags to assign to the resource. If configured with a provider `default_tags` configuration block present, tags with matching keys will overwrite those defined at the provider-level. Tags map[string]string `pulumi:"tags"` // A list of VPC Endpoints specs to be deployed as part of the VPC @@ -99,6 +159,8 @@ type vpcArgs struct { type VpcArgs struct { // Requests an Amazon-provided IPv6 CIDR block with a /56 prefix length for the VPC. You cannot specify the range of IP addresses, or the size of the CIDR block. Default is `false`. Conflicts with `ipv6_ipam_pool_id` AssignGeneratedIpv6CidrBlock pulumi.BoolPtrInput + // The netmask for each available zone to be aligned to. This is optional, the default value is inferred based on an even distribution of available space from the VPC's CIDR block after being divided evenly by the number of availability zones. + AvailabilityZoneCidrMask *int // A list of availability zone names to which the subnets defined in subnetSpecs will be deployed. Optional, defaults to the first 3 AZs in the current region. AvailabilityZoneNames []string // The CIDR block for the VPC. Optional. Defaults to 10.0.0.0/16. @@ -129,6 +191,8 @@ type VpcArgs struct { NumberOfAvailabilityZones *int // A list of subnet specs that should be deployed to each AZ specified in availabilityZoneNames. Optional. Defaults to a (smaller) public subnet and a (larger) private subnet based on the size of the CIDR block for the VPC. Private subnets are allocated CIDR block ranges first, followed by Private subnets, and Isolated subnets are allocated last. SubnetSpecs []SubnetSpecArgs + // The strategy to use when allocating subnets for the VPC. Optional. Defaults to `Legacy`. + SubnetStrategy *SubnetAllocationStrategy // A map of tags to assign to the resource. If configured with a provider `default_tags` configuration block present, tags with matching keys will overwrite those defined at the provider-level. Tags pulumi.StringMapInput // A list of VPC Endpoints specs to be deployed as part of the VPC @@ -288,6 +352,11 @@ func (o VpcOutput) Routes() ec2.RouteArrayOutput { return o.ApplyT(func(v *Vpc) ec2.RouteArrayOutput { return v.Routes }).(ec2.RouteArrayOutput) } +// The resolved subnet specs layout deployed to each availability zone. +func (o VpcOutput) SubnetLayout() ResolvedSubnetSpecArrayOutput { + return o.ApplyT(func(v *Vpc) ResolvedSubnetSpecArrayOutput { return v.SubnetLayout }).(ResolvedSubnetSpecArrayOutput) +} + // The VPC's subnets. func (o VpcOutput) Subnets() ec2.SubnetArrayOutput { return o.ApplyT(func(v *Vpc) ec2.SubnetArrayOutput { return v.Subnets }).(ec2.SubnetArrayOutput) diff --git a/sdk/java/src/main/java/com/pulumi/awsx/ec2/Vpc.java b/sdk/java/src/main/java/com/pulumi/awsx/ec2/Vpc.java index 8fafbd4f6..1c3961a71 100644 --- a/sdk/java/src/main/java/com/pulumi/awsx/ec2/Vpc.java +++ b/sdk/java/src/main/java/com/pulumi/awsx/ec2/Vpc.java @@ -13,6 +13,7 @@ import com.pulumi.aws.ec2.VpcEndpoint; import com.pulumi.awsx.Utilities; import com.pulumi.awsx.ec2.VpcArgs; +import com.pulumi.awsx.ec2.outputs.ResolvedSubnetSpec; import com.pulumi.core.Output; import com.pulumi.core.annotations.Export; import com.pulumi.core.annotations.ResourceType; @@ -21,6 +22,67 @@ import java.util.List; import javax.annotation.Nullable; +/** + * The VPC component provides a VPC with configured subnets and NAT gateways. + * + * ## Example Usage + * + * Basic usage: + * + * ```java + * package generated_program; + * + * import com.pulumi.Context; + * import com.pulumi.Pulumi; + * import com.pulumi.core.Output; + * import com.pulumi.awsx.ec2.Vpc; + * import java.util.List; + * import java.util.ArrayList; + * import java.util.Map; + * import java.io.File; + * import java.nio.file.Files; + * import java.nio.file.Paths; + * + * public class App { + * public static void main(String[] args) { + * Pulumi.run(App::stack); + * } + * + * public static void stack(Context ctx) { + * var vpc = new Vpc("vpc"); + * + * ctx.export("vpcId", vpc.vpcId()); + * ctx.export("vpcPrivateSubnetIds", vpc.privateSubnetIds()); + * ctx.export("vpcPublicSubnetIds", vpc.publicSubnetIds()); + * } + * } + * ``` + * + * ## Subnet Layout Strategies + * + * If no subnet arguments are passed, then a public and private subnet will be created in each AZ with default sizing. The layout of these subnets can be customised by specifying additional arguments. + * + * All strategies are designed to help build a uniform layout of subnets each each availability zone. + * + * If no strategy is specified, "Legacy" will be used for backward compatibility reasons. In the next major version this will change to defaulting to "Auto". + * + * ### Auto + * + * The "Auto" strategy divides the VPC space evenly between the availability zones. Within each availability zone it allocates each subnet in the order they were specified. If a CIDR mask or size was not specified it will default to an even division of the availability zone range. If subnets have different sizes, spaces will be automatically added to ensure subnets don't overlap (e.g. where a previous subnet is smaller than the next). + * + * ### Exact + * + * The "Exact" strategy is the same as "Auto" with the additional requirement to explicitly specify what the whole of each zone's range will be used for. Where you expect to have a gap between or after subnets, these must be passed using the subnet specification type "Unused" to show all space has been properly accounted for. + * + * ### Explicit CIDR Blocks + * + * If you prefer to do your CIDR block calculations yourself, you can specify a list of CIDR blocks for each subnet spec which it will be allocated for in each availability zone. If using explicit layouts, all subnet specs must be declared with explicit CIDR blocks. Each list of CIDR blocks must have the same length as the number of availability zones for the VPC. + * + * ### Legacy + * + * The "Legacy" works similarly to the "Auto" strategy except that within each availability zone it allocates the private subnet first, followed by the private subnets, and lastly the isolated subnets. The order of subnet specifications of the same type can be changed, but the ordering of private, public, isolated is not overridable. For more flexibility we recommend moving to the "Auto" strategy. The output property `subnetLayout` shows the configuration required if specifying the "Auto" strategy to maintain the current layout. + * + */ @ResourceType(type="awsx:ec2:Vpc") public class Vpc extends com.pulumi.resources.ComponentResource { /** @@ -125,6 +187,20 @@ public Output> routeTables() { public Output> routes() { return this.routes; } + /** + * The resolved subnet specs layout deployed to each availability zone. + * + */ + @Export(name="subnetLayout", refs={List.class,ResolvedSubnetSpec.class}, tree="[0,1]") + private Output> subnetLayout; + + /** + * @return The resolved subnet specs layout deployed to each availability zone. + * + */ + public Output> subnetLayout() { + return this.subnetLayout; + } /** * The VPC's subnets. * diff --git a/sdk/java/src/main/java/com/pulumi/awsx/ec2/VpcArgs.java b/sdk/java/src/main/java/com/pulumi/awsx/ec2/VpcArgs.java index f0a94f2da..3a2021d0c 100644 --- a/sdk/java/src/main/java/com/pulumi/awsx/ec2/VpcArgs.java +++ b/sdk/java/src/main/java/com/pulumi/awsx/ec2/VpcArgs.java @@ -3,6 +3,7 @@ package com.pulumi.awsx.ec2; +import com.pulumi.awsx.ec2.enums.SubnetAllocationStrategy; import com.pulumi.awsx.ec2.inputs.NatGatewayConfigurationArgs; import com.pulumi.awsx.ec2.inputs.SubnetSpecArgs; import com.pulumi.awsx.ec2.inputs.VpcEndpointSpecArgs; @@ -37,6 +38,21 @@ public Optional> assignGeneratedIpv6CidrBlock() { return Optional.ofNullable(this.assignGeneratedIpv6CidrBlock); } + /** + * The netmask for each available zone to be aligned to. This is optional, the default value is inferred based on an even distribution of available space from the VPC's CIDR block after being divided evenly by the number of availability zones. + * + */ + @Import(name="availabilityZoneCidrMask") + private @Nullable Integer availabilityZoneCidrMask; + + /** + * @return The netmask for each available zone to be aligned to. This is optional, the default value is inferred based on an even distribution of available space from the VPC's CIDR block after being divided evenly by the number of availability zones. + * + */ + public Optional availabilityZoneCidrMask() { + return Optional.ofNullable(this.availabilityZoneCidrMask); + } + /** * A list of availability zone names to which the subnets defined in subnetSpecs will be deployed. Optional, defaults to the first 3 AZs in the current region. * @@ -262,6 +278,21 @@ public Optional> subnetSpecs() { return Optional.ofNullable(this.subnetSpecs); } + /** + * The strategy to use when allocating subnets for the VPC. Optional. Defaults to `Legacy`. + * + */ + @Import(name="subnetStrategy") + private @Nullable SubnetAllocationStrategy subnetStrategy; + + /** + * @return The strategy to use when allocating subnets for the VPC. Optional. Defaults to `Legacy`. + * + */ + public Optional subnetStrategy() { + return Optional.ofNullable(this.subnetStrategy); + } + /** * A map of tags to assign to the resource. If configured with a provider `default_tags` configuration block present, tags with matching keys will overwrite those defined at the provider-level. * @@ -296,6 +327,7 @@ private VpcArgs() {} private VpcArgs(VpcArgs $) { this.assignGeneratedIpv6CidrBlock = $.assignGeneratedIpv6CidrBlock; + this.availabilityZoneCidrMask = $.availabilityZoneCidrMask; this.availabilityZoneNames = $.availabilityZoneNames; this.cidrBlock = $.cidrBlock; this.enableDnsHostnames = $.enableDnsHostnames; @@ -311,6 +343,7 @@ private VpcArgs(VpcArgs $) { this.natGateways = $.natGateways; this.numberOfAvailabilityZones = $.numberOfAvailabilityZones; this.subnetSpecs = $.subnetSpecs; + this.subnetStrategy = $.subnetStrategy; this.tags = $.tags; this.vpcEndpointSpecs = $.vpcEndpointSpecs; } @@ -354,6 +387,17 @@ public Builder assignGeneratedIpv6CidrBlock(Boolean assignGeneratedIpv6CidrBlock return assignGeneratedIpv6CidrBlock(Output.of(assignGeneratedIpv6CidrBlock)); } + /** + * @param availabilityZoneCidrMask The netmask for each available zone to be aligned to. This is optional, the default value is inferred based on an even distribution of available space from the VPC's CIDR block after being divided evenly by the number of availability zones. + * + * @return builder + * + */ + public Builder availabilityZoneCidrMask(@Nullable Integer availabilityZoneCidrMask) { + $.availabilityZoneCidrMask = availabilityZoneCidrMask; + return this; + } + /** * @param availabilityZoneNames A list of availability zone names to which the subnets defined in subnetSpecs will be deployed. Optional, defaults to the first 3 AZs in the current region. * @@ -639,6 +683,17 @@ public Builder subnetSpecs(SubnetSpecArgs... subnetSpecs) { return subnetSpecs(List.of(subnetSpecs)); } + /** + * @param subnetStrategy The strategy to use when allocating subnets for the VPC. Optional. Defaults to `Legacy`. + * + * @return builder + * + */ + public Builder subnetStrategy(@Nullable SubnetAllocationStrategy subnetStrategy) { + $.subnetStrategy = subnetStrategy; + return this; + } + /** * @param tags A map of tags to assign to the resource. If configured with a provider `default_tags` configuration block present, tags with matching keys will overwrite those defined at the provider-level. * diff --git a/sdk/java/src/main/java/com/pulumi/awsx/ec2/enums/SubnetAllocationStrategy.java b/sdk/java/src/main/java/com/pulumi/awsx/ec2/enums/SubnetAllocationStrategy.java new file mode 100644 index 000000000..fba113541 --- /dev/null +++ b/sdk/java/src/main/java/com/pulumi/awsx/ec2/enums/SubnetAllocationStrategy.java @@ -0,0 +1,50 @@ +// *** WARNING: this file was generated by pulumi-java-gen. *** +// *** Do not edit by hand unless you're certain you know what you are doing! *** + +package com.pulumi.awsx.ec2.enums; + +import com.pulumi.core.annotations.EnumType; +import java.lang.String; +import java.util.Objects; +import java.util.StringJoiner; + + /** + * Strategy for calculating subnet ranges from the subnet specifications. + * + */ + @EnumType + public enum SubnetAllocationStrategy { + /** + * Group private subnets first, followed by public subnets, followed by isolated subnets. + * + */ + Legacy("Legacy"), + /** + * Order remains as specified by specs, allowing gaps where required. + * + */ + Auto("Auto"), + /** + * Whole range of VPC must be accounted for, using "Unused" spec types for deliberate gaps. + * + */ + Exact("Exact"); + + private final String value; + + SubnetAllocationStrategy(String value) { + this.value = Objects.requireNonNull(value); + } + + @EnumType.Converter + public String getValue() { + return this.value; + } + + @Override + public String toString() { + return new StringJoiner(", ", "SubnetAllocationStrategy[", "]") + .add("value='" + this.value + "'") + .toString(); + } + } diff --git a/sdk/java/src/main/java/com/pulumi/awsx/ec2/enums/SubnetType.java b/sdk/java/src/main/java/com/pulumi/awsx/ec2/enums/SubnetType.java index f9a8a506b..355d3d944 100644 --- a/sdk/java/src/main/java/com/pulumi/awsx/ec2/enums/SubnetType.java +++ b/sdk/java/src/main/java/com/pulumi/awsx/ec2/enums/SubnetType.java @@ -28,7 +28,12 @@ public enum SubnetType { * A subnet whose hosts have no connectivity with the internet. * */ - Isolated("Isolated"); + Isolated("Isolated"), + /** + * A subnet range which is reserved, but no subnet will be created. + * + */ + Unused("Unused"); private final String value; diff --git a/sdk/java/src/main/java/com/pulumi/awsx/ec2/inputs/SubnetSpecArgs.java b/sdk/java/src/main/java/com/pulumi/awsx/ec2/inputs/SubnetSpecArgs.java index a81084f74..c9dc08a13 100644 --- a/sdk/java/src/main/java/com/pulumi/awsx/ec2/inputs/SubnetSpecArgs.java +++ b/sdk/java/src/main/java/com/pulumi/awsx/ec2/inputs/SubnetSpecArgs.java @@ -8,6 +8,7 @@ import com.pulumi.core.annotations.Import; import java.lang.Integer; import java.lang.String; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -23,14 +24,29 @@ public final class SubnetSpecArgs extends com.pulumi.resources.ResourceArgs { public static final SubnetSpecArgs Empty = new SubnetSpecArgs(); /** - * The bitmask for the subnet's CIDR block. + * An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. + * + */ + @Import(name="cidrBlocks") + private @Nullable List cidrBlocks; + + /** + * @return An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. + * + */ + public Optional> cidrBlocks() { + return Optional.ofNullable(this.cidrBlocks); + } + + /** + * The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. * */ @Import(name="cidrMask") private @Nullable Integer cidrMask; /** - * @return The bitmask for the subnet's CIDR block. + * @return The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. * */ public Optional cidrMask() { @@ -52,6 +68,21 @@ public Optional name() { return Optional.ofNullable(this.name); } + /** + * Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + * + */ + @Import(name="size") + private @Nullable Integer size; + + /** + * @return Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + * + */ + public Optional size() { + return Optional.ofNullable(this.size); + } + /** * A map of tags to assign to the resource. * @@ -85,8 +116,10 @@ public SubnetType type() { private SubnetSpecArgs() {} private SubnetSpecArgs(SubnetSpecArgs $) { + this.cidrBlocks = $.cidrBlocks; this.cidrMask = $.cidrMask; this.name = $.name; + this.size = $.size; this.tags = $.tags; this.type = $.type; } @@ -110,7 +143,28 @@ public Builder(SubnetSpecArgs defaults) { } /** - * @param cidrMask The bitmask for the subnet's CIDR block. + * @param cidrBlocks An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. + * + * @return builder + * + */ + public Builder cidrBlocks(@Nullable List cidrBlocks) { + $.cidrBlocks = cidrBlocks; + return this; + } + + /** + * @param cidrBlocks An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. + * + * @return builder + * + */ + public Builder cidrBlocks(String... cidrBlocks) { + return cidrBlocks(List.of(cidrBlocks)); + } + + /** + * @param cidrMask The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. * * @return builder * @@ -131,6 +185,17 @@ public Builder name(@Nullable String name) { return this; } + /** + * @param size Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + * + * @return builder + * + */ + public Builder size(@Nullable Integer size) { + $.size = size; + return this; + } + /** * @param tags A map of tags to assign to the resource. * diff --git a/sdk/java/src/main/java/com/pulumi/awsx/ec2/outputs/ResolvedSubnetSpec.java b/sdk/java/src/main/java/com/pulumi/awsx/ec2/outputs/ResolvedSubnetSpec.java new file mode 100644 index 000000000..fc10334ef --- /dev/null +++ b/sdk/java/src/main/java/com/pulumi/awsx/ec2/outputs/ResolvedSubnetSpec.java @@ -0,0 +1,142 @@ +// *** WARNING: this file was generated by pulumi-java-gen. *** +// *** Do not edit by hand unless you're certain you know what you are doing! *** + +package com.pulumi.awsx.ec2.outputs; + +import com.pulumi.awsx.ec2.enums.SubnetType; +import com.pulumi.core.annotations.CustomType; +import java.lang.Integer; +import java.lang.String; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import javax.annotation.Nullable; + +@CustomType +public final class ResolvedSubnetSpec { + /** + * @return An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. + * + */ + private @Nullable List cidrBlocks; + /** + * @return The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + * + */ + private @Nullable Integer cidrMask; + /** + * @return The subnet's name. Will be templated upon creation. + * + */ + private @Nullable String name; + /** + * @return Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + * + */ + private @Nullable Integer size; + /** + * @return The type of subnet. + * + */ + private SubnetType type; + + private ResolvedSubnetSpec() {} + /** + * @return An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. + * + */ + public List cidrBlocks() { + return this.cidrBlocks == null ? List.of() : this.cidrBlocks; + } + /** + * @return The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + * + */ + public Optional cidrMask() { + return Optional.ofNullable(this.cidrMask); + } + /** + * @return The subnet's name. Will be templated upon creation. + * + */ + public Optional name() { + return Optional.ofNullable(this.name); + } + /** + * @return Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + * + */ + public Optional size() { + return Optional.ofNullable(this.size); + } + /** + * @return The type of subnet. + * + */ + public SubnetType type() { + return this.type; + } + + public static Builder builder() { + return new Builder(); + } + + public static Builder builder(ResolvedSubnetSpec defaults) { + return new Builder(defaults); + } + @CustomType.Builder + public static final class Builder { + private @Nullable List cidrBlocks; + private @Nullable Integer cidrMask; + private @Nullable String name; + private @Nullable Integer size; + private SubnetType type; + public Builder() {} + public Builder(ResolvedSubnetSpec defaults) { + Objects.requireNonNull(defaults); + this.cidrBlocks = defaults.cidrBlocks; + this.cidrMask = defaults.cidrMask; + this.name = defaults.name; + this.size = defaults.size; + this.type = defaults.type; + } + + @CustomType.Setter + public Builder cidrBlocks(@Nullable List cidrBlocks) { + this.cidrBlocks = cidrBlocks; + return this; + } + public Builder cidrBlocks(String... cidrBlocks) { + return cidrBlocks(List.of(cidrBlocks)); + } + @CustomType.Setter + public Builder cidrMask(@Nullable Integer cidrMask) { + this.cidrMask = cidrMask; + return this; + } + @CustomType.Setter + public Builder name(@Nullable String name) { + this.name = name; + return this; + } + @CustomType.Setter + public Builder size(@Nullable Integer size) { + this.size = size; + return this; + } + @CustomType.Setter + public Builder type(SubnetType type) { + this.type = Objects.requireNonNull(type); + return this; + } + public ResolvedSubnetSpec build() { + final var o = new ResolvedSubnetSpec(); + o.cidrBlocks = cidrBlocks; + o.cidrMask = cidrMask; + o.name = name; + o.size = size; + o.type = type; + return o; + } + } +} diff --git a/sdk/nodejs/ec2/vpc.ts b/sdk/nodejs/ec2/vpc.ts index da549865b..b2aef965a 100644 --- a/sdk/nodejs/ec2/vpc.ts +++ b/sdk/nodejs/ec2/vpc.ts @@ -9,6 +9,47 @@ import * as utilities from "../utilities"; import * as pulumiAws from "@pulumi/aws"; +/** + * The VPC component provides a VPC with configured subnets and NAT gateways. + * + * ## Example Usage + * + * Basic usage: + * + * ```typescript + * import * as pulumi from "@pulumi/pulumi"; + * import * as awsx from "@pulumi/awsx"; + * + * const vpc = new awsx.ec2.Vpc("vpc", {}); + * export const vpcId = vpc.vpcId; + * export const vpcPrivateSubnetIds = vpc.privateSubnetIds; + * export const vpcPublicSubnetIds = vpc.publicSubnetIds; + * ``` + * + * ## Subnet Layout Strategies + * + * If no subnet arguments are passed, then a public and private subnet will be created in each AZ with default sizing. The layout of these subnets can be customised by specifying additional arguments. + * + * All strategies are designed to help build a uniform layout of subnets each each availability zone. + * + * If no strategy is specified, "Legacy" will be used for backward compatibility reasons. In the next major version this will change to defaulting to "Auto". + * + * ### Auto + * + * The "Auto" strategy divides the VPC space evenly between the availability zones. Within each availability zone it allocates each subnet in the order they were specified. If a CIDR mask or size was not specified it will default to an even division of the availability zone range. If subnets have different sizes, spaces will be automatically added to ensure subnets don't overlap (e.g. where a previous subnet is smaller than the next). + * + * ### Exact + * + * The "Exact" strategy is the same as "Auto" with the additional requirement to explicitly specify what the whole of each zone's range will be used for. Where you expect to have a gap between or after subnets, these must be passed using the subnet specification type "Unused" to show all space has been properly accounted for. + * + * ### Explicit CIDR Blocks + * + * If you prefer to do your CIDR block calculations yourself, you can specify a list of CIDR blocks for each subnet spec which it will be allocated for in each availability zone. If using explicit layouts, all subnet specs must be declared with explicit CIDR blocks. Each list of CIDR blocks must have the same length as the number of availability zones for the VPC. + * + * ### Legacy + * + * The "Legacy" works similarly to the "Auto" strategy except that within each availability zone it allocates the private subnet first, followed by the private subnets, and lastly the isolated subnets. The order of subnet specifications of the same type can be changed, but the ordering of private, public, isolated is not overridable. For more flexibility we recommend moving to the "Auto" strategy. The output property `subnetLayout` shows the configuration required if specifying the "Auto" strategy to maintain the current layout. + */ export class Vpc extends pulumi.ComponentResource { /** @internal */ public static readonly __pulumiType = 'awsx:ec2:Vpc'; @@ -51,6 +92,10 @@ export class Vpc extends pulumi.ComponentResource { * The Routes for the VPC. */ public /*out*/ readonly routes!: pulumi.Output; + /** + * The resolved subnet specs layout deployed to each availability zone. + */ + public /*out*/ readonly subnetLayout!: pulumi.Output; /** * The VPC's subnets. */ @@ -77,6 +122,7 @@ export class Vpc extends pulumi.ComponentResource { opts = opts || {}; if (!opts.id) { resourceInputs["assignGeneratedIpv6CidrBlock"] = args ? args.assignGeneratedIpv6CidrBlock : undefined; + resourceInputs["availabilityZoneCidrMask"] = args ? args.availabilityZoneCidrMask : undefined; resourceInputs["availabilityZoneNames"] = args ? args.availabilityZoneNames : undefined; resourceInputs["cidrBlock"] = args ? args.cidrBlock : undefined; resourceInputs["enableDnsHostnames"] = args ? args.enableDnsHostnames : undefined; @@ -92,6 +138,7 @@ export class Vpc extends pulumi.ComponentResource { resourceInputs["natGateways"] = args ? args.natGateways : undefined; resourceInputs["numberOfAvailabilityZones"] = args ? args.numberOfAvailabilityZones : undefined; resourceInputs["subnetSpecs"] = args ? args.subnetSpecs : undefined; + resourceInputs["subnetStrategy"] = args ? args.subnetStrategy : undefined; resourceInputs["tags"] = args ? args.tags : undefined; resourceInputs["vpcEndpointSpecs"] = args ? args.vpcEndpointSpecs : undefined; resourceInputs["eips"] = undefined /*out*/; @@ -102,6 +149,7 @@ export class Vpc extends pulumi.ComponentResource { resourceInputs["routeTableAssociations"] = undefined /*out*/; resourceInputs["routeTables"] = undefined /*out*/; resourceInputs["routes"] = undefined /*out*/; + resourceInputs["subnetLayout"] = undefined /*out*/; resourceInputs["subnets"] = undefined /*out*/; resourceInputs["vpc"] = undefined /*out*/; resourceInputs["vpcEndpoints"] = undefined /*out*/; @@ -116,6 +164,7 @@ export class Vpc extends pulumi.ComponentResource { resourceInputs["routeTableAssociations"] = undefined /*out*/; resourceInputs["routeTables"] = undefined /*out*/; resourceInputs["routes"] = undefined /*out*/; + resourceInputs["subnetLayout"] = undefined /*out*/; resourceInputs["subnets"] = undefined /*out*/; resourceInputs["vpc"] = undefined /*out*/; resourceInputs["vpcEndpoints"] = undefined /*out*/; @@ -134,6 +183,10 @@ export interface VpcArgs { * Requests an Amazon-provided IPv6 CIDR block with a /56 prefix length for the VPC. You cannot specify the range of IP addresses, or the size of the CIDR block. Default is `false`. Conflicts with `ipv6_ipam_pool_id` */ assignGeneratedIpv6CidrBlock?: pulumi.Input; + /** + * The netmask for each available zone to be aligned to. This is optional, the default value is inferred based on an even distribution of available space from the VPC's CIDR block after being divided evenly by the number of availability zones. + */ + availabilityZoneCidrMask?: number; /** * A list of availability zone names to which the subnets defined in subnetSpecs will be deployed. Optional, defaults to the first 3 AZs in the current region. */ @@ -194,6 +247,10 @@ export interface VpcArgs { * A list of subnet specs that should be deployed to each AZ specified in availabilityZoneNames. Optional. Defaults to a (smaller) public subnet and a (larger) private subnet based on the size of the CIDR block for the VPC. Private subnets are allocated CIDR block ranges first, followed by Private subnets, and Isolated subnets are allocated last. */ subnetSpecs?: inputs.ec2.SubnetSpecArgs[]; + /** + * The strategy to use when allocating subnets for the VPC. Optional. Defaults to `Legacy`. + */ + subnetStrategy?: enums.ec2.SubnetAllocationStrategy; /** * A map of tags to assign to the resource. If configured with a provider `default_tags` configuration block present, tags with matching keys will overwrite those defined at the provider-level. */ diff --git a/sdk/nodejs/types/enums/ec2/index.ts b/sdk/nodejs/types/enums/ec2/index.ts index 33a2b9135..de1c47d97 100644 --- a/sdk/nodejs/types/enums/ec2/index.ts +++ b/sdk/nodejs/types/enums/ec2/index.ts @@ -22,6 +22,26 @@ export const NatGatewayStrategy = { */ export type NatGatewayStrategy = (typeof NatGatewayStrategy)[keyof typeof NatGatewayStrategy]; +export const SubnetAllocationStrategy = { + /** + * Group private subnets first, followed by public subnets, followed by isolated subnets. + */ + Legacy: "Legacy", + /** + * Order remains as specified by specs, allowing gaps where required. + */ + Auto: "Auto", + /** + * Whole range of VPC must be accounted for, using "Unused" spec types for deliberate gaps. + */ + Exact: "Exact", +} as const; + +/** + * Strategy for calculating subnet ranges from the subnet specifications. + */ +export type SubnetAllocationStrategy = (typeof SubnetAllocationStrategy)[keyof typeof SubnetAllocationStrategy]; + export const SubnetType = { /** * A subnet whose hosts can directly communicate with the internet. @@ -35,6 +55,10 @@ export const SubnetType = { * A subnet whose hosts have no connectivity with the internet. */ Isolated: "Isolated", + /** + * A subnet range which is reserved, but no subnet will be created. + */ + Unused: "Unused", } as const; /** diff --git a/sdk/nodejs/types/input.ts b/sdk/nodejs/types/input.ts index 11706590d..99ca9369b 100644 --- a/sdk/nodejs/types/input.ts +++ b/sdk/nodejs/types/input.ts @@ -384,13 +384,21 @@ export namespace ec2 { */ export interface SubnetSpecArgs { /** - * The bitmask for the subnet's CIDR block. + * An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. + */ + cidrBlocks?: string[]; + /** + * The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. */ cidrMask?: number; /** * The subnet's name. Will be templated upon creation. */ name?: string; + /** + * Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + */ + size?: number; /** * A map of tags to assign to the resource. */ diff --git a/sdk/nodejs/types/output.ts b/sdk/nodejs/types/output.ts index 734456d9d..437dc6cc3 100644 --- a/sdk/nodejs/types/output.ts +++ b/sdk/nodejs/types/output.ts @@ -16,6 +16,32 @@ export namespace cloudtrail { } export namespace ec2 { + /** + * Configuration for a VPC subnet spec. + */ + export interface ResolvedSubnetSpec { + /** + * An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. + */ + cidrBlocks?: string[]; + /** + * The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + */ + cidrMask?: number; + /** + * The subnet's name. Will be templated upon creation. + */ + name?: string; + /** + * Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + */ + size?: number; + /** + * The type of subnet. + */ + type: enums.ec2.SubnetType; + } + } export namespace ecr { diff --git a/sdk/python/pulumi_awsx/ec2/__init__.py b/sdk/python/pulumi_awsx/ec2/__init__.py index 7be3dd3d1..0721e1c82 100644 --- a/sdk/python/pulumi_awsx/ec2/__init__.py +++ b/sdk/python/pulumi_awsx/ec2/__init__.py @@ -10,3 +10,4 @@ from .get_default_vpc import * from .vpc import * from ._inputs import * +from . import outputs diff --git a/sdk/python/pulumi_awsx/ec2/_enums.py b/sdk/python/pulumi_awsx/ec2/_enums.py index 63dbcf9ff..bbcf41965 100644 --- a/sdk/python/pulumi_awsx/ec2/_enums.py +++ b/sdk/python/pulumi_awsx/ec2/_enums.py @@ -6,6 +6,7 @@ __all__ = [ 'NatGatewayStrategy', + 'SubnetAllocationStrategy', 'SubnetType', ] @@ -28,6 +29,24 @@ class NatGatewayStrategy(str, Enum): """ +class SubnetAllocationStrategy(str, Enum): + """ + Strategy for calculating subnet ranges from the subnet specifications. + """ + LEGACY = "Legacy" + """ + Group private subnets first, followed by public subnets, followed by isolated subnets. + """ + AUTO = "Auto" + """ + Order remains as specified by specs, allowing gaps where required. + """ + EXACT = "Exact" + """ + Whole range of VPC must be accounted for, using "Unused" spec types for deliberate gaps. + """ + + class SubnetType(str, Enum): """ A type of subnet within a VPC. @@ -44,3 +63,7 @@ class SubnetType(str, Enum): """ A subnet whose hosts have no connectivity with the internet. """ + UNUSED = "Unused" + """ + A subnet range which is reserved, but no subnet will be created. + """ diff --git a/sdk/python/pulumi_awsx/ec2/_inputs.py b/sdk/python/pulumi_awsx/ec2/_inputs.py index be6b45dc4..b096565a3 100644 --- a/sdk/python/pulumi_awsx/ec2/_inputs.py +++ b/sdk/python/pulumi_awsx/ec2/_inputs.py @@ -60,21 +60,29 @@ def elastic_ip_allocation_ids(self, value: Optional[Sequence[pulumi.Input[str]]] class SubnetSpecArgs: def __init__(__self__, *, type: 'SubnetType', + cidr_blocks: Optional[Sequence[str]] = None, cidr_mask: Optional[int] = None, name: Optional[str] = None, + size: Optional[int] = None, tags: Optional[pulumi.Input[Mapping[str, pulumi.Input[str]]]] = None): """ Configuration for a VPC subnet. :param 'SubnetType' type: The type of subnet. - :param int cidr_mask: The bitmask for the subnet's CIDR block. + :param Sequence[str] cidr_blocks: An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. + :param int cidr_mask: The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. :param str name: The subnet's name. Will be templated upon creation. + :param int size: Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. :param pulumi.Input[Mapping[str, pulumi.Input[str]]] tags: A map of tags to assign to the resource. """ pulumi.set(__self__, "type", type) + if cidr_blocks is not None: + pulumi.set(__self__, "cidr_blocks", cidr_blocks) if cidr_mask is not None: pulumi.set(__self__, "cidr_mask", cidr_mask) if name is not None: pulumi.set(__self__, "name", name) + if size is not None: + pulumi.set(__self__, "size", size) if tags is not None: pulumi.set(__self__, "tags", tags) @@ -90,11 +98,23 @@ def type(self) -> 'SubnetType': def type(self, value: 'SubnetType'): pulumi.set(self, "type", value) + @property + @pulumi.getter(name="cidrBlocks") + def cidr_blocks(self) -> Optional[Sequence[str]]: + """ + An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. + """ + return pulumi.get(self, "cidr_blocks") + + @cidr_blocks.setter + def cidr_blocks(self, value: Optional[Sequence[str]]): + pulumi.set(self, "cidr_blocks", value) + @property @pulumi.getter(name="cidrMask") def cidr_mask(self) -> Optional[int]: """ - The bitmask for the subnet's CIDR block. + The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. """ return pulumi.get(self, "cidr_mask") @@ -114,6 +134,18 @@ def name(self) -> Optional[str]: def name(self, value: Optional[str]): pulumi.set(self, "name", value) + @property + @pulumi.getter + def size(self) -> Optional[int]: + """ + Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + """ + return pulumi.get(self, "size") + + @size.setter + def size(self, value: Optional[int]): + pulumi.set(self, "size", value) + @property @pulumi.getter def tags(self) -> Optional[pulumi.Input[Mapping[str, pulumi.Input[str]]]]: diff --git a/sdk/python/pulumi_awsx/ec2/outputs.py b/sdk/python/pulumi_awsx/ec2/outputs.py new file mode 100644 index 000000000..f09028422 --- /dev/null +++ b/sdk/python/pulumi_awsx/ec2/outputs.py @@ -0,0 +1,105 @@ +# coding=utf-8 +# *** WARNING: this file was generated by pulumi-gen-awsx. *** +# *** Do not edit by hand unless you're certain you know what you are doing! *** + +import copy +import warnings +import pulumi +import pulumi.runtime +from typing import Any, Mapping, Optional, Sequence, Union, overload +from .. import _utilities +from ._enums import * + +__all__ = [ + 'ResolvedSubnetSpec', +] + +@pulumi.output_type +class ResolvedSubnetSpec(dict): + """ + Configuration for a VPC subnet spec. + """ + @staticmethod + def __key_warning(key: str): + suggest = None + if key == "cidrBlocks": + suggest = "cidr_blocks" + elif key == "cidrMask": + suggest = "cidr_mask" + + if suggest: + pulumi.log.warn(f"Key '{key}' not found in ResolvedSubnetSpec. Access the value via the '{suggest}' property getter instead.") + + def __getitem__(self, key: str) -> Any: + ResolvedSubnetSpec.__key_warning(key) + return super().__getitem__(key) + + def get(self, key: str, default = None) -> Any: + ResolvedSubnetSpec.__key_warning(key) + return super().get(key, default) + + def __init__(__self__, *, + type: 'SubnetType', + cidr_blocks: Optional[Sequence[str]] = None, + cidr_mask: Optional[int] = None, + name: Optional[str] = None, + size: Optional[int] = None): + """ + Configuration for a VPC subnet spec. + :param 'SubnetType' type: The type of subnet. + :param Sequence[str] cidr_blocks: An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. + :param int cidr_mask: The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + :param str name: The subnet's name. Will be templated upon creation. + :param int size: Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + """ + pulumi.set(__self__, "type", type) + if cidr_blocks is not None: + pulumi.set(__self__, "cidr_blocks", cidr_blocks) + if cidr_mask is not None: + pulumi.set(__self__, "cidr_mask", cidr_mask) + if name is not None: + pulumi.set(__self__, "name", name) + if size is not None: + pulumi.set(__self__, "size", size) + + @property + @pulumi.getter + def type(self) -> 'SubnetType': + """ + The type of subnet. + """ + return pulumi.get(self, "type") + + @property + @pulumi.getter(name="cidrBlocks") + def cidr_blocks(self) -> Optional[Sequence[str]]: + """ + An optional list of CIDR blocks to assign to the subnet spec for each AZ. If specified, the count must match the number of AZs being used for the VPC, and must also be specified for all other subnet specs. + """ + return pulumi.get(self, "cidr_blocks") + + @property + @pulumi.getter(name="cidrMask") + def cidr_mask(self) -> Optional[int]: + """ + The netmask for the subnet's CIDR block. This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + """ + return pulumi.get(self, "cidr_mask") + + @property + @pulumi.getter + def name(self) -> Optional[str]: + """ + The subnet's name. Will be templated upon creation. + """ + return pulumi.get(self, "name") + + @property + @pulumi.getter + def size(self) -> Optional[int]: + """ + Optional size of the subnet's CIDR block - the number of hosts. This value must be a power of 2 (e.g. 256, 512, 1024, etc.). This is optional, the default value is inferred from the `cidrMask`, `cidrBlocks` or based on an even distribution of available space from the VPC's CIDR block after being divided evenly by availability zone. + """ + return pulumi.get(self, "size") + + diff --git a/sdk/python/pulumi_awsx/ec2/vpc.py b/sdk/python/pulumi_awsx/ec2/vpc.py index 6920ee2fd..6076b9bf3 100644 --- a/sdk/python/pulumi_awsx/ec2/vpc.py +++ b/sdk/python/pulumi_awsx/ec2/vpc.py @@ -8,6 +8,7 @@ import pulumi.runtime from typing import Any, Mapping, Optional, Sequence, Union, overload from .. import _utilities +from . import outputs from ._enums import * from ._inputs import * import pulumi_aws @@ -18,6 +19,7 @@ class VpcArgs: def __init__(__self__, *, assign_generated_ipv6_cidr_block: Optional[pulumi.Input[bool]] = None, + availability_zone_cidr_mask: Optional[int] = None, availability_zone_names: Optional[Sequence[str]] = None, cidr_block: Optional[str] = None, enable_dns_hostnames: Optional[pulumi.Input[bool]] = None, @@ -33,11 +35,13 @@ def __init__(__self__, *, nat_gateways: Optional['NatGatewayConfigurationArgs'] = None, number_of_availability_zones: Optional[int] = None, subnet_specs: Optional[Sequence['SubnetSpecArgs']] = None, + subnet_strategy: Optional['SubnetAllocationStrategy'] = None, tags: Optional[pulumi.Input[Mapping[str, pulumi.Input[str]]]] = None, vpc_endpoint_specs: Optional[Sequence['VpcEndpointSpecArgs']] = None): """ The set of arguments for constructing a Vpc resource. :param pulumi.Input[bool] assign_generated_ipv6_cidr_block: Requests an Amazon-provided IPv6 CIDR block with a /56 prefix length for the VPC. You cannot specify the range of IP addresses, or the size of the CIDR block. Default is `false`. Conflicts with `ipv6_ipam_pool_id` + :param int availability_zone_cidr_mask: The netmask for each available zone to be aligned to. This is optional, the default value is inferred based on an even distribution of available space from the VPC's CIDR block after being divided evenly by the number of availability zones. :param Sequence[str] availability_zone_names: A list of availability zone names to which the subnets defined in subnetSpecs will be deployed. Optional, defaults to the first 3 AZs in the current region. :param str cidr_block: The CIDR block for the VPC. Optional. Defaults to 10.0.0.0/16. :param pulumi.Input[bool] enable_dns_hostnames: A boolean flag to enable/disable DNS hostnames in the VPC. Defaults false. @@ -53,11 +57,14 @@ def __init__(__self__, *, :param 'NatGatewayConfigurationArgs' nat_gateways: Configuration for NAT Gateways. Optional. If private and public subnets are both specified, defaults to one gateway per availability zone. Otherwise, no gateways will be created. :param int number_of_availability_zones: A number of availability zones to which the subnets defined in subnetSpecs will be deployed. Optional, defaults to the first 3 AZs in the current region. :param Sequence['SubnetSpecArgs'] subnet_specs: A list of subnet specs that should be deployed to each AZ specified in availabilityZoneNames. Optional. Defaults to a (smaller) public subnet and a (larger) private subnet based on the size of the CIDR block for the VPC. Private subnets are allocated CIDR block ranges first, followed by Private subnets, and Isolated subnets are allocated last. + :param 'SubnetAllocationStrategy' subnet_strategy: The strategy to use when allocating subnets for the VPC. Optional. Defaults to `Legacy`. :param pulumi.Input[Mapping[str, pulumi.Input[str]]] tags: A map of tags to assign to the resource. If configured with a provider `default_tags` configuration block present, tags with matching keys will overwrite those defined at the provider-level. :param Sequence['VpcEndpointSpecArgs'] vpc_endpoint_specs: A list of VPC Endpoints specs to be deployed as part of the VPC """ if assign_generated_ipv6_cidr_block is not None: pulumi.set(__self__, "assign_generated_ipv6_cidr_block", assign_generated_ipv6_cidr_block) + if availability_zone_cidr_mask is not None: + pulumi.set(__self__, "availability_zone_cidr_mask", availability_zone_cidr_mask) if availability_zone_names is not None: pulumi.set(__self__, "availability_zone_names", availability_zone_names) if cidr_block is not None: @@ -88,6 +95,8 @@ def __init__(__self__, *, pulumi.set(__self__, "number_of_availability_zones", number_of_availability_zones) if subnet_specs is not None: pulumi.set(__self__, "subnet_specs", subnet_specs) + if subnet_strategy is not None: + pulumi.set(__self__, "subnet_strategy", subnet_strategy) if tags is not None: pulumi.set(__self__, "tags", tags) if vpc_endpoint_specs is not None: @@ -105,6 +114,18 @@ def assign_generated_ipv6_cidr_block(self) -> Optional[pulumi.Input[bool]]: def assign_generated_ipv6_cidr_block(self, value: Optional[pulumi.Input[bool]]): pulumi.set(self, "assign_generated_ipv6_cidr_block", value) + @property + @pulumi.getter(name="availabilityZoneCidrMask") + def availability_zone_cidr_mask(self) -> Optional[int]: + """ + The netmask for each available zone to be aligned to. This is optional, the default value is inferred based on an even distribution of available space from the VPC's CIDR block after being divided evenly by the number of availability zones. + """ + return pulumi.get(self, "availability_zone_cidr_mask") + + @availability_zone_cidr_mask.setter + def availability_zone_cidr_mask(self, value: Optional[int]): + pulumi.set(self, "availability_zone_cidr_mask", value) + @property @pulumi.getter(name="availabilityZoneNames") def availability_zone_names(self) -> Optional[Sequence[str]]: @@ -285,6 +306,18 @@ def subnet_specs(self) -> Optional[Sequence['SubnetSpecArgs']]: def subnet_specs(self, value: Optional[Sequence['SubnetSpecArgs']]): pulumi.set(self, "subnet_specs", value) + @property + @pulumi.getter(name="subnetStrategy") + def subnet_strategy(self) -> Optional['SubnetAllocationStrategy']: + """ + The strategy to use when allocating subnets for the VPC. Optional. Defaults to `Legacy`. + """ + return pulumi.get(self, "subnet_strategy") + + @subnet_strategy.setter + def subnet_strategy(self, value: Optional['SubnetAllocationStrategy']): + pulumi.set(self, "subnet_strategy", value) + @property @pulumi.getter def tags(self) -> Optional[pulumi.Input[Mapping[str, pulumi.Input[str]]]]: @@ -316,6 +349,7 @@ def __init__(__self__, resource_name: str, opts: Optional[pulumi.ResourceOptions] = None, assign_generated_ipv6_cidr_block: Optional[pulumi.Input[bool]] = None, + availability_zone_cidr_mask: Optional[int] = None, availability_zone_names: Optional[Sequence[str]] = None, cidr_block: Optional[str] = None, enable_dns_hostnames: Optional[pulumi.Input[bool]] = None, @@ -331,14 +365,55 @@ def __init__(__self__, nat_gateways: Optional[pulumi.InputType['NatGatewayConfigurationArgs']] = None, number_of_availability_zones: Optional[int] = None, subnet_specs: Optional[Sequence[pulumi.InputType['SubnetSpecArgs']]] = None, + subnet_strategy: Optional['SubnetAllocationStrategy'] = None, tags: Optional[pulumi.Input[Mapping[str, pulumi.Input[str]]]] = None, vpc_endpoint_specs: Optional[Sequence[pulumi.InputType['VpcEndpointSpecArgs']]] = None, __props__=None): """ - Create a Vpc resource with the given unique name, props, and options. + The VPC component provides a VPC with configured subnets and NAT gateways. + + ## Example Usage + + Basic usage: + + ```python + import pulumi + import pulumi_awsx as awsx + + vpc = awsx.ec2.Vpc("vpc") + pulumi.export("vpcId", vpc.vpc_id) + pulumi.export("vpcPrivateSubnetIds", vpc.private_subnet_ids) + pulumi.export("vpcPublicSubnetIds", vpc.public_subnet_ids) + ``` + + ## Subnet Layout Strategies + + If no subnet arguments are passed, then a public and private subnet will be created in each AZ with default sizing. The layout of these subnets can be customised by specifying additional arguments. + + All strategies are designed to help build a uniform layout of subnets each each availability zone. + + If no strategy is specified, "Legacy" will be used for backward compatibility reasons. In the next major version this will change to defaulting to "Auto". + + ### Auto + + The "Auto" strategy divides the VPC space evenly between the availability zones. Within each availability zone it allocates each subnet in the order they were specified. If a CIDR mask or size was not specified it will default to an even division of the availability zone range. If subnets have different sizes, spaces will be automatically added to ensure subnets don't overlap (e.g. where a previous subnet is smaller than the next). + + ### Exact + + The "Exact" strategy is the same as "Auto" with the additional requirement to explicitly specify what the whole of each zone's range will be used for. Where you expect to have a gap between or after subnets, these must be passed using the subnet specification type "Unused" to show all space has been properly accounted for. + + ### Explicit CIDR Blocks + + If you prefer to do your CIDR block calculations yourself, you can specify a list of CIDR blocks for each subnet spec which it will be allocated for in each availability zone. If using explicit layouts, all subnet specs must be declared with explicit CIDR blocks. Each list of CIDR blocks must have the same length as the number of availability zones for the VPC. + + ### Legacy + + The "Legacy" works similarly to the "Auto" strategy except that within each availability zone it allocates the private subnet first, followed by the private subnets, and lastly the isolated subnets. The order of subnet specifications of the same type can be changed, but the ordering of private, public, isolated is not overridable. For more flexibility we recommend moving to the "Auto" strategy. The output property `subnetLayout` shows the configuration required if specifying the "Auto" strategy to maintain the current layout. + :param str resource_name: The name of the resource. :param pulumi.ResourceOptions opts: Options for the resource. :param pulumi.Input[bool] assign_generated_ipv6_cidr_block: Requests an Amazon-provided IPv6 CIDR block with a /56 prefix length for the VPC. You cannot specify the range of IP addresses, or the size of the CIDR block. Default is `false`. Conflicts with `ipv6_ipam_pool_id` + :param int availability_zone_cidr_mask: The netmask for each available zone to be aligned to. This is optional, the default value is inferred based on an even distribution of available space from the VPC's CIDR block after being divided evenly by the number of availability zones. :param Sequence[str] availability_zone_names: A list of availability zone names to which the subnets defined in subnetSpecs will be deployed. Optional, defaults to the first 3 AZs in the current region. :param str cidr_block: The CIDR block for the VPC. Optional. Defaults to 10.0.0.0/16. :param pulumi.Input[bool] enable_dns_hostnames: A boolean flag to enable/disable DNS hostnames in the VPC. Defaults false. @@ -354,6 +429,7 @@ def __init__(__self__, :param pulumi.InputType['NatGatewayConfigurationArgs'] nat_gateways: Configuration for NAT Gateways. Optional. If private and public subnets are both specified, defaults to one gateway per availability zone. Otherwise, no gateways will be created. :param int number_of_availability_zones: A number of availability zones to which the subnets defined in subnetSpecs will be deployed. Optional, defaults to the first 3 AZs in the current region. :param Sequence[pulumi.InputType['SubnetSpecArgs']] subnet_specs: A list of subnet specs that should be deployed to each AZ specified in availabilityZoneNames. Optional. Defaults to a (smaller) public subnet and a (larger) private subnet based on the size of the CIDR block for the VPC. Private subnets are allocated CIDR block ranges first, followed by Private subnets, and Isolated subnets are allocated last. + :param 'SubnetAllocationStrategy' subnet_strategy: The strategy to use when allocating subnets for the VPC. Optional. Defaults to `Legacy`. :param pulumi.Input[Mapping[str, pulumi.Input[str]]] tags: A map of tags to assign to the resource. If configured with a provider `default_tags` configuration block present, tags with matching keys will overwrite those defined at the provider-level. :param Sequence[pulumi.InputType['VpcEndpointSpecArgs']] vpc_endpoint_specs: A list of VPC Endpoints specs to be deployed as part of the VPC """ @@ -364,7 +440,46 @@ def __init__(__self__, args: Optional[VpcArgs] = None, opts: Optional[pulumi.ResourceOptions] = None): """ - Create a Vpc resource with the given unique name, props, and options. + The VPC component provides a VPC with configured subnets and NAT gateways. + + ## Example Usage + + Basic usage: + + ```python + import pulumi + import pulumi_awsx as awsx + + vpc = awsx.ec2.Vpc("vpc") + pulumi.export("vpcId", vpc.vpc_id) + pulumi.export("vpcPrivateSubnetIds", vpc.private_subnet_ids) + pulumi.export("vpcPublicSubnetIds", vpc.public_subnet_ids) + ``` + + ## Subnet Layout Strategies + + If no subnet arguments are passed, then a public and private subnet will be created in each AZ with default sizing. The layout of these subnets can be customised by specifying additional arguments. + + All strategies are designed to help build a uniform layout of subnets each each availability zone. + + If no strategy is specified, "Legacy" will be used for backward compatibility reasons. In the next major version this will change to defaulting to "Auto". + + ### Auto + + The "Auto" strategy divides the VPC space evenly between the availability zones. Within each availability zone it allocates each subnet in the order they were specified. If a CIDR mask or size was not specified it will default to an even division of the availability zone range. If subnets have different sizes, spaces will be automatically added to ensure subnets don't overlap (e.g. where a previous subnet is smaller than the next). + + ### Exact + + The "Exact" strategy is the same as "Auto" with the additional requirement to explicitly specify what the whole of each zone's range will be used for. Where you expect to have a gap between or after subnets, these must be passed using the subnet specification type "Unused" to show all space has been properly accounted for. + + ### Explicit CIDR Blocks + + If you prefer to do your CIDR block calculations yourself, you can specify a list of CIDR blocks for each subnet spec which it will be allocated for in each availability zone. If using explicit layouts, all subnet specs must be declared with explicit CIDR blocks. Each list of CIDR blocks must have the same length as the number of availability zones for the VPC. + + ### Legacy + + The "Legacy" works similarly to the "Auto" strategy except that within each availability zone it allocates the private subnet first, followed by the private subnets, and lastly the isolated subnets. The order of subnet specifications of the same type can be changed, but the ordering of private, public, isolated is not overridable. For more flexibility we recommend moving to the "Auto" strategy. The output property `subnetLayout` shows the configuration required if specifying the "Auto" strategy to maintain the current layout. + :param str resource_name: The name of the resource. :param VpcArgs args: The arguments to use to populate this resource's properties. :param pulumi.ResourceOptions opts: Options for the resource. @@ -381,6 +496,7 @@ def _internal_init(__self__, resource_name: str, opts: Optional[pulumi.ResourceOptions] = None, assign_generated_ipv6_cidr_block: Optional[pulumi.Input[bool]] = None, + availability_zone_cidr_mask: Optional[int] = None, availability_zone_names: Optional[Sequence[str]] = None, cidr_block: Optional[str] = None, enable_dns_hostnames: Optional[pulumi.Input[bool]] = None, @@ -396,6 +512,7 @@ def _internal_init(__self__, nat_gateways: Optional[pulumi.InputType['NatGatewayConfigurationArgs']] = None, number_of_availability_zones: Optional[int] = None, subnet_specs: Optional[Sequence[pulumi.InputType['SubnetSpecArgs']]] = None, + subnet_strategy: Optional['SubnetAllocationStrategy'] = None, tags: Optional[pulumi.Input[Mapping[str, pulumi.Input[str]]]] = None, vpc_endpoint_specs: Optional[Sequence[pulumi.InputType['VpcEndpointSpecArgs']]] = None, __props__=None): @@ -410,6 +527,7 @@ def _internal_init(__self__, __props__ = VpcArgs.__new__(VpcArgs) __props__.__dict__["assign_generated_ipv6_cidr_block"] = assign_generated_ipv6_cidr_block + __props__.__dict__["availability_zone_cidr_mask"] = availability_zone_cidr_mask __props__.__dict__["availability_zone_names"] = availability_zone_names __props__.__dict__["cidr_block"] = cidr_block __props__.__dict__["enable_dns_hostnames"] = enable_dns_hostnames @@ -425,6 +543,7 @@ def _internal_init(__self__, __props__.__dict__["nat_gateways"] = nat_gateways __props__.__dict__["number_of_availability_zones"] = number_of_availability_zones __props__.__dict__["subnet_specs"] = subnet_specs + __props__.__dict__["subnet_strategy"] = subnet_strategy __props__.__dict__["tags"] = tags __props__.__dict__["vpc_endpoint_specs"] = vpc_endpoint_specs __props__.__dict__["eips"] = None @@ -435,6 +554,7 @@ def _internal_init(__self__, __props__.__dict__["route_table_associations"] = None __props__.__dict__["route_tables"] = None __props__.__dict__["routes"] = None + __props__.__dict__["subnet_layout"] = None __props__.__dict__["subnets"] = None __props__.__dict__["vpc"] = None __props__.__dict__["vpc_endpoints"] = None @@ -509,6 +629,14 @@ def routes(self) -> pulumi.Output[Sequence['pulumi_aws.ec2.Route']]: """ return pulumi.get(self, "routes") + @property + @pulumi.getter(name="subnetLayout") + def subnet_layout(self) -> pulumi.Output[Sequence['outputs.ResolvedSubnetSpec']]: + """ + The resolved subnet specs layout deployed to each availability zone. + """ + return pulumi.get(self, "subnet_layout") + @property @pulumi.getter def subnets(self) -> pulumi.Output[Sequence['pulumi_aws.ec2.Subnet']]: