Repository: aws-cloudformation/cfn-lint

Issue:
E0002 Unknown exception while processing rule W1001
*cfn-lint version: (`cfn-lint --version`)* cfn-lint 0.13.1

*Description of issue.* I have a large template. Still working on creating a reproducible test case. I can send this template to you privately if you give me an email address.

This is what I see:

```
$ cfn-lint standards/region.cfn.yaml
E0002 Unknown exception while processing rule W1001: 'NoneType' object has no attribute 'test'
standards/region.cfn.yaml:1:1
```

When I re-run with `-d` logging, the output ends with this traceback:

```pytb
  File "/usr/local/lib/python3.7/site-packages/cfnlint/__init__.py", line 186, in run_check
    return check(*args)
  File "/usr/local/lib/python3.7/site-packages/cfnlint/__init__.py", line 77, in wrapper
    results = match_function(self, filename, cfn, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/cfnlint/__init__.py", line 106, in matchall
    return self.match(cfn)  # pylint: disable=E1102
  File "/usr/local/lib/python3.7/site-packages/cfnlint/rules/functions/RelationshipConditions.py", line 43, in match
    scenarios = cfn.is_resource_available(ref_obj, value)
  File "/usr/local/lib/python3.7/site-packages/cfnlint/__init__.py", line 911, in is_resource_available
    scenarios = self.conditions.get_scenarios(test_path_conditions.keys())
  File "/usr/local/lib/python3.7/site-packages/cfnlint/conditions.py", line 379, in get_scenarios
    r_condition[condition] = self.Conditions.get(condition).test(scenario)
  File "/usr/local/lib/python3.7/site-packages/cfnlint/conditions.py", line 115, in test
    if not a.test(scenarios):
  File "/usr/local/lib/python3.7/site-packages/cfnlint/conditions.py", line 127, in test
    return self.Equals.test(scenarios)
AttributeError: 'NoneType' object has no attribute 'test'
```

Adding some debugging before line 127 in `conditions.py` shows that `self.Equals` is None when this happens.


Discussion:
We've got some new conditional parsing logic that really tried to dig in on the different scenarios. Sounds like you've found the edges of it.  We're evaluating the efficacy down each of the different paths within the conditions of the template. Sounds like there's something we're not catching in your Fn:If logic.
@alexjurkiewicz At the last I would just need your conditions block.  I'll do some digging into how that could happen. 
I've sent you the full template via aws developers slack

On Wed, 6 Feb 2019 at 01:24 kddejong <notifications@github.com> wrote:

> @alexjurkiewicz <https://github.com/alexjurkiewicz> At the last I would
> just need your conditions block. I'll do some digging into how that could
> happen.
>
> —
> You are receiving this because you were mentioned.
>
>
> Reply to this email directly, view it on GitHub
> <https://github.com/awslabs/cfn-python-lint/issues/643#issuecomment-460655708>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AAXKdZs3Lhk46pkcwcUOMxrFstw-G8z-ks5vKZQagaJpZM4aih3s>
> .
>

Thanks.  I think I got this figured out.  Should have a fix in shortly.

Resolving patch:
diff --git a/src/cfnlint/conditions.py b/src/cfnlint/conditions.py
--- a/src/cfnlint/conditions.py
+++ b/src/cfnlint/conditions.py
@@ -96,17 +96,24 @@ class Condition(object):
     Equals = None
     Influenced_Equals = None
 
-    def __init__(self, template, name):
+    def __init__(self, template, name=None, sub_condition=None):
         self.And = []
         self.Or = []
         self.Not = []
         self.Influenced_Equals = {}
-        value = template.get('Conditions', {}).get(name, {})
-        try:
-            self.process_condition(template, value)
-        except ConditionParseError:
-            LOGGER.debug('Error parsing condition: %s', name)
-            self.Equal = None
+        if name is not None:
+            value = template.get('Conditions', {}).get(name, {})
+            try:
+                self.process_condition(template, value)
+            except ConditionParseError:
+                LOGGER.debug('Error parsing condition: %s', name)
+                self.Equals = None
+        elif sub_condition is not None:
+            try:
+                self.process_condition(template, sub_condition)
+            except ConditionParseError:
+                LOGGER.debug('Error parsing condition: %s', name)
+                self.Equals = None
 
     def test(self, scenarios):
         """ Test a condition based on a scenario """
@@ -123,7 +130,6 @@ def test(self, scenarios):
         if self.Not:
             for n in self.Not:
                 return not n.test(scenarios)
-
         return self.Equals.test(scenarios)
 
     def process_influenced_equal(self, equal):
@@ -172,7 +178,11 @@ def process_function(self, template, values):
             if isinstance(value, dict):
                 if len(value) == 1:
                     for k, v in value.items():
-                        if k == 'Condition':
+                        if k == cfnlint.helpers.FUNCTION_EQUALS:
+                            equal = Equals(v)
+                            self.process_influenced_equal(equal)
+                            results.append(equal)
+                        elif k == 'Condition':
                             condition = Condition(template, v)
                             results.append(condition)
                             for i_e_k, i_e_v in condition.Influenced_Equals.items():
@@ -180,10 +190,14 @@ def process_function(self, template, values):
                                     self.Influenced_Equals[i_e_k] = set()
                                 for s_v in i_e_v:
                                     self.Influenced_Equals[i_e_k].add(s_v)
-                        elif k == cfnlint.helpers.FUNCTION_EQUALS:
-                            equal = Equals(v)
-                            self.process_influenced_equal(equal)
-                            results.append(equal)
+                        else:
+                            condition = Condition(template, None, value)
+                            results.append(condition)
+                            for i_e_k, i_e_v in condition.Influenced_Equals.items():
+                                if not self.Influenced_Equals.get(i_e_k):
+                                    self.Influenced_Equals[i_e_k] = set()
+                                for s_v in i_e_v:
+                                    self.Influenced_Equals[i_e_k].add(s_v)
 
         return results
 


Test patch:
diff --git a/test/module/conditions/test_condition.py b/test/module/conditions/test_condition.py
--- a/test/module/conditions/test_condition.py
+++ b/test/module/conditions/test_condition.py
@@ -216,3 +216,47 @@ def test_bad_format_condition_bad_equals_size(self):
         self.assertEqual(result.Not, [])  # No Not
         self.assertIsNone(result.Equals)
         self.assertEqual(result.Influenced_Equals, {})
+
+    def test_nested_conditions(self):
+        """ Test getting a condition setup """
+        template = {
+            'Conditions': {
+                'condition1': {
+                    'Fn::Equals': [{'Ref': 'AWS::Region'}, 'us-east-1']
+                },
+                'condition2': {
+                    'Fn::Equals': [{'Ref': 'myEnvironment'}, 'prod']
+                },
+                'orCondition': {'Fn::Or': [
+                    {'Fn::And': [{'Condition': 'condition1'}, {'Condition': 'condition2'}]},
+                    {'Fn::Or': [
+                        {'Fn::Equals': [{'Ref': 'AWS::Region'}, 'us-west-2']},
+                        {'Fn::Equals': [{'Ref': 'AWS::Region'}, 'eu-north-1']},
+                    ]}
+                ]}
+            }
+        }
+        result = conditions.Condition(template, 'orCondition')
+        self.assertEqual(
+            result.Influenced_Equals,
+            {
+                '36305712594f5e76fbcbbe2f82cd3f850f6018e9': {'us-east-1', 'us-west-2', 'eu-north-1'},
+                'd60d12101638186a2c742b772ec8e69b3e2382b9': {'prod'}
+            }
+        )
+        self.assertTrue(
+            result.test({
+                '36305712594f5e76fbcbbe2f82cd3f850f6018e9': 'us-east-1',
+                'd60d12101638186a2c742b772ec8e69b3e2382b9': 'prod'}))
+        self.assertFalse(
+            result.test({
+                '36305712594f5e76fbcbbe2f82cd3f850f6018e9': 'us-east-1',
+                'd60d12101638186a2c742b772ec8e69b3e2382b9': 'dev'}))
+        self.assertFalse(
+            result.test({
+                '36305712594f5e76fbcbbe2f82cd3f850f6018e9': 'us-west-1',
+                'd60d12101638186a2c742b772ec8e69b3e2382b9': 'prod'}))
+        self.assertTrue(
+            result.test({
+                '36305712594f5e76fbcbbe2f82cd3f850f6018e9': 'us-west-2',
+                'd60d12101638186a2c742b772ec8e69b3e2382b9': 'dev'}))
diff --git a/test/module/conditions/test_conditions.py b/test/module/conditions/test_conditions.py
--- a/test/module/conditions/test_conditions.py
+++ b/test/module/conditions/test_conditions.py
@@ -59,7 +59,6 @@ def test_success_size_of_conditions(self):
         """Test success run"""
         self.assertEqual(len(self.conditions.Conditions), 9)
         self.assertEqual(len(self.conditions.Parameters), 2)
-        print(self.conditions.Parameters)
         self.assertListEqual(self.conditions.Parameters['55caa18684cddafa866bdb947fb31ea563b2ea73'], ['None', 'Single NAT', 'High Availability'])
 
     def test_success_is_production(self):