Repository: aws-cloudformation/cfn-lint

Issue:
[AWS::SSM::Association] - [BUG] - cfn-lint doesn't recognize `Values: expected type: JSONArray, found: String`
### CloudFormation Lint Version

cfn-lint 0.87.1

### What operating system are you using?

Windows 11

### Describe the bug

cfn-lint doesn't recognize the following error message:

```
Resource handler returned message:
"Model validation failed (#/Targets/0/Values: expected type: JSONArray, found: String)"
(RequestToken: REQUESTTOKEN, HandlerErrorCode: InvalidRequest)
```

for the following example:

```yaml
  SSMAssociation:
    Type: AWS::SSM::Association
    Properties:
      Name: "SSM Document Name"
      ScheduleExpression: rate(2 days)
      Targets:
        - Key: InstanceIds
          Values: !GetAtt EC2Instance.InstanceId
```

This is how it should look like:

```yaml
  SSMAssociation:
    Type: AWS::SSM::Association
    Properties:
      Name: "SSM Document Name"
      ScheduleExpression: rate(2 days)
      Targets:
        - Key: InstanceIds
          Values:
            - !GetAtt EC2Instance.InstanceId
```

### Expected behavior

cfn-lint finds this error.

### Reproduction template

See above.


Discussion:
Hmm, should be able to find this one.  I wonder if we aren't handling JSON values well.  I'll look into this.
Already fixed in v1 but is a bug in v0.

Resolving patch:
diff --git a/src/cfnlint/rules/resources/properties/Properties.py b/src/cfnlint/rules/resources/properties/Properties.py
--- a/src/cfnlint/rules/resources/properties/Properties.py
+++ b/src/cfnlint/rules/resources/properties/Properties.py
@@ -493,6 +493,26 @@ def propertycheck(self, text, proptype, parenttype, resourcename, path, root):
                                                 message.format(prop, resourcename),
                                             )
                                         )
+                                elif "Fn::GetAtt" in text[prop]:
+                                    getatt = text[prop]["Fn::GetAtt"]
+                                    if isinstance(getatt, str):
+                                        getatt = getatt.split(".", 1)
+                                    valid_getatts = self.cfn.get_valid_getatts()
+                                    if getatt[0] in valid_getatts:
+                                        if getatt[1] in valid_getatts[getatt[0]]:
+                                            getatt_prop = valid_getatts[getatt[0]][
+                                                getatt[1]
+                                            ]
+                                            if getatt_prop.get("Type") != "List":
+                                                message = "Property {0} should be of type List for resource {1}"
+                                                matches.append(
+                                                    RuleMatch(
+                                                        proppath,
+                                                        message.format(
+                                                            prop, resourcename
+                                                        ),
+                                                    )
+                                                )
                                 else:
                                     if len(text[prop]) == 1:
                                         for k in text[prop].keys():
diff --git a/src/cfnlint/template/template.py b/src/cfnlint/template/template.py
--- a/src/cfnlint/template/template.py
+++ b/src/cfnlint/template/template.py
@@ -423,16 +423,10 @@ def build_output_string(resource_type, property_name):
                 valtype = value["Type"]
                 if isinstance(valtype, str):
                     if valtype.startswith(astrik_string_types):
-                        LOGGER.debug(
-                            "Cant build an appropriate getatt list from %s", valtype
-                        )
                         results[name] = {"*": {"PrimitiveItemType": "String"}}
                     elif valtype.startswith(astrik_unknown_types) or valtype.endswith(
                         "::MODULE"
                     ):
-                        LOGGER.debug(
-                            "Cant build an appropriate getatt list from %s", valtype
-                        )
                         results[name] = {"*": {}}
                     else:
                         if value["Type"] in resourcetypes:


Test patch:
diff --git a/test/fixtures/templates/bad/object_should_be_list.yaml b/test/fixtures/templates/bad/object_should_be_list.yaml
--- a/test/fixtures/templates/bad/object_should_be_list.yaml
+++ b/test/fixtures/templates/bad/object_should_be_list.yaml
@@ -677,3 +677,19 @@ Resources:
         - - AttributeName: !Ref PartitionKeyName
             KeyType: HASH
           - "String2"
+  EC2Instance:
+    Type: AWS::EC2::Instance
+    Properties:
+      InstanceType: t2.micro
+      ImageId: XXXXXXXXXXXXXXXXXXXXX
+      Tags:
+        - Key: Name
+          Value: !Ref AWS::StackName
+  SSMAssociation:
+    Type: AWS::SSM::Association
+    Properties:
+      Name: "SSM Document Name"
+      ScheduleExpression: rate(2 days)
+      Targets:
+        - Key: InstanceIds
+          Values: !GetAtt EC2Instance.InstanceId
diff --git a/test/unit/rules/resources/properties/test_properties.py b/test/unit/rules/resources/properties/test_properties.py
--- a/test/unit/rules/resources/properties/test_properties.py
+++ b/test/unit/rules/resources/properties/test_properties.py
@@ -37,7 +37,7 @@ def test_file_negative(self):
     def test_file_negative_2(self):
         """Failure test"""
         self.helper_file_negative(
-            "test/fixtures/templates/bad/object_should_be_list.yaml", 4
+            "test/fixtures/templates/bad/object_should_be_list.yaml", 5
         )
 
     def test_file_negative_3(self):