(core): Fn::Split delimiter is escaped when stringified
- Dominant language
- TypeScript
- Stars
- 12.9k
- Forks
- 4.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 83
Description
Consider a `Fn::Split` intrinsic that is encoded as JSON. For example, a `Split` that is used in the definition of [a State Machine](https://github.com/aws/aws-cdk/blob/5f1b57603330e707bc68f56c267a9e45faa29e55/packages/%40aws-cdk/aws-stepfunctions/lib/state-machine.ts#L399) or in [the arguments to a Custom Resource](https://github.com/aws/aws-cdk/pull/13074). If the `Split` delimiter contains some JSON-reserved characters (such as `"` or `\`), [the token-aware stringification](https://github.com/aws/aws-cdk/blob/dc5ee6d55f5156e59d9cbc82e2a9fcb888bb2c1c/packages/%40aws-cdk/core/lib/private/cloudformation-lang.ts#L142) still [quotes the delimiter](https://github.com/aws/aws-cdk/blob/dc5ee6d55f5156e59d9cbc82e2a9fcb888bb2c1c/packages/%40aws-cdk/core/lib/private/cloudformation-lang.ts#L404) as if it were a true string literal. This means that values originating in CloudFormation (such as `CfnParameter`s or `CfnWaitCondition`s) cannot be "parsed" using JSON-reserved characters.
### Reproduction Steps
```ts
import { CfnOutput, CfnParameter, Construct, Fn, Stack, StackProps } from '@aws-cdk/core';
export class CdkStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const parameter = new CfnParameter(this, 'Parameter', {
default: 'abc"def"ghi'
});
const split = Fn.split('"', parameter.valueAsString);
const select = Fn.select(1, split);
const encodedString = this.toJsonString(select);
new CfnOutput(this, 'Output', {
value: encodedString,
});
}
}
```
### What did you expect to happen?
Stack successfully deploys, produces output with value `"def"`.
### What actually happened?
Stack fails to deploy, produces CloudFormation error:
> Template error: Fn::Select cannot select nonexistent value at index 1
### Environment
- **CDK CLI Version :** 1.102.0 (build a75d52f)
- **Framework Version:** 1.102.0
- **Node.js Version:** v15.3.0
- **OS :** macOS 10.15.7
- **Language (Version):** TypeScript (3.9.9)
### Other
Switching the `"` in both the parameter and the split delimiter to some other character such as `:` produces the correct result.
The concrete example that provided the impetus for this bug report was due to #13074 which modified custom resources to accept their Create/Update/Delete parameters as JSON-encoded strings. Previously, we were using a CfnWaitCondition to allow the deployer to manually create a WorkDocs site in the middle of stack deployment (since there is no API or CFN resource to do so). Then, the response sent to CloudFormation to signal successful creation was used in other WorkDocs resources (such as users and folders) to both populate the site organization ID, as well as set an implicit CloudFormation dependency on the creation of the site. Because the signal is presented to the template as a JSON object (see the very bottom of [this documentation page](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-waitcondition.html#using-cfn-waitcondition-signaljson)), some string parsing within the template was necessary to grab the ID from the response. We simply used the Split and Select intrinsics to separate the data into a list of strings and get the ID. The templates that are synthesized before and after #13074 show how the delimiter is escaped from `"\"` to `"\\\""`, while the value cannot be similarly escaped since it itself is an intrinsic. The CfnWaitCondition logical ID is `ActiveDirectoryWorkDocs8BACF9E0`, and an example of the value returned from `{Fn::GetAtt: ["ActiveDirectoryWorkDocs8BACF9E0", "Data"]}` is `{"UniqueId123":"d-83630ac3db"}`
Before:
```json
"Create": {
"service": "WorkDocs",
"action": "createUser",
"parameters": {
"OrganizationId": {
"Fn::Select": [
3,
{
"Fn::Split": [
"\"",
{
"Fn::GetAtt": [
"ActiveDirectoryWorkDocs8BACF9E0",
"Data"
]
}
]
}
]
},
"Username": "username",
"Password": "",
"GivenName": "First",
"Surname": "Last",
"StorageRule": {
"StorageType": "UNLIMITED"
}
},
"physicalResourceId": {
"responsePath": "User.Id"
}
},
```
After:
```json
"Create": {
"Fn::Join": [
"",
[
"{\"service\":\"WorkDocs\",\"action\":\"createUser\",\"parameters\":{\"OrganizationId\":\"",
{
"Fn::Select": [
3,
{
"Fn::Split": [
"\\\"",
{
"Fn::GetAtt": [
"ActiveDirectoryWorkDocs8BACF9E0",
"Data"
]
}
]
}
]
},
"\",\"Username\":\"username\",\"Password\":\"\",\"GivenName\":\"First\",\"Surname\":\"Last\",\"StorageRule\":{\"StorageType\":\"UNLIMITED\"}},\"physicalResourceId\":\
{\"responsePath\":\"User.Id\"}}"
]
]
},
```
---
This is :bug: Bug Report
Contributor guide
Research direction
Start in packages/@aws-cdk/core/lib/private/cloudformation-lang.ts, especially the token-aware stringification around the cited lines. Reproduce the issue with the TypeScript Fn::Split example and compare the synthesized template's delimiter with the intrinsic value. Done means JSON-reserved delimiters remain usable with CloudFormation-originated values and the stack produces the expected selected output.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cloud, infrastructure
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100