Farski
Farski

Reputation: 1766

Can custom YAML tags be added to CloudFormation templates using transform macros?

AWS CloudFormation includes support for a number of intrinsic functions that can operate on values that only become available within the stack at runtime. These include functions like Split, Join, and Ref.

When authoring templates in JSON, these functions are represented as key-value pairs, where the keys are Ref, Fn::Split, or Fn::Join. The values depend on which function is being used, and could be a string, array, etc.

When authoring templates in YAML, there are short forms available for these functions, such as !Ref, !Split, and !Join. These take advantage of the custom tag functionality that is part of YAML, which allows for defining new types in a YAML document beyond the native types (string, number, null, etc).

Is it possible to use CloudFormation template macros to create YAML templates that include additional, user-defined custom tags? For example, could a template include !Uppercase some-string that gets processed by the macro into SOME-STRING?

Upvotes: 0

Views: 468

Answers (1)

Farski
Farski

Reputation: 1766

No, it's not possible to create YAML templates with additional custom tags, even when using template macros.

It's safe to assume that CloudFormation's native template format is JSON, and that YAML templates are always converted to JSON before the service does any real work with them. This is true of template macros, as well. The fragment data that a macro's Lambda function receives will always be a JSON-like object, even when the template was authored in YAML.

For example, if a template includes Value: !Ref MyResource, which uses the short form of the Ref intrinsic function, the macro Lambda function will receive "Value": { "Ref": "Foo" }. This is true of any YAML short form syntax present in a template. The short form syntax is expanded to the JSON-friendly long form syntax before being passed to the macro.

The part of the CloudFormation service that parses YAML templates will return an error if any unknown YAML tags are present in the template, even when the template is being transformed by a macro. It will not generically attempt to expand them to long form syntax. So !Uppercase would cause an error very early in the template processing chain of CloudFormation.

Additionally, the template parser will also return an error if there any unknown intrinsic functions are present (which is to say, things that look like native intrinsic functions). For example, including "Value": { "Fn::Uppercase": "Foo" } will return a Encountered unsupported function error even before the template has been processed by macros. It seems as though JSON keys beginning with Fn:: are handled specially by CloudFormation.

There are a couple of ways in which template macros can be used to mimic intrinsic functions, though.

The native Ref intrinsic function expects a string value, and it is possible to create custom pseudo parameters that get processed and replaced by template macros. For example, a macro could be used to support a pseudo parameter like !Ref AcmeCo::Timestamp, similar to the native !Ref AWS::AccountId. As expected this would be expanded to { "Ref": "AcmeCo::Timestamp" } when delivered to the Lambda function, but the function would have the opportunity to find that pseudo parameter and replace it with some static value (like a timestamp, in this case). There is no requirement that custom pseudo parameters follow the Namespace::ParameterName format, but you would want to ensure that any custom pseudo parameters you introduce don't have the possibility of colliding with an actual resource logical ID that may be passed to Ref.

It's also possible to create things similar to intrinsic functions, as long as they don't start with Fn::. For example, you could reimplement the Join function as AcmeFn::Join, or create a new function like AcmeFn::Uppercase. These would make it to the macro Lambda function in a way that could be processed. It's important to remember, though, that the macro function never receives or has access to the runtime values of other functions, so while it would be possible to do Value: { "AcmeFm::Uppercase": "hello world" } and have the macro transform that to Value: "HELLO WORLD", it's not possible to do Value: { "AcmeFm::Uppercase": !Ref MyS3Bucket }, and have it resolve to the uppercased name of some S3 bucket. Any data or values being transformed or manipulated by a macro must be passed directly into the macro as literal values. It's also important to remember that these custom functions would always need to use the long form syntax, since custom YAML tags are not allowed.

Upvotes: 1

Related Questions