Repository: aws-cloudformation/cfn-lint

Issue:
Need to validate that resource type is a string
*cfn-lint version: (`cfn-lint --version`)* 0.30.1

*Description of issue.*

![image](https://user-images.githubusercontent.com/5374887/81467963-2c6f5280-9191-11ea-800d-61a8a8445fce.png)

![image](https://user-images.githubusercontent.com/5374887/81468015-97208e00-9191-11ea-8933-6ba116b64f83.png)



Discussion:
https://github.com/aws-cloudformation/cfn-python-lint/blob/b788cc9bd3d49ed20d5f2e58602755a0ef37f52c/src/cfnlint/rules/resources/ServerlessTransform.py#L36

https://github.com/aws-cloudformation/cfn-python-lint/blob/2d7ed333e5514896c07a6514eb8a84907b536f29/src/cfnlint/rules/resources/Configuration.py#L63

https://github.com/aws-cloudformation/cfn-python-lint/blob/2d7ed333e5514896c07a6514eb8a84907b536f29/src/cfnlint/rules/resources/Configuration.py#L96

`startswith` getting called indirectly in [`E1010 `](https://github.com/aws-cloudformation/cfn-python-lint/blob/master/src/cfnlint/rules/functions/GetAtt.py)

---

bit of an odd use case I'd rather not explicitly check in every rule though, maybe there's somewhere earlier like [`rules/__init__.py`](https://github.com/aws-cloudformation/cfn-python-lint/blob/master/src/cfnlint/rules/__init__.py) this could be caught?
The code lines you pointed out are all using either `cfn.template.get('Resources', {}).items()` or `cfn.get_resources().items()`. If we can change all instances of the former to the latter, maybe we can make a change within [`cfn.get_resources()`](https://github.com/aws-cloudformation/cfn-python-lint/blob/52c02962e90354b265fec839e71e1d038e899fd4/src/cfnlint/template.py#L63):

```python
results = {}
for k, v in resources.items():
    if isinstance(v, dict):
        existing_resource_type = v.get('Type', None)
        if not isinstance(existing_resource_type, six.string_types):
            raise ValueError("A resource type has to be a string")
        if (existing_resource_type in resource_type) or (not resource_type and existing_resource_type is not None):
            results[k] = v

return results
```
@miparnisari I completely agree catching it in `get_resources()`. I tried to make the functions like `cfn.get_resources()` return resources only if they are valid.  No value in returning something if the Type is a number or not even specified.  It should be a safe way to get back "valid" resources that any rule or even other core functionality can use.


Resolving patch:
diff --git a/src/cfnlint/rules/resources/ServerlessTransform.py b/src/cfnlint/rules/resources/ServerlessTransform.py
--- a/src/cfnlint/rules/resources/ServerlessTransform.py
+++ b/src/cfnlint/rules/resources/ServerlessTransform.py
@@ -2,6 +2,7 @@
 Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 SPDX-License-Identifier: MIT-0
 """
+import six
 from cfnlint.rules import CloudFormationLintRule
 from cfnlint.rules import RuleMatch
 
@@ -31,10 +32,11 @@ def match(self, cfn):
 
         for resource_name, resource_values in cfn.get_resources().items():
             resource_type = resource_values['Type']
-            if resource_type.startswith('AWS::Serverless::'):
-                message = 'Serverless Transform required for Type {0} for resource {1}'
-                matches.append(
-                    RuleMatch(['Transform'], message.format(resource_type, resource_name))
-                )
-                break
+            if isinstance(resource_type, six.string_types):
+                if resource_type.startswith('AWS::Serverless::'):
+                    message = 'Serverless Transform required for Type {0} for resource {1}'
+                    matches.append(
+                        RuleMatch(['Transform'], message.format(resource_type, resource_name))
+                    )
+                    break
         return matches
diff --git a/src/cfnlint/template.py b/src/cfnlint/template.py
--- a/src/cfnlint/template.py
+++ b/src/cfnlint/template.py
@@ -166,20 +166,21 @@ def get_valid_getatts(self):
         for name, value in resources.items():
             if 'Type' in value:
                 valtype = value['Type']
-                if valtype.startswith(astrik_string_types):
-                    LOGGER.debug('Cant build an appropriate getatt list from %s', valtype)
-                    results[name] = {'*': {'PrimitiveItemType': 'String'}}
-                elif valtype.startswith(astrik_unknown_types):
-                    LOGGER.debug('Cant build an appropriate getatt list from %s', valtype)
-                    results[name] = {'*': {}}
-                else:
-                    if value['Type'] in resourcetypes:
-                        if 'Attributes' in resourcetypes[valtype]:
-                            results[name] = {}
-                            for attname, attvalue in resourcetypes[valtype]['Attributes'].items():
-                                element = {}
-                                element.update(attvalue)
-                                results[name][attname] = element
+                if isinstance(valtype, six.string_types):
+                    if valtype.startswith(astrik_string_types):
+                        LOGGER.debug('Cant build an appropriate getatt list from %s', valtype)
+                        results[name] = {'*': {'PrimitiveItemType': 'String'}}
+                    elif valtype.startswith(astrik_unknown_types):
+                        LOGGER.debug('Cant build an appropriate getatt list from %s', valtype)
+                        results[name] = {'*': {}}
+                    else:
+                        if value['Type'] in resourcetypes:
+                            if 'Attributes' in resourcetypes[valtype]:
+                                results[name] = {}
+                                for attname, attvalue in resourcetypes[valtype]['Attributes'].items():
+                                    element = {}
+                                    element.update(attvalue)
+                                    results[name][attname] = element
 
         return results
 


Test patch: