Skip to content

Commit

Permalink
Fix IPV6 cidr blocks (#197)
Browse files Browse the repository at this point in the history
Depends on pulumi/pulumi-aws-native#1797

Because of pulumi/pulumi-aws-native#1798 we
need to add a special case for Ipv6 cidr blocks that are added to the
VPC via a `VPCCidrBlock` resource.

This PR adds special handling where we track both the `Vpc` and the
`VpcCidrBlock` resource and swap any references.
  • Loading branch information
corymhall authored Nov 7, 2024
1 parent 6f291ff commit add3044
Show file tree
Hide file tree
Showing 11 changed files with 649 additions and 29 deletions.
3 changes: 3 additions & 0 deletions integration/ec2/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: pulumi-aws-ec2
runtime: nodejs
description: ec2 integration test
127 changes: 127 additions & 0 deletions integration/ec2/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import * as aws from '@pulumi/aws';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import * as pulumicdk from '@pulumi/cdk';
import { SecretValue } from 'aws-cdk-lib/core';

class Ec2Stack extends pulumicdk.Stack {
constructor(app: pulumicdk.App, id: string, options?: pulumicdk.StackOptions) {
super(app, id, options);
const vpc = new ec2.Vpc(this, 'Vpc', {
maxAzs: 2,
ipProtocol: ec2.IpProtocol.DUAL_STACK,
vpnGateway: true,
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
natGateways: 1,
vpnConnections: {
dynamic: {
ip: '1.2.3.4',
tunnelOptions: [
{
preSharedKeySecret: SecretValue.unsafePlainText('secretkey1234'),
},
{
preSharedKeySecret: SecretValue.unsafePlainText('secretkey5678'),
},
],
},
static: {
ip: '4.5.6.7',
staticRoutes: ['192.168.10.0/24', '192.168.20.0/24'],
},
},
subnetConfiguration: [
{
name: 'Public',
subnetType: ec2.SubnetType.PUBLIC,
},
{
name: 'Private',
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
{
name: 'Isolated',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
],
restrictDefaultSecurityGroup: false,
});

vpc.addFlowLog('FlowLogs', {
destination: ec2.FlowLogDestination.toCloudWatchLogs(),
});

vpc.addGatewayEndpoint('Dynamo', {
service: ec2.GatewayVpcEndpointAwsService.DYNAMODB,
});
vpc.addInterfaceEndpoint('ecr', {
service: ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER,
});

new ec2.PrefixList(this, 'PrefixList', {});
const nacl = new ec2.NetworkAcl(this, 'NetworkAcl', {
vpc,
subnetSelection: { subnetType: ec2.SubnetType.PUBLIC },
});
nacl.addEntry('AllowAll', {
cidr: ec2.AclCidr.anyIpv4(),
ruleAction: ec2.Action.ALLOW,
ruleNumber: 100,
traffic: ec2.AclTraffic.allTraffic(),
});
new ec2.KeyPair(this, 'KeyPair');

const nlb = new elbv2.NetworkLoadBalancer(this, 'NLB1', { vpc });
new ec2.VpcEndpointService(this, 'EndpointService', {
vpcEndpointServiceLoadBalancers: [nlb],
allowedPrincipals: [new iam.ArnPrincipal('ec2.amazonaws.com')],
});
}
}

new pulumicdk.App(
'app',
(scope: pulumicdk.App) => {
new Ec2Stack(scope, 'teststack');
},
{
appOptions: {
remapCloudControlResource: (logicalId, typeName, props, options) => {
if (typeName === 'AWS::EC2::VPNGatewayRoutePropagation') {
const tableIds: string[] = props.RouteTableIds;
return tableIds.flatMap((tableId, i) => {
const id = i === 0 ? logicalId : `${logicalId}-${i}`;
return {
logicalId: id,
resource: new aws.ec2.VpnGatewayRoutePropagation(
id,
{
routeTableId: tableId,
vpnGatewayId: props.VpnGatewayId,
},
options,
),
};
});
}
if (typeName === 'AWS::EC2::NetworkAclEntry') {
return new aws.ec2.NetworkAclRule(logicalId, {
egress: props.Egress,
toPort: props.PortRange?.To,
fromPort: props.PortRange?.From,
protocol: props.Protocol,
ruleNumber: props.RuleNumber,
networkAclId: props.NetworkAclId,
ruleAction: props.RuleAction,
cidrBlock: props.CidrBlock,
ipv6CidrBlock: props.Ipv6CidrBlock,
icmpCode: props.Icmp?.Code,
icmpType: props.Icmp?.Type,
});
}
return undefined;
},
},
},
);
15 changes: 15 additions & 0 deletions integration/ec2/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "pulumi-aws-cdk",
"devDependencies": {
"@types/node": "^10.0.0"
},
"dependencies": {
"@pulumi/aws": "^6.0.0",
"@pulumi/aws-native": "^1.6.0",
"@pulumi/cdk": "^0.5.0",
"@pulumi/pulumi": "^3.0.0",
"aws-cdk-lib": "2.149.0",
"constructs": "10.3.0",
"esbuild": "^0.24.0"
}
}
18 changes: 18 additions & 0 deletions integration/ec2/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"strict": true,
"outDir": "bin",
"target": "es2019",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"./*.ts"
]
}
9 changes: 9 additions & 0 deletions integration/examples_nodejs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ func TestApiGatewayDomain(t *testing.T) {
integration.ProgramTest(t, &test)
}

func TestEc2(t *testing.T) {
test := getJSBaseOptions(t).
With(integration.ProgramTestOptions{
Dir: filepath.Join(getCwd(t), "ec2"),
})

integration.ProgramTest(t, &test)
}

func getJSBaseOptions(t *testing.T) integration.ProgramTestOptions {
base := getBaseOptions(t)
baseJS := base.With(integration.ProgramTestOptions{
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"devDependencies": {
"@aws-cdk/aws-apprunner-alpha": "2.20.0-alpha.0",
"@pulumi/aws": "^6.32.0",
"@pulumi/aws-native": "^1.0.0",
"@pulumi/aws-native": "^1.6.0",
"@pulumi/docker": "^4.5.0",
"@pulumi/pulumi": "3.121.0",
"@types/archiver": "^6.0.2",
Expand Down
38 changes: 27 additions & 11 deletions src/converters/app-converter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as cdk from 'aws-cdk-lib/core';
import * as pulumi from '@pulumi/pulumi';
import { AssemblyManifestReader, StackManifest } from '../assembly';
import { ConstructInfo, GraphBuilder, GraphNode } from '../graph';
import { ConstructInfo, Graph, GraphBuilder, GraphNode } from '../graph';
import { ArtifactConverter } from './artifact-converter';
import { lift, Mapping, AppComponent } from '../types';
import { CdkConstruct, ResourceAttributeMapping, ResourceMapping } from '../interop';
Expand Down Expand Up @@ -94,6 +94,7 @@ export class StackConverter extends ArtifactConverter {
private readonly cdkStack: cdk.Stack;

private _stackResource?: CdkConstruct;
private readonly graph: Graph;

public get stackResource(): CdkConstruct {
if (!this._stackResource) {
Expand All @@ -105,17 +106,16 @@ export class StackConverter extends ArtifactConverter {
constructor(host: AppComponent, readonly stack: StackManifest) {
super(host);
this.cdkStack = host.stacks[stack.id];
this.graph = GraphBuilder.build(this.stack);
}

public convert(dependencies: Set<ArtifactConverter>) {
const dependencyGraphNodes = GraphBuilder.build(this.stack);

// process parameters first because resources will reference them
for (const [logicalId, value] of Object.entries(this.stack.parameters ?? {})) {
this.mapParameter(logicalId, value.Type, value.Default);
}

for (const n of dependencyGraphNodes) {
for (const n of this.graph.nodes) {
if (n.construct.id === this.stack.id) {
this._stackResource = new CdkConstruct(`${this.app.name}/${n.construct.path}`, n.construct.id, {
parent: this.app.component,
Expand Down Expand Up @@ -156,8 +156,8 @@ export class StackConverter extends ArtifactConverter {
}
}

for (let i = dependencyGraphNodes.length - 1; i >= 0; i--) {
const n = dependencyGraphNodes[i];
for (let i = this.graph.nodes.length - 1; i >= 0; i--) {
const n = this.graph.nodes[i];
if (!n.resource) {
(<CdkConstruct>this.constructs.get(n.construct)!).done();
}
Expand Down Expand Up @@ -359,7 +359,23 @@ export class StackConverter extends ArtifactConverter {
private resolveIntrinsic(fn: string, params: any) {
switch (fn) {
case 'Fn::GetAtt': {
debug(`Fn::GetAtt(${params[0]}, ${params[1]})`);
const logicalId = params[0];
const attributeName = params[1];
debug(`Fn::GetAtt(${logicalId}, ${attributeName})`);
// Special case for VPC Ipv6CidrBlocks
// Ipv6 cidr blocks are added to the VPC through a separate VpcCidrBlock resource
// Due to [pulumi/pulumi-aws-native#1798] the `Ipv6CidrBlocks` attribute will always be empty
// and we need to instead pull the `Ipv6CidrBlock` attribute from the VpcCidrBlock resource.
if (
logicalId in this.graph.vpcNodes &&
attributeName === 'Ipv6CidrBlocks' &&
this.graph.vpcNodes[logicalId].vpcCidrBlockNode?.logicalId
) {
return [
this.resolveAtt(this.graph.vpcNodes[logicalId].vpcCidrBlockNode.logicalId, 'Ipv6CidrBlock'),
];
}

return this.resolveAtt(params[0], params[1]);
}

Expand All @@ -375,17 +391,17 @@ export class StackConverter extends ArtifactConverter {
case 'Fn::Base64':
return lift((str) => Buffer.from(str).toString('base64'), this.processIntrinsics(params));

case 'Fn::Cidr':
case 'Fn::Cidr': {
return lift(
([ipBlock, count, cidrBits]) =>
cidr({
ipBlock,
count,
cidrBits,
count: parseInt(count, 10),
cidrBits: parseInt(cidrBits, 10),
}).then((r) => r.subnets),
this.processIntrinsics(params),
);

}
case 'Fn::GetAZs':
return lift(([region]) => getAzs({ region }).then((r) => r.azs), this.processIntrinsics(params));

Expand Down
Loading

0 comments on commit add3044

Please sign in to comment.