Skip to content

Commit

Permalink
Fix IPV6 cidr blocks
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 committed Nov 4, 2024
1 parent 1236da2 commit 5963e39
Show file tree
Hide file tree
Showing 10 changed files with 377 additions and 24 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.5.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"
]
}
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.5.0",
"@pulumi/docker": "^4.5.0",
"@pulumi/pulumi": "3.121.0",
"@types/archiver": "^6.0.2",
Expand Down
28 changes: 22 additions & 6 deletions src/converters/app-converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export class StackConverter extends ArtifactConverter {
private readonly cdkStack: cdk.Stack;

private _stackResource?: CdkConstruct;
private _graph: GraphBuilder;

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

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

// process parameters first because resources will reference them
for (const [logicalId, value] of Object.entries(this.stack.parameters ?? {})) {
Expand Down Expand Up @@ -359,7 +361,21 @@ 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 === this._graph.vpcNode?.logicalId &&
attributeName === 'Ipv6CidrBlocks' &&
this._graph.vpcCidrBlockNode?.logicalId
) {
return [this.resolveAtt(this._graph.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
36 changes: 31 additions & 5 deletions src/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,19 @@ export class GraphBuilder {
// Map of resource logicalId to GraphNode. Allows for easy lookup by logicalId
cfnElementNodes: Map<string, GraphNode>;

// If the app has a VpcCidrBlock resource, this will be set to the GraphNode representing it
vpcCidrBlockNode?: GraphNode;
// If the app has a Vpc resource, this will be set to the GraphNode representing it
vpcNode?: GraphNode;

constructor(private readonly stack: StackManifest) {
this.constructNodes = new Map<ConstructInfo, GraphNode>();
this.cfnElementNodes = new Map<string, GraphNode>();
}

// build constructs a dependency graph from the adapter and returns its nodes sorted in topological order.
public static build(stack: StackManifest): GraphNode[] {
const b = new GraphBuilder(stack);
return b._build();
public build(): GraphNode[] {
return this._build();
}

/**
Expand Down Expand Up @@ -163,6 +167,12 @@ export class GraphBuilder {
`Something went wrong: resourceType ${resource.Type} does not equal CfnType ${cfnType}`,
);
}
if (resource.Type === 'AWS::EC2::VPCCidrBlock') {
this.vpcCidrBlockNode = node;
}
if (resource.Type === 'AWS::EC2::VPC') {
this.vpcNode = node;
}
}
this.constructNodes.set(construct, node);
if (tree.children) {
Expand Down Expand Up @@ -285,9 +295,25 @@ export class GraphBuilder {

private addEdgesForIntrinsic(fn: string, params: any, source: GraphNode) {
switch (fn) {
case 'Fn::GetAtt':
this.addEdgeForRef(params[0], source);
case 'Fn::GetAtt': {
let logicalId = params[0];
const attributeName = params[1];
// 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.
// Here we switching the dependency to be on the `VpcCidrBlock` resource (since that will also have a dependency
// on the VPC resource)
if (
logicalId === this.vpcNode?.logicalId &&
attributeName === 'Ipv6CidrBlocks' &&
this.vpcCidrBlockNode?.logicalId
) {
logicalId = this.vpcCidrBlockNode.logicalId;
}
this.addEdgeForRef(logicalId, source);
break;
}
case 'Fn::Sub':
{
const [template, vars] =
Expand Down
Loading

0 comments on commit 5963e39

Please sign in to comment.