Reputation: 821
The accepted answer bellow by Victor is the correct workaround.
I get in contact with AWS Support that confirmed the bug. They are aware of the issue and linked me to those 2 links to follow the case.
... During my investigation and replication of the issue, I noticed that this issue seems to be with how CDK handles the VPCEndpoint resource as its parameters include a list of DNS entries with them.
This is a known issue with the resource:
https://github.com/aws/aws-cdk/issues/5897
https://github.com/aws/aws-cdk/issues/9488
Until then if you face this problem, the solution is here.
I was expecting to be able to use Fn.select and Fn.split out of the box, but the bellow code have some very weird behavior, wondering if I'm doing something unexpected.
I expected the output to contains the declared functions fn::Select [1, fn:split [ fn:select [0, getAttr [ something ] ] ] ]
But actually what I see is just getAttr [something]
, what leads to Value of property Parameters must be an object with String (or simple type) properties
during deployment
Any help is appreciated
Here's the code to reproduce the error
#!/usr/bin/env node
import 'source-map-support/register';
import {App, aws_ec2, aws_lambda, Fn, NestedStack, NestedStackProps, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
const app = new App();
export class MainStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);
new Nested1(this, 'first', {})
}
}
export class Nested1 extends NestedStack {
constructor(scope: Construct, id: string, props: NestedStackProps) {
super(scope, id, props);
const vpc = new aws_ec2.Vpc(this, 'vpc', {
subnetConfiguration: [{
cidrMask: 28,
name: 'private-test-bug',
subnetType: aws_ec2.SubnetType.PRIVATE_ISOLATED,
}]
})
const apiEndpoint = new aws_ec2.InterfaceVpcEndpoint(this, `apiEndpoint`, {
service: {
name: 'com.amazonaws.eu-central-1.execute-api',
port: 443,
},
vpc: vpc,
subnets: {
subnets: vpc.isolatedSubnets,
onePerAz: true
},
open: true,
privateDnsEnabled: false,
});
// BUG IS HERE
new Nested2(this, 'nested2', { VpcEndpoint: apiEndpoint }) // CDK should handle and parse the reference, but it dont
}
}
export interface Nested2StackProps extends NestedStackProps { VpcEndpoint: aws_ec2.InterfaceVpcEndpoint; }
export class Nested2 extends NestedStack {
constructor(scope: Construct, id: string, props: Nested2StackProps) {
super(scope, id, props);
const lambda = new aws_lambda.Function(this, 'lambda-function', {
code: aws_lambda.Code.fromInline(`exports.handler = (event, context) => { console.log(process.env.dns_name) }`),
handler: 'index.handler',
runtime: aws_lambda.Runtime.NODEJS_16_X,
environment: { dns_name: Fn.select(1, Fn.split(':', Fn.select(0, props.VpcEndpoint.vpcEndpointDnsEntries))) } // USAGE IS HERE
})
}
}
new MainStack (app, 'TestStack', {
env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
});
Upvotes: 1
Views: 2269
Reputation: 3780
In a nutshell, it looks like a bug in the CDK. The CDK tries to send an array as a parameter to the nested template. Instead, it should join the array and pass it as a string.
I suggest a workaround for the issue. I split my code into two parts. First, I explain the idea and show some code snippets. Finally, I show the complete solution.
We define parameter in the nested stack like this:
const stack = new NestedStack(scope, id, {
parameters: {
VpcEndpointDnsEntries: props.VpcEndpointDnsEntries
},
})
In the code, we create an object for this parameter and use it to get the parameter value.
const endpoints = new CfnParameter(stack, 'VpcEndpointDnsEntries', {
type: 'List<String>',
description: 'List of entries.'
})
// Read the value:
const strings = endpoints.valueAsList
In the final step, we pass parameters to the nested stack almost as usual, except that we join the string.
createNestedStack(stack, 'NestedStack',
{ VpcEndpointDnsEntries: Fn.join(',', apiEndpoint.vpcEndpointDnsEntries) })
Note that the lambda function is not working, it throws a runtime exception. Otherwise, the stack and nested stack deploys normally.
Please find the complete code below.
Sorry, I am really in a hurry now. I plan to fix grammar and update the answer this evening.
import { App, CfnParameter, Fn, NestedStack, Stack } from 'aws-cdk-lib'
import { env } from 'process'
import { InterfaceVpcEndpoint, Vpc } from 'aws-cdk-lib/aws-ec2'
import { Code, Function, Runtime } from 'aws-cdk-lib/aws-lambda'
function createMainStack (scope, id, props) {
const stack = new Stack(scope, id, props)
const vpc = Vpc.fromLookup(stack, 'Vpc', { vpcName: 'WarehousePrinters' })
const apiEndpoint = new InterfaceVpcEndpoint(stack, `ApiEndpoint`, {
service: {
name: 'com.amazonaws.eu-west-1.execute-api',
port: 443,
},
vpc,
subnets: { subnets: vpc.privateSubnets, onePerAz: true },
open: true,
privateDnsEnabled: false,
})
createNestedStack(stack, 'NestedStack', { VpcEndpointDnsEntries: Fn.join(',', apiEndpoint.vpcEndpointDnsEntries) })
return stack
}
function createNestedStack (scope, id, props) {
const stack = new NestedStack(scope, id, {
parameters: {
VpcEndpointDnsEntries: props.VpcEndpointDnsEntries
},
})
const endpoints = new CfnParameter(stack, 'VpcEndpointDnsEntries', {
type: 'List<String>',
description: 'List of entries.'
})
new Function(stack, 'LambdaFunction', {
code: Code.fromInline(`export function handler = () => console.log(env.dns_name)`),
handler: 'index.handler',
runtime: Runtime.NODEJS_16_X,
environment: {
dns_name: Fn.select(1, Fn.split(':', Fn.select(0, endpoints.valueAsList)))
}
})
return stack
}
const app = new App()
createMainStack(app, 'MainStack', {
env: { account: env.CDK_DEFAULT_ACCOUNT, region: env.CDK_DEFAULT_REGION }
})
Upvotes: 1