cfn-language-discussion icon indicating copy to clipboard operation
cfn-language-discussion copied to clipboard

Using Intrinsic Functions in JSON Keys

Open dkovvuri opened this issue 2 years ago • 5 comments

Community Note

  • Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request
  • Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritize the request
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment

Security disclosures

If you think you’ve found a potential security issue, please do not post it in the Issues. Instead, please follow the instructions here or email AWS security directly.

Request

In CloudFormation certain resource properties in require input as a Map of String such as DockerLabels, RequestTemplates. For these properties it's not possible to use intrinsic functions for the 'keys'. Requesting Support for Intrinsic functions in template JSON keys.

Tell us about the problem you are trying to solve. What are you trying to do, and why is it hard?

For example, I'd like to create an ECS Task Definition resource by specifying DockerLabels as such:

  • Using an intrinsic function in the JSON key:
          DockerLabels:
            !Sub "Production-${AppName}": !Ref Priority

Using this specification fails with a validation error Template format error: [/Resources/TaskDefinition/Type/ContainerDefinitions/0/DockerLabels] map keys must be strings; received a map instead

  • Using the Fn::Sub at the property level as such:
          DockerLabels:
            Fn::Sub:
              - | 
                Production-${AppName}: ${Priority}
              - AppName: "Traffic"
                Priority: "2"

This fails during resource creation with the error #/ContainerDefinitions/0/DockerLabels: expected type: JSONObject

Are you currently working around this issue?

The only workaround is to manually hardcode the map of strings or using a custom-macro to process the template parameters in to a dynamic-map.

What is the expect behavior with this new feature

          DockerLabels:
            !Sub "Production-${AppName}": !Ref Priority

This should be able to dynamically collapse to:

          DockerLabels:
           "Production-Logging" : 2

dkovvuri avatar Jun 09 '22 15:06 dkovvuri

I'm not sure if YAML supports tags on keys, which would prevent this syntax. I believe the way YAML works, the entire value after the tag is passed to it. So:

DockerLabels:
  !Sub "Production-${AppName}": !Ref Priority

can only really be equivalent to

DockerLabels:
  Fn::Sub:
    "Production-${AppName}": !Ref Priority

I think I'd prefer a more explicit form than your positional arguments, perhaps something like

DockerLabels:
  !Sub
    Key: Production-${AppName}
    Value: ${Priority}
    # Parameters: (if needed)

benkehoe avatar Jun 16 '22 19:06 benkehoe

Aren't you missing a : after !Sub?

josb avatar Jun 17 '22 17:06 josb

!Sub is a YAML tag. It is a marker that the YAML parser uses to interpret the YAML data that follows the tag. So !SomeTag foo is a YAML snippet that has the SomeTag tag applied to the string foo. In CloudFormation's case, the parser pretty much only does one thing, which is convert !TagName <data> to {"Fn::TagName": <data>} (with the exception that !Ref becomes "Ref" not "Fn::Ref".

Currently, all the tags in use only allow the data following the tag to be a string (for a single parameter) or a list (for multiple parameters). My example uses an object, which is essentially using multiple named parameters.

benkehoe avatar Jun 17 '22 19:06 benkehoe

Right, the unusual vertical space threw me off. Yes, that looks fine upon closer inspection (and I'm aware of YAML tags and how they generally work). Thanks for the quick and thorough explanation, Ben!

josb avatar Jun 17 '22 19:06 josb

Being very specific to the property DockerLabels. The property only accepts a map-of-strings so in that case the following specification will not work as Fn::Sub returns a string and this case it would be a stringified JSON (For example: {\n \"Key\": \"test\",\n \"Value\": \"test\"\n})

DockerLabels:
  !Sub
    Key: Production-${AppName}
    Value: ${Priority}
    # Parameters: (if needed)

One solution I can think of is - the way the AWS::ECS::TaskDefinition resource provider is implemented needs to change from accepting a Map of Strings to using a List of Tags data-type. That way, I could author my template as such:

DockerLabels:
   - Key: !Sub Production-${AppName}
     Value: !Sub ${Priority}

dkovvuri avatar Jun 17 '22 20:06 dkovvuri