-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Description
Describe the bug
Hi,
I must integrate some pre-built components (provided as CFN templates) in my app as nested stacks.
When I try to use computed outputs (whose value use an intrinsic function for instance) exposed by the nested stack in my CDK stack, deployment fails with message:
❌ Deployment failed: Error [ValidationError]: Template error: every Fn::Join object requires two parameters, (1) a string delimiter and (2) a list of strings to be joined or a function that returns a list of strings (such as Fn::GetAZs) to be joined.
It looks like the generated CFN is incorrect.
This is confirmed by running cloudformation validate-template
with the CLI.
Expected Behavior
To be able to use nested stack's outputs in my CDK constructs.
Current Behavior
Invalid CFN template generates an error during deployment:
❌ Deployment failed: Error [ValidationError]: Template error: every Fn::Join object requires two parameters, (1) a string delimiter and (2) a list of strings to be joined or a function that returns a list of strings (such as Fn::GetAZs) to be joined.
Reproduction Steps
For instance, I have a CFN template creating some VPC Endpoint, exposing outputs like this:
"Outputs": {
"SimpleOutput": {
"Description": "Simple Output",
"Value": {
"Ref": "VpcEndpoint"
}
},
"ComputedOutput": {
"Description": "Computed Output",
"Value": {
"Fn::Join": [
",",
{
"Fn::GetAtt": "VpcEndpoint.DnsEntries"
}
]
}
}
}
Now consider this stack:
interface MyNestedStackProps extends NestedStackProps {
VpcId: string,
SubnetIDs: string[]
}
class MyNestedStack extends NestedStack {
readonly cfnInc: cfninc.CfnInclude
constructor(scope: Construct, id: string, props: MyNestedStackProps) {
super(scope, id)
// Include CFN template
this.cfnInc = new cfninc.CfnInclude(this, 'CfnTemplate', {
templateFile: 'cfn-template.json',
parameters: {
'VpcId': props.VpcId,
'SubnetIDs': props.SubnetIDs
}
})
}
}
export interface CdkbugreproducerStackProps extends StackProps {
vpcName: string
}
export class CdkbugreproducerStack extends Stack {
constructor(scope: Construct, id: string, props: CdkbugreproducerStackProps) {
super(scope, id, props)
const vpc = Vpc.fromLookup(this, 'MyVPC', { vpcName: 'My-VPC' })
const privateSubnetIds = vpc.privateSubnets.map(_ => _.subnetId)
// Include CFN template as a NESTED STACK in the stack
const nested = new MyNestedStack(this, 'MyNestedStack', {
VpcId: vpc.vpcId,
SubnetIDs: privateSubnetIds
})
const simpleOutput = nested.cfnInc.getOutput('SimpleOutput').value
const computedOutput = nested.cfnInc.getOutput('ComputedOutput').value
new ssm.StringParameter(this, 'MyParamSimpleOutput', {
parameterName: 'MyParamFromStackSimpleOutput',
stringValue: simpleOutput
})
new ssm.StringParameter(this, 'MyParamComputedOutput', {
parameterName: 'MyParamFromStackComputedOutput',
stringValue: computedOutput
})
}
}
So, in this simple example, I just want to get the DNS entries of the VPC Endpoint (created in the nested stack) and create an SSM parameter with its value, which fails.
In practice, we have similar error with other stacks exposing outputs with Fn::Sub
, like for instance in:
"DomainEndpoint": {
"Description": "ElasticSearch Service domain endpoint",
"Value": {
"Fn::Sub": "https://${ElasticSearch.DomainEndpoint}"
}
},
Possible Solution
No idea. I don't even have work-around for this.
Additional Information/Context
No response
CDK CLI Version
2.96.2 (build 3edd240)
Framework Version
2.96.2
Node.js Version
v18.12.0
OS
macos
Language
Typescript
Language Version
3.9.10
Other information
I have a full reproducer project available in case it can help (can push it to GitHub or GitLab if needed).
Some observations after testing various things:
- This error does not happen when including the CFN template directly into the CDK stack, but I must add them as nested stacks (for compliance reasons)
- In the code above, If I comment the SSM parameter for "ComputedOutput", it works.
Here's the output of the synthesis for this "ComputedOutput" SSM parameter:
...
"MyParamComputedOutput8C461DF5": {
"Type": "AWS::SSM::Parameter",
"Properties": {
"Name": "MyParamFromStackComputedOutput",
"Type": "String",
"Value": {
"Fn::Join": [
",",
{
"Fn::GetAtt": [
"MyNestedStackNestedStackMyNestedStackNestedStackResource9C617903",
"Outputs.cdkbugreproducercfMyNestedStackCfnTemplateVpcEndpointC42D0C11DnsEntries"
]
}
]
}
},
...
And the generated CF for the outputs in the included nested stack looks like this:
"Outputs": {
"SimpleOutput": {
"Description": "Simple Output",
"Value": {
"Ref": "VpcEndpoint"
}
},
"ComputedOutput": {
"Description": "Computed Output",
"Value": {
"Fn::Join": [
",",
{
"Fn::GetAtt": "VpcEndpoint.DnsEntries"
}
]
}
},
"cdkbugreproducercfMyNestedStackCfnTemplateVpcEndpointC42D0C11Ref": {
"Value": {
"Ref": "VpcEndpoint"
}
},
"cdkbugreproducercfMyNestedStackCfnTemplateVpcEndpointC42D0C11DnsEntries": {
"Value": {
"Fn::GetAtt": "VpcEndpoint.DnsEntries"
}
}
}