Crash when full YAML FindInMap inside short GetAtt
CloudFormation Lint Version
1.31.3
What operating system are you using?
Fedora Linux 41 (Workstation Edition)
Describe the bug
CloudFormation's GetAtt and FindInMap intrinsic functions each have full function name and short form syntaxes in YAML. If the AWS::LanguageExtensions transform is declared, then FindInMap can be used inside GetAtt. However, if GetAtt is in the short form and FindInMap is written out with the full function name, then cfn-lint will crash, even though the YAML is syntactically valid.
From the complete reproduction template below, this section causes the crash:
Value: !GetAtt
Fn::FindInMap:
- S3Domains
- Bucket1
- Domain
If I instead use only the full function names, then cfn-lint does not crash and passes.
Value:
Fn::GetAtt:
Fn::FindInMap:
- S3Domains
- Bucket1
- Domain
As an aside: If I use rain's format command on this full function name syntax, it converts it to the mixed syntax format seen in the complete reproduction template below.
Here is the output from the crash. Nothing else is outputted:
Traceback (most recent call last):
File "/usr/bin/cfn-lint", line 8, in <module>
sys.exit(main())
~~~~^^
File "/usr/lib/python3.13/site-packages/cfnlint/runner.py", line 466, in main
runner.cli()
~~~~~~~~~~^^
File "/usr/lib/python3.13/site-packages/cfnlint/runner.py", line 453, in cli
self._cli_output(list(self.run()))
~~~~^^^^^^^^^^^^
File "/usr/lib/python3.13/site-packages/cfnlint/runner.py", line 405, in run
yield from self._validate_filenames(self.config.templates)
File "/usr/lib/python3.13/site-packages/cfnlint/runner.py", line 299, in _validate_filenames
(template, matches) = decode(filename)
~~~~~~^^^^^^^^^^
File "/usr/lib/python3.13/site-packages/cfnlint/decode/decode.py", line 32, in decode
return _decode(cfn_yaml.load, cfn_json.load, filename, filename)
File "/usr/lib/python3.13/site-packages/cfnlint/decode/decode.py", line 42, in _decode
template = yaml_f(payload)
File "/usr/lib/python3.13/site-packages/cfnlint/decode/cfn_yaml.py", line 327, in load
return loads(content, filename)
File "/usr/lib/python3.13/site-packages/cfnlint/decode/cfn_yaml.py", line 300, in loads
template = loader.get_single_data()
File "/usr/lib64/python3.13/site-packages/yaml/constructor.py", line 51, in get_single_data
return self.construct_document(node)
~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "/usr/lib64/python3.13/site-packages/yaml/constructor.py", line 55, in construct_document
data = self.construct_object(node)
File "/usr/lib64/python3.13/site-packages/yaml/constructor.py", line 100, in construct_object
data = constructor(self, node)
File "/usr/lib/python3.13/site-packages/cfnlint/decode/cfn_yaml.py", line 100, in construct_yaml_map
value = self.construct_object(value_node, False)
File "/usr/lib64/python3.13/site-packages/yaml/constructor.py", line 100, in construct_object
data = constructor(self, node)
File "/usr/lib/python3.13/site-packages/cfnlint/decode/cfn_yaml.py", line 100, in construct_yaml_map
value = self.construct_object(value_node, False)
File "/usr/lib64/python3.13/site-packages/yaml/constructor.py", line 100, in construct_object
data = constructor(self, node)
File "/usr/lib/python3.13/site-packages/cfnlint/decode/cfn_yaml.py", line 100, in construct_yaml_map
value = self.construct_object(value_node, False)
File "/usr/lib64/python3.13/site-packages/yaml/constructor.py", line 100, in construct_object
data = constructor(self, node)
File "/usr/lib/python3.13/site-packages/cfnlint/decode/cfn_yaml.py", line 100, in construct_yaml_map
value = self.construct_object(value_node, False)
File "/usr/lib64/python3.13/site-packages/yaml/constructor.py", line 100, in construct_object
data = constructor(self, node)
File "/usr/lib/python3.13/site-packages/cfnlint/decode/cfn_yaml.py", line 195, in construct_yaml_seq
(obj,) = SafeConstructor.construct_yaml_seq(self, node)
^^^^^^
File "/usr/lib64/python3.13/site-packages/yaml/constructor.py", line 408, in construct_yaml_seq
data.extend(self.construct_sequence(node))
~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "/usr/lib64/python3.13/site-packages/yaml/constructor.py", line 129, in construct_sequence
return [self.construct_object(child, deep=deep)
~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
File "/usr/lib64/python3.13/site-packages/yaml/constructor.py", line 100, in construct_object
data = constructor(self, node)
File "/usr/lib/python3.13/site-packages/cfnlint/decode/cfn_yaml.py", line 100, in construct_yaml_map
value = self.construct_object(value_node, False)
File "/usr/lib64/python3.13/site-packages/yaml/constructor.py", line 102, in construct_object
data = constructor(self, tag_suffix, node)
File "/usr/lib/python3.13/site-packages/cfnlint/decode/cfn_yaml.py", line 291, in multi_constructor
return dict_node({tag_suffix: constructor(node)}, node.start_mark, node.end_mark)
~~~~~~~~~~~^^^^^^
File "/usr/lib/python3.13/site-packages/cfnlint/decode/cfn_yaml.py", line 266, in construct_getatt
return [self.construct_object(child, deep=False) for child in node.value]
~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
File "/usr/lib64/python3.13/site-packages/yaml/constructor.py", line 79, in construct_object
if node.tag in self.yaml_constructors:
^^^^^^^^
AttributeError: 'tuple' object has no attribute 'tag'
Expected behavior
I expect cfn-lint to exit successfully, without reporting any syntax issues.
Reproduction template
AWSTemplateFormatVersion: "2010-09-09"
Mappings:
S3Domains:
Bucket1:
Domain: S3Bucket1.DomainName
Transform: AWS::LanguageExtensions
Resources:
S3Bucket1:
Type: AWS::S3::Bucket
Properties:
BucketName: my-bucket-one
S3Bucket2:
Type: AWS::S3::Bucket
Properties:
BucketName: my-bucket-two
Tags:
- Key: Bucket1Domain
Value: !GetAtt
Fn::FindInMap:
- S3Domains
- Bucket1
- Domain
I realized that I was running an older version of cfn-lint. I installed 1.33.2, and it has the same behavior.
The reproduction template fails with Transform AWS::LanguageExtensions failed with: Fn::GetAtt layout is incorrect. Rollback requested by user.. We should be more gracefully handle this issue.
So I have a fix for this but this reproduction template will now work without any issues (cfn-lint) where as the transform itself is failing.
Thanks, handling the situation gracefully will be good.
I did some testing myself, and am posting what I learned here just in case anyone comes across this in the future, to help them.
First, the GetAtt YAML full function name syntax vs. short form syntax are rather different from each other. One takes a list, and the other a single value:
Fn::GetAtt: [ logicalNameOfResource, attributeName ]
!GetAtt logicalNameOfResource.attributeName
So, using the short form GetAtt as below should be valid, but when I try to use it I get that same Transform AWS::LanguageExtensions failed with: Fn::GetAtt layout is incorrect. Rollback requested by user. error as you:
Value: !GetAtt
Fn::FindInMap:
- S3Domains
- Bucket1
- Domain
In my original submission I mentioned that using the full function name for GetAtt passes cfn-lint. I now realize that this is actually wrong. And when I try using it, I get the Fn::GetAtt layout is incorrect error again, which this time makes sense. The below is invalid syntax, per Amazon's documentation. Actually, now that I think of it, I think this is another bug in cfn-lint. Should I open a new bug report?
Value:
Fn::GetAtt:
Fn::FindInMap:
- S3Domains
- Bucket1
- Domain
Anyways, I figured out the completely correct way to do this. It passes cfn-lint 1.33.2, and it successfully runs in CloudFormation. The S3 buckets are created, and the tag on the second bucket shows up as expected.
AWSTemplateFormatVersion: "2010-09-09"
Mappings:
S3Domains:
Bucket1:
DomainResource: S3Bucket1
DomainAttribute: DomainName
Transform: AWS::LanguageExtensions
Resources:
S3Bucket1:
Type: AWS::S3::Bucket
Properties:
BucketName: my-bucket-one
S3Bucket2:
Type: AWS::S3::Bucket
Properties:
BucketName: my-bucket-two
Tags:
- Key: Bucket1Domain
Value:
Fn::GetAtt:
- Fn::FindInMap:
- S3Domains
- Bucket1
- DomainResource
- Fn::FindInMap:
- S3Domains
- Bucket1
- DomainAttribute
Ok, another comment. I tried passing that though rain format, and it converted the last part to the below. Per Amazon's documentation, this should be invalid syntax. And yet, it passes cfn-lint and when I use it to make a stack in CloudFormation it creates the resources and the tag no problem. So... I guess it's fine after all?
- Key: Bucket1Domain
Value: !GetAtt
- !FindInMap
- S3Domains
- Bucket1
- DomainResource
- !FindInMap
- S3Domains
- Bucket1
- DomainAttribute
This is valid if you have Transform: AWS::LanguageExtensions. docs