Repository: aws-cloudformation/cfn-lint

Issue:
How to handle conditionals in list items
*cfn-lint version: `0.7.1`*

*I have a condition around whether or not a fargate service uses the service registry. The way I had it set up fails to pass linting, but does work in CloudFormation.*

Here is the template causing the problem (abbreviated to just the problem areas):

```yaml
AWSTemplateFormatVersion: 2010-09-09

Parameters:

  ServiceDiscoveryName:
    Description: Name of the service when used for internal service discovery
    Type: String
    Default: ''

Conditions:

  UseServiceDiscovery:
    Fn::Not:
      - Fn::Equals:
          - Ref: ServiceDiscoveryName
          - ''

Resources:

  Service:
    Type: AWS::ECS::Service
    Properties:
      # other properties here
      ServiceRegistries:
        Fn::If:
          - UseServiceDiscovery
          - - RegistryArn:
                Fn::GetAtt: [ServiceDiscoveryRegistry, Arn]
          - Ref: AWS::NoValue
```

This results in the linter error:
```
Property ServiceRegistries should be of type List for resource Service at Resources/Service/Properties/ServiceRegistries
cloudformation/shared-templates/fargate-service-alb.yaml:111:7
```

I then tried the following change:
```
    ServiceRegistries:
        - RegistryArn:
            Fn::If:
              - UseServiceDiscovery
              - Fn::GetAtt: [ServiceDiscoveryRegistry, Arn]
              - Ref: AWS::NoValue
```

And that did appease the linter. However, it resulted in the following CloudFormation error:
```
registry arn cannot be blank. (Service: AmazonECS; Status Code: 400; Error Code: InvalidParameterException; Request ID: 5bd193f7-b861-11e8-8b0a-cd1b086b8bb9)
```

Our work-around to keep us moving was to add `Ref` to our spec override:

```json
    "AWS::ECS::Service.ServiceRegistry": {
      "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-service-serviceregistry.html",
      "Properties": {
        "Port": {
          "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-service-serviceregistry.html#cfn-ecs-service-serviceregistry-port",
          "PrimitiveType": "Integer",
          "Required": false,
          "UpdateType": "Mutable"
        },
        "RegistryArn": {
          "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-service-serviceregistry.html#cfn-ecs-service-serviceregistry-registryarn",
          "PrimitiveType": "String",
          "Required": false,
          "UpdateType": "Mutable"
        },
        "Ref": {
          "PrimitiveType": "String",
          "Required": false
        }
      }
```

and then meet in the middle with:

```
      ServiceRegistries:
        Fn::If:
          - UseServiceDiscovery
          - - RegistryArn:
                Fn::GetAtt: [ServiceDiscoveryRegistry, Arn]
          - - Ref: AWS::NoValue
```

I do believe that final template makes the most sense to appease the linter and CloudFormation. But without the spec override, we get:
```
E3002 Invalid Property Resources/Service/Properties/ServiceRegistries/0/Ref
cloudformation/shared-templates/fargate-service-alb.yaml:111:7
```

If I am doing something silly or missing something else, corrections are certainly welcome. If not, is there an easy way that `Ref: AWS::NoValue` could be respected as special and not cause an error when it is sitting in for another `Type`?

Thanks for this tool! I think it's going to greatly speed up our CloudFormation generation and testing. I really want to see it succeed. I am happy to provide any other context or input.



Discussion:
This is a bug.  Working on a fix for it now and should have something shortly.  We aren't properly handling the Ref AWS::NoValue which is messing this up.  This was an earlier rule that could use a little updating.  While we aren't where I want to be with conditions we are a lot closer and should have been catching this.  Thank you!  

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
@@ -82,35 +82,37 @@ def check_list_for_condition(self, text, prop, parenttype, resourcename, propspe
             for sub_key, sub_value in text[prop].items():
                 if sub_key in cfnlint.helpers.CONDITION_FUNCTIONS:
                     if len(sub_value) == 3:
-                        if isinstance(sub_value[1], list):
-                            for index, item in enumerate(sub_value[1]):
-                                arrproppath = path[:]
+                        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[:]
 
-                                arrproppath.append(index)
-                                matches.extend(self.propertycheck(
-                                    item, propspec['ItemType'],
-                                    parenttype, resourcename, arrproppath, False))
-                        else:
-                            message = 'Property {0} should be of type List for resource {1}'
-                            matches.append(
-                                RuleMatch(
-                                    path,
-                                    message.format(prop, resourcename)))
-
-                        if isinstance(sub_value[2], list):
-                            for index, item in enumerate(sub_value[2]):
-                                arrproppath = path[:]
-
-                                arrproppath.append(index)
-                                matches.extend(self.propertycheck(
-                                    item, propspec['ItemType'],
-                                    parenttype, resourcename, arrproppath, False))
-                        else:
-                            message = 'Property {0} should be of type List for resource {1} at {2}'
-                            matches.append(
-                                RuleMatch(
-                                    path + [2],
-                                    message.format(prop, resourcename, ('/'.join(str(x) for x in 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':
+                                            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 = '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)))


Test patch:
diff --git a/test/fixtures/templates/good/resource_properties.yaml b/test/fixtures/templates/good/resource_properties.yaml
--- a/test/fixtures/templates/good/resource_properties.yaml
+++ b/test/fixtures/templates/good/resource_properties.yaml
@@ -589,10 +589,7 @@ Resources:
           KeyType: HASH
         - AttributeName: !Ref SortKeyName
           KeyType: RANGE
-      - - AttributeName: !Ref PartitionKeyName
-          KeyType: HASH
-        - AttributeName: !Ref SortKeyName
-          KeyType: RANGE
+      - !Ref AWS::NoValue
   CustomResource1:
     Type: 'AWS::CloudFormation::CustomResource'
     Properties: