Repository: aws-cloudformation/cfn-lint

Issue:
E3002: Property IpAddresses should be of type List for resource
*cfn-lint version: cfn-lint 0.25.2*

When I use "conditional !If" inside "IpAddresses" properties of "AWS::Route53Resolver::ResolverEndpoint" I get error E3002.

```bash
cfn-lint --info -t vpc-k8s.template 
2019-11-22 10:03:57,241 - cfnlint - INFO - Run scan of template vpc-k8s.template
E3002 Property IpAddresses should be of type List for resource R53ResolverInboundEndPoint at Resources/R53ResolverInboundEndPoint/Properties/IpAddresses/Fn::If/2
vpc-k8s.template:824:11
```
But the template work perfectly and `aws cloudformation validate-template --template-body file://vpc-k8s.template` works fine too

```yaml
  R53ResolverInboundEndPoint:
    Type: AWS::Route53Resolver::ResolverEndpoint
    Condition: CreateR53Resolver
    Properties:
      Direction: INBOUND
      IpAddresses: !If
        - 3AZ
        - - SubnetId: !Ref PrivateSubnet1
          - SubnetId: !Ref PrivateSubnet2
          - SubnetId: !Ref PrivateSubnet3
        - !If
          - 2AZ
          - - SubnetId: !Ref PrivateSubnet1
            - SubnetId: !Ref PrivateSubnet2
          - - "AWS::NoValue"
      Name: !Sub "vpc-${VpcName}-inbound-endpoint"
      SecurityGroupIds:
        - !Ref R53ResolverSecurityGroup
```

