Repository: aws-cloudformation/cfn-lint

Issue:
Multiple 'str_node' object has no attribute 'items' errors with bad Outputs section
*cfn-lint version: (`cfn-lint --version`)*
0.8.1

*Description of issue.*
Multiple 'str_node' object has no attribute 'items' errors with bad Outputs section:

```
E0002 Unknown exception while processing rule E6001: 'str_node' object has no attribute 'items'
rds-cluster-template.yml:1:1

E0002 Unknown exception while processing rule E6005: 'str_node' object has no attribute 'items'
rds-cluster-template.yml:1:1

E0002 Unknown exception while processing rule E6012: 'str_node' object has no attribute 'items'
rds-cluster-template.yml:1:1

E0002 Unknown exception while processing rule E6004: 'str_node' object has no attribute 'items'
rds-cluster-template.yml:1:1

E0002 Unknown exception while processing rule E6002: 'str_node' object has no attribute 'items'
rds-cluster-template.yml:1:1
```

I recognize this template makes no sense, but we should probably do a better job of cacthing the problem.

Template:

```yaml
---
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  EC2:
    Type: "AWS::EC2::Instance"
    Properties:
      ImageId: "i-2395829568"
Outputs:
  PathToMasterStackFile
```


Discussion:

Resolving patch:
diff --git a/src/cfnlint/__init__.py b/src/cfnlint/__init__.py
--- a/src/cfnlint/__init__.py
+++ b/src/cfnlint/__init__.py
@@ -172,6 +172,22 @@ def is_rule_enabled(self, rule_id):
 
         return True
 
+    def run_check(self, check, filename, rule_id, *args):
+        """ Run a check """
+        try:
+            return check(*args)
+        except AttributeError as err:
+            LOGGER.debug('Attribute error while processing rule %s: %s', rule_id, str(err))
+            LOGGER.debug('Stack trace: %s', err, exc_info=True)
+            return []
+        except Exception as err:  # pylint: disable=W0703
+            if self.is_rule_enabled('E0002'):
+                message = 'Unknown exception while processing rule {}: {}'
+                return [
+                    cfnlint.Match(
+                        1, 1, 1, 1,
+                        filename, cfnlint.RuleError(), message.format(rule_id, str(err)))]
+
     def resource_property(self, filename, cfn, path, properties, resource_type, property_type):
         """Run loops in resource checks for embedded properties"""
         matches = []
@@ -182,17 +198,12 @@ def resource_property(self, filename, cfn, path, properties, resource_type, prop
             property_spec_name = '%s.%s' % (resource_type, property_type)
         if property_spec_name in property_spec:
             for rule in self.rules:
-                try:
-                    matches.extend(
-                        rule.matchall_resource_sub_properties(
-                            filename, cfn, properties, property_spec_name, path))
-                except Exception as err:  # pylint: disable=W0703
-                    if self.is_rule_enabled('E0002'):
-                        message = 'Unknown exception while processing rule {}: {}'
-                        matches.append(cfnlint.Match(
-                            1, 1,
-                            1, 1,
-                            filename, cfnlint.RuleError(), message.format(rule.id, str(err))))
+                matches.extend(
+                    self.run_check(
+                        rule.matchall_resource_sub_properties, filename, rule.id,
+                        filename, cfn, properties, property_spec_name, path
+                    )
+                )
 
             resource_spec_properties = property_spec.get(property_spec_name, {}).get('Properties')
             if isinstance(properties, dict):
@@ -271,32 +282,23 @@ def run(self, filename, cfn):
         """Run rules"""
         matches = []
         for rule in self.rules:
-            try:
-                matches.extend(rule.matchall(filename, cfn))
-            except Exception as err:  # pylint: disable=W0703
-                if self.is_rule_enabled('E0002'):
-                    message = 'Unknown exception while processing rule {}: {}'
-                    matches.append(cfnlint.Match(
-                        1, 1,
-                        1, 1,
-                        filename, cfnlint.RuleError(), message.format(rule.id, str(err))))
+            matches.extend(
+                self.run_check(
+                    rule.matchall, filename, rule.id, filename, cfn
+                )
+            )
 
         for resource_name, resource_attributes in cfn.get_resources().items():
             resource_type = resource_attributes.get('Type')
             resource_properties = resource_attributes.get('Properties', {})
             path = ['Resources', resource_name, 'Properties']
             for rule in self.rules:
-                try:
-                    matches.extend(
-                        rule.matchall_resource_properties(
-                            filename, cfn, resource_properties, resource_type, path))
-                except Exception as err:  # pylint: disable=W0703
-                    if self.is_rule_enabled('E0002'):
-                        message = 'Unknown exception while processing rule {}: {}'
-                        matches.append(cfnlint.Match(
-                            1, 1,
-                            1, 1,
-                            filename, cfnlint.RuleError(), message.format(rule.id, str(err))))
+                matches.extend(
+                    self.run_check(
+                        rule.matchall_resource_properties, filename, rule.id,
+                        filename, cfn, resource_properties, resource_type, path
+                    )
+                )
 
             matches.extend(
                 self.run_resource(
diff --git a/src/cfnlint/decode/node.py b/src/cfnlint/decode/node.py
--- a/src/cfnlint/decode/node.py
+++ b/src/cfnlint/decode/node.py
@@ -15,9 +15,12 @@
   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 """
 import sys
+import logging
 from copy import deepcopy
 import six
 
+LOGGER = logging.getLogger(__name__)
+
 
 def create_str_node_class(cls):
     """
diff --git a/src/cfnlint/rules/outputs/Configuration.py b/src/cfnlint/rules/outputs/Configuration.py
--- a/src/cfnlint/rules/outputs/Configuration.py
+++ b/src/cfnlint/rules/outputs/Configuration.py
@@ -40,13 +40,16 @@ def match(self, cfn):
 
         outputs = cfn.template.get('Outputs', {})
         if outputs:
-            for output_name, output_value in outputs.items():
-                for prop in output_value:
-                    if prop not in self.valid_keys:
-                        message = 'Output {0} has invalid property {1}'
-                        matches.append(RuleMatch(
-                            ['Outputs', output_name, prop],
-                            message.format(output_name, prop)
-                        ))
+            if isinstance(outputs, dict):
+                for output_name, output_value in outputs.items():
+                    for prop in output_value:
+                        if prop not in self.valid_keys:
+                            message = 'Output {0} has invalid property {1}'
+                            matches.append(RuleMatch(
+                                ['Outputs', output_name, prop],
+                                message.format(output_name, prop)
+                            ))
+            else:
+                matches.append(RuleMatch(['Outputs'], 'Outputs do not follow correct format.'))
 
         return matches


Test patch: