cfn-lint icon indicating copy to clipboard operation
cfn-lint copied to clipboard

Crash when full YAML FindInMap inside short GetAtt

Open pnickerson-cashstar opened this issue 8 months ago • 6 comments

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

pnickerson-cashstar avatar Apr 11 '25 22:04 pnickerson-cashstar

I realized that I was running an older version of cfn-lint. I installed 1.33.2, and it has the same behavior.

pnickerson-cashstar avatar Apr 11 '25 23:04 pnickerson-cashstar

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.

kddejong avatar Apr 15 '25 15:04 kddejong

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.

kddejong avatar Apr 15 '25 16:04 kddejong

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

pnickerson-cashstar avatar Apr 17 '25 15:04 pnickerson-cashstar

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

pnickerson-cashstar avatar Apr 17 '25 15:04 pnickerson-cashstar

This is valid if you have Transform: AWS::LanguageExtensions. docs

kddejong avatar Apr 23 '25 14:04 kddejong