![Screenshot 2019-11-22 at 9 39 05 AM](https://user-images.githubusercontent.com/1197820/69412968-35b65100-0d10-11ea-97f3-d71423b63ef0.png)
![Screenshot 2019-11-22 at 9 39 15 AM](https://user-images.githubusercontent.com/1197820/69412981-39e26e80-0d10-11ea-9273-4377975c9d89.png)



Discussion:
Thanks for the details.  Thought we had this covered but obviously not.  I will take a look.

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
@@ -68,65 +68,79 @@ def primitivetypecheck(self, value, primtype, proppath):
 
         return matches
 
-    def check_list_for_condition(self, text, prop, parenttype, resourcename, propspec, path):
-        """Checks lists that are a dict for conditions"""
+    def _check_list_for_condition(self, text, prop, parenttype, resourcename, propspec, path):
+        """ Loop for conditions """
         matches = []
-        if len(text[prop]) == 1:  # pylint: disable=R1702
-            for sub_key, sub_value in text[prop].items():
-                if sub_key in cfnlint.helpers.CONDITION_FUNCTIONS:
-                    if len(sub_value) == 3:
-                        for if_i, if_v in enumerate(sub_value[1:]):
-                            condition_path = path[:] + [sub_key, if_i + 1]
-                            if isinstance(if_v, list):
-                                for index, item in enumerate(if_v):
-                                    arrproppath = condition_path[:]
+        if len(text) == 3:
+            for if_i, if_v in enumerate(text[1:]):
+                condition_path = path[:] + [if_i + 1]
+                if isinstance(if_v, list):
+                    for index, item in enumerate(if_v):
+                        arrproppath = condition_path[:]
 
-                                    arrproppath.append(index)
-                                    matches.extend(self.propertycheck(
-                                        item, propspec['ItemType'],
-                                        parenttype, resourcename, arrproppath, False))
-                            elif isinstance(if_v, dict):
-                                if len(if_v) == 1:
-                                    for d_k, d_v in if_v.items():
-                                        if d_k != 'Ref' or d_v != 'AWS::NoValue':
-                                            if d_k == 'Fn::GetAtt':
-                                                resource_name = None
-                                                if isinstance(d_v, list):
-                                                    resource_name = d_v[0]
-                                                elif isinstance(d_v, six.string_types):
-                                                    resource_name = d_v.split('.')[0]
-                                                if resource_name:
-                                                    resource_type = self.cfn.template.get(
-                                                        'Resources', {}).get(resource_name, {}).get('Type')
-                                                    if not (resource_type.startswith('Custom::')):
-                                                        message = 'Property {0} should be of type List for resource {1} at {2}'
-                                                        matches.append(
-                                                            RuleMatch(
-                                                                condition_path,
-                                                                message.format(prop, resourcename, ('/'.join(str(x) for x in condition_path)))))
-                                            else:
-                                                message = 'Property {0} should be of type List for resource {1} at {2}'
-                                                matches.append(
-                                                    RuleMatch(
-                                                        condition_path,
-                                                        message.format(prop, resourcename, ('/'.join(str(x) for x in condition_path)))))
+                        arrproppath.append(index)
+                        matches.extend(self.propertycheck(
+                            item, propspec['ItemType'],
+                            parenttype, resourcename, arrproppath, False))
+                elif isinstance(if_v, dict):
+                    if len(if_v) == 1:
+                        for d_k, d_v in if_v.items():
+                            if d_k != 'Ref' or d_v != 'AWS::NoValue':
+                                if d_k == 'Fn::GetAtt':
+                                    resource_name = None
+                                    if isinstance(d_v, list):
+                                        resource_name = d_v[0]
+                                    elif isinstance(d_v, six.string_types):
+                                        resource_name = d_v.split('.')[0]
+                                    if resource_name:
+                                        resource_type = self.cfn.template.get(
+                                            'Resources', {}).get(resource_name, {}).get('Type')
+                                        if not (resource_type.startswith('Custom::')):
+                                            message = 'Property {0} should be of type List for resource {1} at {2}'
+                                            matches.append(
+                                                RuleMatch(
+                                                    condition_path,
+                                                    message.format(prop, resourcename, ('/'.join(str(x) for x in condition_path)))))
+                                elif d_k == 'Fn::If':
+                                    matches.extend(
+                                        self._check_list_for_condition(
+                                            d_v, prop, parenttype, resourcename, propspec, condition_path)
+                                    )
                                 else:
                                     message = 'Property {0} should be of type List for resource {1} at {2}'
                                     matches.append(
                                         RuleMatch(
                                             condition_path,
                                             message.format(prop, resourcename, ('/'.join(str(x) for x in condition_path)))))
-                            else:
-                                message = 'Property {0} should be of type List for resource {1} at {2}'
-                                matches.append(
-                                    RuleMatch(
-                                        condition_path,
-                                        message.format(prop, resourcename, ('/'.join(str(x) for x in condition_path)))))
-
                     else:
-                        message = 'Invalid !If condition specified at %s' % (
-                            '/'.join(map(str, path)))
-                        matches.append(RuleMatch(path, message))
+                        message = 'Property {0} should be of type List for resource {1} at {2}'
+                        matches.append(
+                            RuleMatch(
+                                condition_path,
+                                message.format(prop, resourcename, ('/'.join(str(x) for x in condition_path)))))
+                else:
+                    message = 'Property {0} should be of type List for resource {1} at {2}'
+                    matches.append(
+                        RuleMatch(
+                            condition_path,
+                            message.format(prop, resourcename, ('/'.join(str(x) for x in condition_path)))))
+
+        else:
+            message = 'Invalid !If condition specified at %s' % (
+                '/'.join(map(str, path)))
+            matches.append(RuleMatch(path, message))
+
+        return matches
+
+    def check_list_for_condition(self, text, prop, parenttype, resourcename, propspec, path):
+        """Checks lists that are a dict for conditions"""
+        matches = []
+        if len(text[prop]) == 1:  # pylint: disable=R1702
+            for sub_key, sub_value in text[prop].items():
+                if sub_key in cfnlint.helpers.CONDITION_FUNCTIONS:
+                    matches.extend(self._check_list_for_condition(
+                        sub_value, prop, parenttype, resourcename, propspec, path + [sub_key]
+                    ))
                 else:
                     # FindInMaps can be lists of objects so skip checking those
                     if sub_key != 'Fn::FindInMap':


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
@@ -668,6 +668,12 @@ Resources:
           KeyType: HASH
         - AttributeName: !Ref SortKeyName
           KeyType: RANGE
-      - - AttributeName: !Ref PartitionKeyName
-          KeyType: HASH
-        - "String2"
+      - !If
+        - HasSortKey
+        - - AttributeName: !Ref PartitionKeyName
+            KeyType: HASH
+          - AttributeName: !Ref SortKeyName
+            KeyType: RANGE
+        - - AttributeName: !Ref PartitionKeyName
+            KeyType: HASH
+          - "String2"
diff --git a/test/fixtures/templates/good/resources/properties/properties_nested_if.yaml b/test/fixtures/templates/good/resources/properties/properties_nested_if.yaml
new file mode 100644
--- /dev/null
+++ b/test/fixtures/templates/good/resources/properties/properties_nested_if.yaml
@@ -0,0 +1,20 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+  R53ResolverInboundEndPoint:
+    Type: AWS::Route53Resolver::ResolverEndpoint
+    Condition: CreateR53Resolver
+    Properties:
+      Direction: INBOUND
+      IpAddresses: !If
+        - 3AZ
+        - - SubnetId: !Ref PrivateSubnet1
+          - SubnetId: !Ref PrivateSubnet2
+          - SubnetId: !Ref PrivateSubnet3
+        - !If
+          - 2AZ
+          - - SubnetId: !Ref PrivateSubnet1
+            - SubnetId: !Ref PrivateSubnet2
+          - - "AWS::NoValue"
+      Name: !Sub "vpc-${VpcName}-inbound-endpoint"
+      SecurityGroupIds:
+        - !Ref R53ResolverSecurityGroup
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
@@ -18,6 +18,7 @@ def setUp(self):
         self.success_templates = [
             'test/fixtures/templates/good/resource_properties.yaml',
             'test/fixtures/templates/good/resources/properties/templated_code.yaml',
+            'test/fixtures/templates/good/resources/properties/properties_nested_if.yaml',
         ]
 
     def test_file_positive(self):