Repository: aws-cloudformation/cfn-lint

Issue:
Crash with malformed templates not containing YAML maps
*cfn-lint version: (`cfn-lint --version`)* 0.6.1

*Description of issue.*

```
$ echo malformed > test.yml
$ cfn-lint test.yml
Traceback (most recent call last):
  File "/Users/myusername/.virtualenvs/myvirtualenv/bin/cfn-lint", line 11, in <module>
    sys.exit(main())
  File "/Users/myusername/.virtualenvs/myvirtualenv/lib/python3.7/site-packages/cfnlint/__main__.py", line 27, in main
    (args, filename, template, rules, fmt, formatter) = cfnlint.core.get_template_args_rules(sys.argv[1:])
  File "/Users/myusername/.virtualenvs/myvirtualenv/lib/python3.7/site-packages/cfnlint/core.py", line 222, in get_template_args_rules
    for section, values in get_default_args(template).items():
  File "/Users/myusername/.virtualenvs/myvirtualenv/lib/python3.7/site-packages/cfnlint/core.py", line 258, in get_default_args
    if isinstance(configs, dict):
UnboundLocalError: local variable 'configs' referenced before assignment
```

This doesn't work if I instead do `echo '{}' > test.yml`, but does with `echo 1 > test.yml` and `echo [] > test.yml`, so it seems to be a problem with the type of the object in the template.



Discussion:
It looks like the string in the file is being decoded as valid and passed into the config parser to start looking at the (non-existent) meta-data:

https://github.com/awslabs/cfn-python-lint/blob/38b0f23a319eddd9b94dc9e2649c64b96775214e/src/cfnlint/core.py#L223-L225

https://github.com/awslabs/cfn-python-lint/blob/38b0f23a319eddd9b94dc9e2649c64b96775214e/src/cfnlint/core.py#L223-L225

We'll need better error trapping when we attempt to extract values from the config section

Resolving patch:
diff --git a/src/cfnlint/core.py b/src/cfnlint/core.py
--- a/src/cfnlint/core.py
+++ b/src/cfnlint/core.py
@@ -256,23 +256,23 @@ def get_default_args(template):
     if isinstance(template, dict):
         configs = template.get('Metadata', {}).get('cfn-lint', {}).get('config', {})
 
-    if isinstance(configs, dict):
-        for config_name, config_value in configs.items():
-            if config_name == 'ignore_checks':
-                if isinstance(config_value, list):
-                    defaults['ignore_checks'] = config_value
-            if config_name == 'regions':
-                if isinstance(config_value, list):
-                    defaults['regions'] = config_value
-            if config_name == 'append_rules':
-                if isinstance(config_value, list):
-                    defaults['override_spec'] = config_value
-            if config_name == 'override_spec':
-                if isinstance(config_value, (six.string_types, six.text_type)):
-                    defaults['override_spec'] = config_value
-            if config_name == 'ignore_bad_template':
-                if isinstance(config_value, bool):
-                    defaults['ignore_bad_template'] = config_value
+        if isinstance(configs, dict):
+            for config_name, config_value in configs.items():
+                if config_name == 'ignore_checks':
+                    if isinstance(config_value, list):
+                        defaults['ignore_checks'] = config_value
+                if config_name == 'regions':
+                    if isinstance(config_value, list):
+                        defaults['regions'] = config_value
+                if config_name == 'append_rules':
+                    if isinstance(config_value, list):
+                        defaults['override_spec'] = config_value
+                if config_name == 'override_spec':
+                    if isinstance(config_value, (six.string_types, six.text_type)):
+                        defaults['override_spec'] = config_value
+                if config_name == 'ignore_bad_template':
+                    if isinstance(config_value, bool):
+                        defaults['ignore_bad_template'] = config_value
 
     return defaults
 
diff --git a/src/cfnlint/decode/__init__.py b/src/cfnlint/decode/__init__.py
--- a/src/cfnlint/decode/__init__.py
+++ b/src/cfnlint/decode/__init__.py
@@ -74,6 +74,9 @@ def decode(filename, ignore_bad_template):
         else:
             matches = [create_match_yaml_parser_error(err, filename)]
 
+    if not isinstance(template, dict):
+        # Template isn't a dict which means nearly nothing will work
+        matches = [cfnlint.Match(1, 1, 1, 1, filename, cfnlint.ParseError(), message='Template needs to be an object.')]
     return (template, matches)
 
 


Test patch:
diff --git a/test/fixtures/templates/bad/string.yaml b/test/fixtures/templates/bad/string.yaml
new file mode 100644
--- /dev/null
+++ b/test/fixtures/templates/bad/string.yaml
@@ -0,0 +1 @@
+BadTemplate
diff --git a/test/module/test_string_template.py b/test/module/test_string_template.py
new file mode 100644
--- /dev/null
+++ b/test/module/test_string_template.py
@@ -0,0 +1,36 @@
+"""
+  Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+  Permission is hereby granted, free of charge, to any person obtaining a copy of this
+  software and associated documentation files (the "Software"), to deal in the Software
+  without restriction, including without limitation the rights to use, copy, modify,
+  merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+  permit persons to whom the Software is furnished to do so.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+"""
+import json
+from cfnlint import Template, RulesCollection
+from cfnlint.core import DEFAULT_RULESDIR  # pylint: disable=E0401
+import cfnlint.decode.cfn_yaml  # pylint: disable=E0401
+import cfnlint.decode.cfn_json  # pylint: disable=E0401
+from testlib.testcase import BaseTestCase
+
+
+class TestNonObjectTemplate(BaseTestCase):
+    """Test Duplicates Parsing """
+    def setUp(self):
+        """ SetUp template object"""
+
+    def test_fail_yaml_run(self):
+        """Test failure run"""
+
+        filename = 'fixtures/templates/bad/string.yaml'
+
+        _, matches = cfnlint.decode.decode(filename, True)
+        assert len(matches) == 1