Repository: aws-cloudformation/cfn-lint

Issue:
E2507 error when using using policy statements for serverless functions
*cfn-lint version: 0.8.2* (and doesn't happen with earlier versions)

The template below produces the following error when processing it with `cfn-lint`:
```
E0002 Unknown exception while processing rule E2507: 'str' object has no attribute 'start_mark'
function_with_policy_templates.yaml:1:1
````

```yaml
AWSTemplateFormatVersion: '2010-09-09'

Transform: 'AWS::Serverless-2016-10-31'

Resources:
  Test:
    Type: 'AWS::Serverless::Function'
    Properties:
      CodeUri: ./
      Handler: hello.handler
      Runtime: python3.6
      Policies:
        - Statement:
          - Action: [ 'dynamodb:*' ]
            Effect: Allow
            Resource: '*'
```

That issue goes away when removing the `Transform: 'AWS::Serverless-2016-10-31'`, but that's of course no fix, as that would prevent transformation of the `AWS::Serverless::Function`.


Discussion:

Resolving patch:
diff --git a/src/cfnlint/helpers.py b/src/cfnlint/helpers.py
--- a/src/cfnlint/helpers.py
+++ b/src/cfnlint/helpers.py
@@ -23,7 +23,7 @@
 import re
 import inspect
 import pkg_resources
-from cfnlint.decode.node import dict_node, list_node
+from cfnlint.decode.node import dict_node, list_node, str_node
 
 LOGGER = logging.getLogger(__name__)
 
@@ -216,13 +216,20 @@ def convert_dict(template, start_mark=(0, 0), end_mark=(0, 0)):
     if isinstance(template, dict):
         if not isinstance(template, dict_node):
             template = dict_node(template, start_mark, end_mark)
-        for k, v in template.items():
-            template[k] = convert_dict(v)
+        for k, v in template.copy().items():
+            k_start_mark = start_mark
+            k_end_mark = end_mark
+            if isinstance(k, str_node):
+                k_start_mark = k.start_mark
+                k_end_mark = k.end_mark
+            new_k = str_node(k, k_start_mark, k_end_mark)
+            del template[k]
+            template[new_k] = convert_dict(v, k_start_mark, k_end_mark)
     elif isinstance(template, list):
         if not isinstance(template, list_node):
             template = list_node(template, start_mark, end_mark)
         for i, v in enumerate(template):
-            template[i] = convert_dict(v)
+            template[i] = convert_dict(v, start_mark, end_mark)
 
     return template
 


Test patch:
diff --git a/test/module/helpers/__init__.py b/test/module/helpers/__init__.py
new file mode 100644
--- /dev/null
+++ b/test/module/helpers/__init__.py
@@ -0,0 +1,16 @@
+"""
+  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.
+"""
diff --git a/test/module/helpers/test_convert_dict.py b/test/module/helpers/test_convert_dict.py
new file mode 100644
--- /dev/null
+++ b/test/module/helpers/test_convert_dict.py
@@ -0,0 +1,45 @@
+"""
+  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.helpers import convert_dict
+from cfnlint.decode.node import dict_node, list_node, str_node
+from cfnlint import Runner
+import cfnlint.decode.cfn_yaml
+from testlib.testcase import BaseTestCase
+
+
+class TestConvertDict(BaseTestCase):
+    """Test Converting regular dicts into CFN Dicts """
+
+    def test_success_run(self):
+        """Test success run"""
+        obj = {
+            'Key': {
+                'SubKey': 'Value'
+            },
+            'List': [
+                {'SubKey': 'AnotherValue'}
+            ]
+        }
+
+        results = convert_dict(obj)
+        self.assertTrue(isinstance(results, dict_node))
+        self.assertTrue(isinstance(results.get('Key'), dict_node))
+        self.assertTrue(isinstance(results.get('List')[0], dict_node))
+        self.assertTrue(isinstance(results.get('List'), list_node))
+        for k, _ in results.items():
+            self.assertTrue(isinstance(k, str_node))