Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions awsx/ec2/subnetDistributorLegacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export function getSubnetSpecsLegacy(
type: "Private",
subnetName: `${vpcName}-${privateSubnetsIn[j].name ?? "private"}-${i + 1}`,
tags: privateSubnetsIn[j].tags,
assignIpv6AddressOnCreation: privateSubnetsIn[j].assignIpv6AddressOnCreation,
});
currentAddress = nextAddress;
}
Expand Down Expand Up @@ -125,6 +126,7 @@ export function getSubnetSpecsLegacy(
type: "Public",
subnetName: `${vpcName}-${publicSubnetsIn[j].name ?? "public"}-${i + 1}`,
tags: publicSubnetsIn[j].tags,
assignIpv6AddressOnCreation: publicSubnetsIn[j].assignIpv6AddressOnCreation,
});
currentAddress = nextAddress;
}
Expand Down Expand Up @@ -166,6 +168,7 @@ export function getSubnetSpecsLegacy(
type: "Isolated",
subnetName: `${vpcName}-${isolatedSubnetsIn[j].name ?? "isolated"}-${i + 1}`,
tags: isolatedSubnetsIn[j].tags,
assignIpv6AddressOnCreation: isolatedSubnetsIn[j].assignIpv6AddressOnCreation,
});
currentAddress = nextAddress;
}
Expand Down
2 changes: 2 additions & 0 deletions awsx/ec2/subnetDistributorNew.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export function getSubnetSpecs(
azName,
subnetName: subnetName(vpcName, subnetSpec, azNum),
tags: subnetSpec.tags,
assignIpv6AddressOnCreation: subnetSpec.assignIpv6AddressOnCreation,
};
});
});
Expand Down Expand Up @@ -193,6 +194,7 @@ export function getSubnetSpecsExplicit(
azName,
subnetName: subnetName(vpcName, subnetSpec, azNum),
tags: subnetSpec.tags,
assignIpv6AddressOnCreation: subnetSpec.assignIpv6AddressOnCreation,
});
}
}
Expand Down
1 change: 1 addition & 0 deletions awsx/ec2/subnetSpecs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface SubnetSpec {
type: SubnetTypeInputs;
azName: string;
subnetName: string;
assignIpv6AddressOnCreation?: boolean;
tags?: pulumi.Input<{
[key: string]: pulumi.Input<string>;
}>;
Expand Down
29 changes: 29 additions & 0 deletions awsx/ec2/vpc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { getSubnetSpecsLegacy } from "./subnetDistributorLegacy";
import {
compareSubnetSpecs,
findSubnetGap,
createIpv6SubnetCidrBlock,
OverlappingSubnet,
shouldCreateNatGateway,
validateEips,
Expand Down Expand Up @@ -470,3 +471,31 @@ describe("child resource api", () => {
}
});
});

describe("createIpv6SubnetCidrBlock", () => {
it("index 0 keeps the trailing 00 byte", () => {
expect(createIpv6SubnetCidrBlock("2600:1f14:82a:ab00::/56", 0)).toBe("2600:1f14:82a:ab00::/64");
});

it("index 42 yields 0x2a in fourth hextet", () => {
expect(createIpv6SubnetCidrBlock("2600:1f14:82a:ab00::/56", 42)).toBe(
"2600:1f14:82a:ab2a::/64",
);
});

it("throws on non-IPv6 input", () => {
expect(() => createIpv6SubnetCidrBlock("10.0.0.0/56", 0)).toThrow(TypeError);
});

it("throws on non-/56 VPC block", () => {
expect(() => createIpv6SubnetCidrBlock("2600:1f14:82a:ab00::/48", 0)).toThrow(RangeError);
});

it("throws when index < 0", () => {
expect(() => createIpv6SubnetCidrBlock("2600:1f14:82a:ab00::/56", -1)).toThrow(RangeError);
});

it("throws when index > 255", () => {
expect(() => createIpv6SubnetCidrBlock("2600:1f14:82a:ab00::/56", 300)).toThrow(RangeError);
});
});
43 changes: 43 additions & 0 deletions awsx/ec2/vpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
ExplicitSubnetSpecInputs,
} from "./subnetDistributorNew";
import { Netmask } from "netmask";
import { isIPv6 } from "net";

interface VpcData {
vpc: aws.ec2.Vpc;
Expand Down Expand Up @@ -171,6 +172,8 @@ export class Vpc extends schema.Vpc<VpcData> {
availabilityZone: spec.azName,
mapPublicIpOnLaunch: spec.type.toLowerCase() === "public",
cidrBlock: spec.cidrBlock,
ipv6CidrBlock: handleIpv6Cidr(vpc.ipv6CidrBlock, spec, subnetSpecs),
assignIpv6AddressOnCreation: spec.assignIpv6AddressOnCreation,
tags: {
...sharedTags,
...spec.tags,
Expand Down Expand Up @@ -576,6 +579,7 @@ export function extractSubnetSpecInputFromLegacyLayout(
...extractName(subnet.subnetName, subnet.type),
cidrMask: netmask.bitmask,
...(subnet.tags ? { tags: subnet.tags } : {}),
assignIpv6AddressOnCreation: subnet.assignIpv6AddressOnCreation,
});
previousNetmask = netmask;
}
Expand Down Expand Up @@ -785,3 +789,42 @@ export function validateNoGaps(vpcCidr: string, subnetSpecs: SubnetSpec[]) {
`There are gaps in the subnet ranges. Please fix the following gaps: ${gaps.join(", ")}`,
);
}

export function createIpv6SubnetCidrBlock(vpcCidr: string, index: number) {
const [addr, mask] = vpcCidr.split("/");

if (mask !== "56") {
throw new RangeError(`VPC must be a /56 block (got /${mask})`);
}
if (!isIPv6(addr)) {
throw new TypeError(`"${addr}" is not a valid IPv6 address`);
}
if (!Number.isInteger(index) || index < 0 || index > 0xff) {
throw new RangeError("index must be an integer from 0-255");
}

const hextets = addr.split(":");
const prefix = hextets.slice(0, 3).join(":");

const existing = parseInt(hextets[3], 16);
const updated = (existing & 0xff00) | index;
const result = updated.toString(16);

return `${prefix}:${result}::/64`;
}

export function handleIpv6Cidr(
ipv6CidrBlock: pulumi.Output<string>,
spec: SubnetSpecPartial,
specs: SubnetSpecPartial[],
): pulumi.Output<string> | undefined {
if (!spec.assignIpv6AddressOnCreation) return;
const index = specs
.filter((s) => s.type !== "Unused")
.sort(compareSubnetSpecs)
.findIndex((s) => s.subnetName === spec.subnetName);
if (index === -1) return;
return pulumi.output(ipv6CidrBlock).apply((cidrBlock) => {
return createIpv6SubnetCidrBlock(cidrBlock, index);
});
}
2 changes: 2 additions & 0 deletions awsx/schema-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,7 @@ export interface ResolvedSubnetSpecOutputs {
export type SubnetAllocationStrategyInputs = "Legacy" | "Auto" | "Exact";
export type SubnetAllocationStrategyOutputs = "Legacy" | "Auto" | "Exact";
export interface SubnetSpecInputs {
readonly assignIpv6AddressOnCreation?: boolean;
readonly cidrBlocks?: string[];
readonly cidrMask?: number;
readonly name?: string;
Expand All @@ -635,6 +636,7 @@ export interface SubnetSpecInputs {
readonly type: SubnetTypeInputs;
}
export interface SubnetSpecOutputs {
readonly assignIpv6AddressOnCreation?: boolean;
readonly cidrBlocks?: string[];
readonly cidrMask?: number;
readonly name?: string;
Expand Down
23 changes: 23 additions & 0 deletions examples/examples_nodejs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,30 @@ func TestVpc(t *testing.T) {
Dir: filepath.Join(getCwd(t), "vpc", "nodejs", "vpc-multiple-similar-subnet-types"),
RetryFailedSteps: true, // Internet Gateway occasionally fails to delete on first attempt.
})
integration.ProgramTest(t, &test)
}

func TestIPv6Assignment(t *testing.T) {
validate := func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
subnets := stack.Outputs["subnets"].([]any)
for _, subnetInterface := range subnets {
subnet := subnetInterface.(map[string]any)
assignIpv6 := subnet["assignIpv6AddressOnCreation"].(bool)
ipv6Cidr := subnet["ipv6CidrBlock"]

if assignIpv6 {
assert.NotEmpty(t, ipv6Cidr, "Subnets with IPv6 assignment should have IPv6 CIDR")
} else {
assert.Empty(t, ipv6Cidr, "Subnets without IPv6 assignment should have no IPv6 CIDR")
}
}
}
test := getNodeJSBaseOptions(t).
With(integration.ProgramTestOptions{
RunUpdateTest: false,
Dir: filepath.Join(getCwd(t), "vpc", "nodejs", "vpc-ipv6-assignment"),
ExtraRuntimeValidation: validate,
})
integration.ProgramTest(t, &test)
}

Expand Down
3 changes: 3 additions & 0 deletions examples/vpc/nodejs/vpc-ipv6-assignment/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: nodejs
runtime: nodejs
description: AWSX VPC - ipv6
16 changes: 16 additions & 0 deletions examples/vpc/nodejs/vpc-ipv6-assignment/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as awsx from "@pulumi/awsx";

const vpc = new awsx.ec2.Vpc("test-vpc", {
assignGeneratedIpv6CidrBlock: true,
subnetSpecs: [
{
type: "Private",
assignIpv6AddressOnCreation: true,
},
{
type: "Public",
},
],
});

export const subnets = vpc.subnets;
11 changes: 11 additions & 0 deletions examples/vpc/nodejs/vpc-ipv6-assignment/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "nodejs",
"devDependencies": {
"@types/node": "^18"
},
"dependencies": {
"@pulumi/pulumi": "^3.0.0",
"@pulumi/awsx": "latest",
"@pulumi/aws": "^6.0.0"
}
}
18 changes: 18 additions & 0 deletions examples/vpc/nodejs/vpc-ipv6-assignment/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"strict": true,
"outDir": "bin",
"target": "es2016",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.ts"
]
}
5 changes: 5 additions & 0 deletions provider/cmd/pulumi-resource-awsx/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,11 @@
"awsx:ec2:SubnetSpec": {
"description": "Configuration for a VPC subnet.",
"properties": {
"assignIpv6AddressOnCreation": {
"type": "boolean",
"plain": true,
"description": "Indicates whether a network interface created in this subnet receives an IPv6 address."
},
"cidrBlocks": {
"type": "array",
"items": {
Expand Down
4 changes: 4 additions & 0 deletions provider/pkg/schemagen/ec2.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,10 @@ func subnetSpecType() schema.ComplexTypeSpec {
"divided evenly by availability zone.",
TypeSpec: plainInt(),
},
"assignIpv6AddressOnCreation": {
Description: "Indicates whether a network interface created in this subnet receives an IPv6 address.",
TypeSpec: schema.TypeSpec{Type: "boolean", Plain: true},
},
"tags": {
TypeSpec: schema.TypeSpec{
Type: "object",
Expand Down
6 changes: 6 additions & 0 deletions sdk/dotnet/Ec2/Inputs/SubnetSpecArgs.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions sdk/go/awsx/ec2/pulumiTypes.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions sdk/nodejs/types/input.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading