We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
AWS::ServiceCatalog::CloudFormationProvisionedProduct.Outputs
In July 2020, support for retrieving the outputs of provisioned Service Catalog products [was added to CloudFormation](https://aws.amazon.com/blogs/mt/how-to-launch-secure-and-governed-aws-resources-with-aws-cloudformation-and-aws-service-catalog/).

Given the following example template:

```yaml
AWSTemplateFormatVersion: '2010-09-09'

Resources:
  ProvisionedProduct:
    Type: AWS::ServiceCatalog::CloudFormationProvisionedProduct
    Properties:
      ProductName: example
      ProvisioningArtifactName: v1

Outputs:
  OutputValue:
    Value: !GetAtt ProvisionedProduct.Outputs.Example

  OutputSub:
    Value: !Sub '${ProvisionedProduct.Outputs.Example}-example'
```

The following linting errors are output when using cfn-lint 0.35.0:

```
E1010 Invalid GetAtt ProvisionedProduct.Outputs.Example for resource OutputValue
example.yml:12:5

E1019 Parameter ProvisionedProduct.Outputs.Example for Fn::Sub not found at Outputs/OutputSub/Value/Fn::Sub
example.yml:15:5
```

Access to these outputs using `!GetAtt` is documented in the announcement of this functionality (e.g. `!GetAtt ApacheALB.Outputs.ALBTarget`). Additionally the example template given above works correctly when deployed.

`Output` is documented in the [AWS::ServiceCatalog::CloudFormationProvisionedProduct return values](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-servicecatalog-cloudformationprovisionedproduct.html#aws-resource-servicecatalog-cloudformationprovisionedproduct-return-values) and appears to already be defined within cfn-lint CloudSpecs as a `Map` type.

I believe that this a false positive and am not aware of any alternative syntax to access these outputs (e.g. `!FindInMap` doesn't appear to be usable in this instance).

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/cfnlint/rules/functions/GetAtt.py`
Content:
```
1 """
2 Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 SPDX-License-Identifier: MIT-0
4 """
5 import six
6 from cfnlint.rules import CloudFormationLintRule
7 from cfnlint.rules import RuleMatch
8 import cfnlint.helpers
9 
10 
11 class GetAtt(CloudFormationLintRule):
12     """Check if GetAtt values are correct"""
13     id = 'E1010'
14     shortdesc = 'GetAtt validation of parameters'
15     description = 'Validates that GetAtt parameters are to valid resources and properties of those resources'
16     source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html'
17     tags = ['functions', 'getatt']
18 
19     def __init__(self):
20         """Init """
21         super(GetAtt, self).__init__()
22         self.propertytypes = []
23         self.resourcetypes = []
24 
25     def initialize(self, cfn):
26         resourcespecs = cfnlint.helpers.RESOURCE_SPECS[cfn.regions[0]]
27         self.resourcetypes = resourcespecs['ResourceTypes']
28         self.propertytypes = resourcespecs['PropertyTypes']
29 
30     def match(self, cfn):
31         matches = []
32 
33         getatts = cfn.search_deep_keys('Fn::GetAtt')
34         valid_getatts = cfn.get_valid_getatts()
35 
36         valid_attribute_functions = ['Ref']
37 
38         for getatt in getatts:
39             if len(getatt[-1]) < 2:
40                 message = 'Invalid GetAtt for {0}'
41                 matches.append(RuleMatch(getatt, message.format('/'.join(map(str, getatt[:-1])))))
42                 continue
43             if isinstance(getatt[-1], six.string_types):
44                 resname, restype = getatt[-1].split('.', 1)
45             else:
46                 resname = None
47                 restype = None
48                 if isinstance(getatt[-1][1], six.string_types):
49                     resname = getatt[-1][0]
50                     restype = '.'.join(getatt[-1][1:])
51                 elif isinstance(getatt[-1][1], dict):
52                     # You can ref the secondary part of a getatt
53 
54                     resname = getatt[-1][0]
55                     restype = getatt[-1][1]
56                     if len(restype) == 1:
57                         for k in restype.keys():
58                             if k not in valid_attribute_functions:
59                                 message = 'GetAtt only supports functions "{0}" for attributes at {1}'
60                                 matches.append(
61                                     RuleMatch(
62                                         getatt,
63                                         message.format(
64                                             ', '.join(map(str, valid_attribute_functions)),
65                                             '/'.join(map(str, getatt[:-1])))))
66                     else:
67                         message = 'Invalid GetAtt structure {0} at {1}'
68                         matches.append(RuleMatch(getatt, message.format(
69                             getatt[-1], '/'.join(map(str, getatt[:-1])))))
70 
71                     # setting restype to None as we can't validate that anymore
72                     restype = None
73                 else:
74                     message = 'Invalid GetAtt structure {0} at {1}'
75                     matches.append(RuleMatch(getatt, message.format(
76                         getatt[-1], '/'.join(map(str, getatt[:-1])))))
77 
78             # only check resname if its set.  if it isn't it is because of bad structure
79             # and an error is already provided
80             if resname:
81                 if resname in valid_getatts:
82                     if restype is not None:
83                         if restype not in valid_getatts[resname] and '*' not in valid_getatts[resname]:
84                             message = 'Invalid GetAtt {0}.{1} for resource {2}'
85                             matches.append(RuleMatch(
86                                 getatt[:-1], message.format(resname, restype, getatt[1])))
87                 else:
88                     message = 'Invalid GetAtt {0}.{1} for resource {2}'
89                     matches.append(RuleMatch(getatt, message.format(resname, restype, getatt[1])))
90 
91         return matches
92
```

Path: `src/cfnlint/rules/functions/Sub.py`
Content:
```
1 """
2 Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 SPDX-License-Identifier: MIT-0
4 """
5 import six
6 from cfnlint.helpers import PSEUDOPARAMS, VALID_PARAMETER_TYPES_LIST
7 from cfnlint.rules import CloudFormationLintRule
8 from cfnlint.rules import RuleMatch
9 
10 
11 class Sub(CloudFormationLintRule):
12     """Check if Sub values are correct"""
13     id = 'E1019'
14     shortdesc = 'Sub validation of parameters'
15     description = 'Making sure the sub function is properly configured'
16     source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html'
17     tags = ['functions', 'sub']
18 
19     def _test_string(self, cfn, sub_string, parameters, tree):
20         """Test if a string has appropriate parameters"""
21 
22         matches = []
23         string_params = cfn.get_sub_parameters(sub_string)
24 
25         for string_param in string_params:
26             if isinstance(string_param, (six.string_types)):
27                 matches.extend(self._test_parameter(string_param, cfn, parameters, tree))
28 
29         return matches
30 
31     def _get_parameters(self, cfn):
32         """Get all Parameter Names"""
33         results = {}
34         parameters = cfn.template.get('Parameters', {})
35         if isinstance(parameters, dict):
36             for param_name, param_values in parameters.items():
37                 # This rule isn't here to check the Types but we need
38                 # something valid if it doesn't exist
39                 results[param_name] = param_values.get('Type', 'String')
40 
41         return results
42 
43     def _test_parameters(self, parameters, cfn, tree):
44         """Check parameters for appropriate configuration"""
45 
46         supported_functions = [
47             'Fn::Base64',
48             'Fn::FindInMap',
49             'Fn::GetAZs',
50             'Fn::GetAtt',
51             'Fn::If',
52             'Fn::ImportValue',
53             'Fn::Join',
54             'Fn::Select',
55             'Fn::Sub',
56             'Ref',
57         ]
58 
59         matches = []
60         for parameter_name, parameter_value_obj in parameters.items():
61             param_tree = tree[:] + [parameter_name]
62             if isinstance(parameter_value_obj, dict):
63                 if len(parameter_value_obj) == 1:
64                     for key, value in parameter_value_obj.items():
65                         if key not in supported_functions:
66                             message = 'Sub parameter should use a valid function for {0}'
67                             matches.append(RuleMatch(
68                                 param_tree, message.format('/'.join(map(str, tree)))))
69                         elif key in ['Ref']:
70                             matches.extend(self._test_parameter(value, cfn, {}, tree))
71                         elif key in ['Fn::GetAtt']:
72                             if isinstance(value, list):
73                                 # Only test this if all the items are a string
74                                 if_all_strings = True
75                                 for v in value:
76                                     if not isinstance(v, six.string_types):
77                                         # skip things got too complex
78                                         if_all_strings = False
79                                 if if_all_strings:
80                                     matches.extend(self._test_parameter(
81                                         '.'.join(value), cfn, {}, tree))
82                             elif isinstance(value, six.string_types):
83                                 matches.extend(self._test_parameter(value, cfn, {}, tree))
84                 else:
85                     message = 'Sub parameter should be an object of 1 for {0}'
86                     matches.append(RuleMatch(
87                         param_tree, message.format('/'.join(map(str, tree)))))
88             elif not isinstance(parameter_value_obj, six.string_types):
89                 message = 'Sub parameter should be an object of 1 or string for {0}'
90                 matches.append(RuleMatch(
91                     param_tree, message.format('/'.join(map(str, tree)))))
92 
93         return matches
94 
95     def _test_parameter(self, parameter, cfn, parameters, tree):
96         """ Test a parameter """
97 
98         matches = []
99         get_atts = cfn.get_valid_getatts()
100 
101         valid_params = list(PSEUDOPARAMS)
102         valid_params.extend(cfn.get_resource_names())
103         template_parameters = self._get_parameters(cfn)
104 
105         for key, _ in parameters.items():
106             valid_params.append(key)
107 
108         if parameter not in valid_params:
109             found = False
110             if parameter in template_parameters:
111                 found = True
112                 if template_parameters.get(parameter) in VALID_PARAMETER_TYPES_LIST:
113                     message = 'Fn::Sub cannot use list {0} at {1}'
114                     matches.append(RuleMatch(
115                         tree, message.format(parameter, '/'.join(map(str, tree)))))
116             for resource, attributes in get_atts.items():
117                 for attribute_name, attribute_values in attributes.items():
118                     if resource == parameter.split('.')[0] and attribute_name == '*':
119                         if attribute_values.get('Type') == 'List':
120                             message = 'Fn::Sub cannot use list {0} at {1}'
121                             matches.append(RuleMatch(
122                                 tree, message.format(parameter, '/'.join(map(str, tree)))))
123                         found = True
124                     elif (resource == parameter.split('.')[0] and
125                           attribute_name == '.'.join(parameter.split('.')[1:])):
126                         if attribute_values.get('Type') == 'List':
127                             message = 'Fn::Sub cannot use list {0} at {1}'
128                             matches.append(RuleMatch(
129                                 tree, message.format(parameter, '/'.join(map(str, tree)))))
130                         found = True
131             if not found:
132                 message = 'Parameter {0} for Fn::Sub not found at {1}'
133                 matches.append(RuleMatch(
134                     tree, message.format(parameter, '/'.join(map(str, tree)))))
135 
136         return matches
137 
138     def match(self, cfn):
139         matches = []
140 
141         sub_objs = cfn.search_deep_keys('Fn::Sub')
142 
143         for sub_obj in sub_objs:
144             sub_value_obj = sub_obj[-1]
145             tree = sub_obj[:-1]
146             if isinstance(sub_value_obj, six.string_types):
147                 matches.extend(self._test_string(cfn, sub_value_obj, {}, tree))
148             elif isinstance(sub_value_obj, list):
149                 if len(sub_value_obj) == 2:
150                     sub_string = sub_value_obj[0]
151                     parameters = sub_value_obj[1]
152                     if not isinstance(sub_string, six.string_types):
153                         message = 'Subs first element should be of type string for {0}'
154                         matches.append(RuleMatch(
155                             tree + [0], message.format('/'.join(map(str, tree)))))
156                     if not isinstance(parameters, dict):
157                         message = 'Subs second element should be an object for {0}'
158                         matches.append(RuleMatch(
159                             tree + [1], message.format('/'.join(map(str, tree)))))
160                     else:
161                         matches.extend(self._test_string(cfn, sub_string, parameters, tree + [0]))
162                         matches.extend(self._test_parameters(parameters, cfn, tree))
163                 else:
164                     message = 'Sub should be an array of 2 for {0}'
165                     matches.append(RuleMatch(
166                         tree, message.format('/'.join(map(str, tree)))))
167             elif isinstance(sub_value_obj, dict):
168                 if len(sub_value_obj) == 1:
169                     for key, _ in sub_value_obj.items():
170                         if not key == 'Fn::Transform':
171                             message = 'Sub should be a string or array of 2 items for {0}'
172                             matches.append(RuleMatch(
173                                 tree, message.format('/'.join(map(str, tree)))))
174                 else:
175                     message = 'Sub should be a string or array of 2 items for {0}'
176                     matches.append(RuleMatch(
177                         tree, message.format('/'.join(map(str, tree)))))
178             else:
179                 message = 'Sub should be a string or array of 2 items for {0}'
180                 matches.append(RuleMatch(
181                     tree, message.format('/'.join(map(str, tree)))))
182 
183         return matches
184
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/cfnlint/rules/functions/GetAtt.py b/src/cfnlint/rules/functions/GetAtt.py
--- a/src/cfnlint/rules/functions/GetAtt.py
+++ b/src/cfnlint/rules/functions/GetAtt.py
@@ -80,6 +80,15 @@
             if resname:
                 if resname in valid_getatts:
                     if restype is not None:
+                        # Check for maps
+                        restypeparts = restype.split('.')
+                        if restypeparts[0] in valid_getatts[resname] and len(restypeparts) >= 2:
+                            if valid_getatts[resname][restypeparts[0]].get('Type') != 'Map':
+                                message = 'Invalid GetAtt {0}.{1} for resource {2}'
+                                matches.append(RuleMatch(
+                                    getatt[:-1], message.format(resname, restype, getatt[1])))
+                            else:
+                                continue
                         if restype not in valid_getatts[resname] and '*' not in valid_getatts[resname]:
                             message = 'Invalid GetAtt {0}.{1} for resource {2}'
                             matches.append(RuleMatch(
diff --git a/src/cfnlint/rules/functions/Sub.py b/src/cfnlint/rules/functions/Sub.py
--- a/src/cfnlint/rules/functions/Sub.py
+++ b/src/cfnlint/rules/functions/Sub.py
@@ -115,19 +115,19 @@
                         tree, message.format(parameter, '/'.join(map(str, tree)))))
             for resource, attributes in get_atts.items():
                 for attribute_name, attribute_values in attributes.items():
-                    if resource == parameter.split('.')[0] and attribute_name == '*':
-                        if attribute_values.get('Type') == 'List':
-                            message = 'Fn::Sub cannot use list {0} at {1}'
-                            matches.append(RuleMatch(
-                                tree, message.format(parameter, '/'.join(map(str, tree)))))
-                        found = True
-                    elif (resource == parameter.split('.')[0] and
-                          attribute_name == '.'.join(parameter.split('.')[1:])):
-                        if attribute_values.get('Type') == 'List':
-                            message = 'Fn::Sub cannot use list {0} at {1}'
-                            matches.append(RuleMatch(
-                                tree, message.format(parameter, '/'.join(map(str, tree)))))
-                        found = True
+                    if resource == parameter.split('.')[0]:
+                        if attribute_name == '*':
+                            found = True
+                        elif attribute_name == '.'.join(parameter.split('.')[1:]):
+                            if attribute_values.get('Type') == 'List':
+                                message = 'Fn::Sub cannot use list {0} at {1}'
+                                matches.append(RuleMatch(
+                                    tree, message.format(parameter, '/'.join(map(str, tree)))))
+                            found = True
+                        else:
+                            if attribute_name == parameter.split('.')[1] and attribute_values.get('Type') == 'Map':
+                                found = True
+
             if not found:
                 message = 'Parameter {0} for Fn::Sub not found at {1}'
                 matches.append(RuleMatch(

{"golden_diff": "diff --git a/src/cfnlint/rules/functions/GetAtt.py b/src/cfnlint/rules/functions/GetAtt.py\n--- a/src/cfnlint/rules/functions/GetAtt.py\n+++ b/src/cfnlint/rules/functions/GetAtt.py\n@@ -80,6 +80,15 @@\n             if resname:\n                 if resname in valid_getatts:\n                     if restype is not None:\n+                        # Check for maps\n+                        restypeparts = restype.split('.')\n+                        if restypeparts[0] in valid_getatts[resname] and len(restypeparts) >= 2:\n+                            if valid_getatts[resname][restypeparts[0]].get('Type') != 'Map':\n+                                message = 'Invalid GetAtt {0}.{1} for resource {2}'\n+                                matches.append(RuleMatch(\n+                                    getatt[:-1], message.format(resname, restype, getatt[1])))\n+                            else:\n+                                continue\n                         if restype not in valid_getatts[resname] and '*' not in valid_getatts[resname]:\n                             message = 'Invalid GetAtt {0}.{1} for resource {2}'\n                             matches.append(RuleMatch(\ndiff --git a/src/cfnlint/rules/functions/Sub.py b/src/cfnlint/rules/functions/Sub.py\n--- a/src/cfnlint/rules/functions/Sub.py\n+++ b/src/cfnlint/rules/functions/Sub.py\n@@ -115,19 +115,19 @@\n                         tree, message.format(parameter, '/'.join(map(str, tree)))))\n             for resource, attributes in get_atts.items():\n                 for attribute_name, attribute_values in attributes.items():\n-                    if resource == parameter.split('.')[0] and attribute_name == '*':\n-                        if attribute_values.get('Type') == 'List':\n-                            message = 'Fn::Sub cannot use list {0} at {1}'\n-                            matches.append(RuleMatch(\n-                                tree, message.format(parameter, '/'.join(map(str, tree)))))\n-                        found = True\n-                    elif (resource == parameter.split('.')[0] and\n-                          attribute_name == '.'.join(parameter.split('.')[1:])):\n-                        if attribute_values.get('Type') == 'List':\n-                            message = 'Fn::Sub cannot use list {0} at {1}'\n-                            matches.append(RuleMatch(\n-                                tree, message.format(parameter, '/'.join(map(str, tree)))))\n-                        found = True\n+                    if resource == parameter.split('.')[0]:\n+                        if attribute_name == '*':\n+                            found = True\n+                        elif attribute_name == '.'.join(parameter.split('.')[1:]):\n+                            if attribute_values.get('Type') == 'List':\n+                                message = 'Fn::Sub cannot use list {0} at {1}'\n+                                matches.append(RuleMatch(\n+                                    tree, message.format(parameter, '/'.join(map(str, tree)))))\n+                            found = True\n+                        else:\n+                            if attribute_name == parameter.split('.')[1] and attribute_values.get('Type') == 'Map':\n+                                found = True\n+\n             if not found:\n                 message = 'Parameter {0} for Fn::Sub not found at {1}'\n                 matches.append(RuleMatch(\n", "issue": "AWS::ServiceCatalog::CloudFormationProvisionedProduct.Outputs\nIn July 2020, support for retrieving the outputs of provisioned Service Catalog products [was added to CloudFormation](https://aws.amazon.com/blogs/mt/how-to-launch-secure-and-governed-aws-resources-with-aws-cloudformation-and-aws-service-catalog/).\r\n\r\nGiven the following example template:\r\n\r\n```yaml\r\nAWSTemplateFormatVersion: '2010-09-09'\r\n\r\nResources:\r\n  ProvisionedProduct:\r\n    Type: AWS::ServiceCatalog::CloudFormationProvisionedProduct\r\n    Properties:\r\n      ProductName: example\r\n      ProvisioningArtifactName: v1\r\n\r\nOutputs:\r\n  OutputValue:\r\n    Value: !GetAtt ProvisionedProduct.Outputs.Example\r\n\r\n  OutputSub:\r\n    Value: !Sub '${ProvisionedProduct.Outputs.Example}-example'\r\n```\r\n\r\nThe following linting errors are output when using cfn-lint 0.35.0:\r\n\r\n```\r\nE1010 Invalid GetAtt ProvisionedProduct.Outputs.Example for resource OutputValue\r\nexample.yml:12:5\r\n\r\nE1019 Parameter ProvisionedProduct.Outputs.Example for Fn::Sub not found at Outputs/OutputSub/Value/Fn::Sub\r\nexample.yml:15:5\r\n```\r\n\r\nAccess to these outputs using `!GetAtt` is documented in the announcement of this functionality (e.g. `!GetAtt ApacheALB.Outputs.ALBTarget`). Additionally the example template given above works correctly when deployed.\r\n\r\n`Output` is documented in the [AWS::ServiceCatalog::CloudFormationProvisionedProduct return values](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-servicecatalog-cloudformationprovisionedproduct.html#aws-resource-servicecatalog-cloudformationprovisionedproduct-return-values) and appears to already be defined within cfn-lint CloudSpecs as a `Map` type.\r\n\r\nI believe that this a false positive and am not aware of any alternative syntax to access these outputs (e.g. `!FindInMap` doesn't appear to be usable in this instance).\n", "before_files": [{"content": "\"\"\"\nCopyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\nSPDX-License-Identifier: MIT-0\n\"\"\"\nimport six\nfrom cfnlint.rules import CloudFormationLintRule\nfrom cfnlint.rules import RuleMatch\nimport cfnlint.helpers\n\n\nclass GetAtt(CloudFormationLintRule):\n    \"\"\"Check if GetAtt values are correct\"\"\"\n    id = 'E1010'\n    shortdesc = 'GetAtt validation of parameters'\n    description = 'Validates that GetAtt parameters are to valid resources and properties of those resources'\n    source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html'\n    tags = ['functions', 'getatt']\n\n    def __init__(self):\n        \"\"\"Init \"\"\"\n        super(GetAtt, self).__init__()\n        self.propertytypes = []\n        self.resourcetypes = []\n\n    def initialize(self, cfn):\n        resourcespecs = cfnlint.helpers.RESOURCE_SPECS[cfn.regions[0]]\n        self.resourcetypes = resourcespecs['ResourceTypes']\n        self.propertytypes = resourcespecs['PropertyTypes']\n\n    def match(self, cfn):\n        matches = []\n\n        getatts = cfn.search_deep_keys('Fn::GetAtt')\n        valid_getatts = cfn.get_valid_getatts()\n\n        valid_attribute_functions = ['Ref']\n\n        for getatt in getatts:\n            if len(getatt[-1]) < 2:\n                message = 'Invalid GetAtt for {0}'\n                matches.append(RuleMatch(getatt, message.format('/'.join(map(str, getatt[:-1])))))\n                continue\n            if isinstance(getatt[-1], six.string_types):\n                resname, restype = getatt[-1].split('.', 1)\n            else:\n                resname = None\n                restype = None\n                if isinstance(getatt[-1][1], six.string_types):\n                    resname = getatt[-1][0]\n                    restype = '.'.join(getatt[-1][1:])\n                elif isinstance(getatt[-1][1], dict):\n                    # You can ref the secondary part of a getatt\n\n                    resname = getatt[-1][0]\n                    restype = getatt[-1][1]\n                    if len(restype) == 1:\n                        for k in restype.keys():\n                            if k not in valid_attribute_functions:\n                                message = 'GetAtt only supports functions \"{0}\" for attributes at {1}'\n                                matches.append(\n                                    RuleMatch(\n                                        getatt,\n                                        message.format(\n                                            ', '.join(map(str, valid_attribute_functions)),\n                                            '/'.join(map(str, getatt[:-1])))))\n                    else:\n                        message = 'Invalid GetAtt structure {0} at {1}'\n                        matches.append(RuleMatch(getatt, message.format(\n                            getatt[-1], '/'.join(map(str, getatt[:-1])))))\n\n                    # setting restype to None as we can't validate that anymore\n                    restype = None\n                else:\n                    message = 'Invalid GetAtt structure {0} at {1}'\n                    matches.append(RuleMatch(getatt, message.format(\n                        getatt[-1], '/'.join(map(str, getatt[:-1])))))\n\n            # only check resname if its set.  if it isn't it is because of bad structure\n            # and an error is already provided\n            if resname:\n                if resname in valid_getatts:\n                    if restype is not None:\n                        if restype not in valid_getatts[resname] and '*' not in valid_getatts[resname]:\n                            message = 'Invalid GetAtt {0}.{1} for resource {2}'\n                            matches.append(RuleMatch(\n                                getatt[:-1], message.format(resname, restype, getatt[1])))\n                else:\n                    message = 'Invalid GetAtt {0}.{1} for resource {2}'\n                    matches.append(RuleMatch(getatt, message.format(resname, restype, getatt[1])))\n\n        return matches\n", "path": "src/cfnlint/rules/functions/GetAtt.py"}, {"content": "\"\"\"\nCopyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\nSPDX-License-Identifier: MIT-0\n\"\"\"\nimport six\nfrom cfnlint.helpers import PSEUDOPARAMS, VALID_PARAMETER_TYPES_LIST\nfrom cfnlint.rules import CloudFormationLintRule\nfrom cfnlint.rules import RuleMatch\n\n\nclass Sub(CloudFormationLintRule):\n    \"\"\"Check if Sub values are correct\"\"\"\n    id = 'E1019'\n    shortdesc = 'Sub validation of parameters'\n    description = 'Making sure the sub function is properly configured'\n    source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html'\n    tags = ['functions', 'sub']\n\n    def _test_string(self, cfn, sub_string, parameters, tree):\n        \"\"\"Test if a string has appropriate parameters\"\"\"\n\n        matches = []\n        string_params = cfn.get_sub_parameters(sub_string)\n\n        for string_param in string_params:\n            if isinstance(string_param, (six.string_types)):\n                matches.extend(self._test_parameter(string_param, cfn, parameters, tree))\n\n        return matches\n\n    def _get_parameters(self, cfn):\n        \"\"\"Get all Parameter Names\"\"\"\n        results = {}\n        parameters = cfn.template.get('Parameters', {})\n        if isinstance(parameters, dict):\n            for param_name, param_values in parameters.items():\n                # This rule isn't here to check the Types but we need\n                # something valid if it doesn't exist\n                results[param_name] = param_values.get('Type', 'String')\n\n        return results\n\n    def _test_parameters(self, parameters, cfn, tree):\n        \"\"\"Check parameters for appropriate configuration\"\"\"\n\n        supported_functions = [\n            'Fn::Base64',\n            'Fn::FindInMap',\n            'Fn::GetAZs',\n            'Fn::GetAtt',\n            'Fn::If',\n            'Fn::ImportValue',\n            'Fn::Join',\n            'Fn::Select',\n            'Fn::Sub',\n            'Ref',\n        ]\n\n        matches = []\n        for parameter_name, parameter_value_obj in parameters.items():\n            param_tree = tree[:] + [parameter_name]\n            if isinstance(parameter_value_obj, dict):\n                if len(parameter_value_obj) == 1:\n                    for key, value in parameter_value_obj.items():\n                        if key not in supported_functions:\n                            message = 'Sub parameter should use a valid function for {0}'\n                            matches.append(RuleMatch(\n                                param_tree, message.format('/'.join(map(str, tree)))))\n                        elif key in ['Ref']:\n                            matches.extend(self._test_parameter(value, cfn, {}, tree))\n                        elif key in ['Fn::GetAtt']:\n                            if isinstance(value, list):\n                                # Only test this if all the items are a string\n                                if_all_strings = True\n                                for v in value:\n                                    if not isinstance(v, six.string_types):\n                                        # skip things got too complex\n                                        if_all_strings = False\n                                if if_all_strings:\n                                    matches.extend(self._test_parameter(\n                                        '.'.join(value), cfn, {}, tree))\n                            elif isinstance(value, six.string_types):\n                                matches.extend(self._test_parameter(value, cfn, {}, tree))\n                else:\n                    message = 'Sub parameter should be an object of 1 for {0}'\n                    matches.append(RuleMatch(\n                        param_tree, message.format('/'.join(map(str, tree)))))\n            elif not isinstance(parameter_value_obj, six.string_types):\n                message = 'Sub parameter should be an object of 1 or string for {0}'\n                matches.append(RuleMatch(\n                    param_tree, message.format('/'.join(map(str, tree)))))\n\n        return matches\n\n    def _test_parameter(self, parameter, cfn, parameters, tree):\n        \"\"\" Test a parameter \"\"\"\n\n        matches = []\n        get_atts = cfn.get_valid_getatts()\n\n        valid_params = list(PSEUDOPARAMS)\n        valid_params.extend(cfn.get_resource_names())\n        template_parameters = self._get_parameters(cfn)\n\n        for key, _ in parameters.items():\n            valid_params.append(key)\n\n        if parameter not in valid_params:\n            found = False\n            if parameter in template_parameters:\n                found = True\n                if template_parameters.get(parameter) in VALID_PARAMETER_TYPES_LIST:\n                    message = 'Fn::Sub cannot use list {0} at {1}'\n                    matches.append(RuleMatch(\n                        tree, message.format(parameter, '/'.join(map(str, tree)))))\n            for resource, attributes in get_atts.items():\n                for attribute_name, attribute_values in attributes.items():\n                    if resource == parameter.split('.')[0] and attribute_name == '*':\n                        if attribute_values.get('Type') == 'List':\n                            message = 'Fn::Sub cannot use list {0} at {1}'\n                            matches.append(RuleMatch(\n                                tree, message.format(parameter, '/'.join(map(str, tree)))))\n                        found = True\n                    elif (resource == parameter.split('.')[0] and\n                          attribute_name == '.'.join(parameter.split('.')[1:])):\n                        if attribute_values.get('Type') == 'List':\n                            message = 'Fn::Sub cannot use list {0} at {1}'\n                            matches.append(RuleMatch(\n                                tree, message.format(parameter, '/'.join(map(str, tree)))))\n                        found = True\n            if not found:\n                message = 'Parameter {0} for Fn::Sub not found at {1}'\n                matches.append(RuleMatch(\n                    tree, message.format(parameter, '/'.join(map(str, tree)))))\n\n        return matches\n\n    def match(self, cfn):\n        matches = []\n\n        sub_objs = cfn.search_deep_keys('Fn::Sub')\n\n        for sub_obj in sub_objs:\n            sub_value_obj = sub_obj[-1]\n            tree = sub_obj[:-1]\n            if isinstance(sub_value_obj, six.string_types):\n                matches.extend(self._test_string(cfn, sub_value_obj, {}, tree))\n            elif isinstance(sub_value_obj, list):\n                if len(sub_value_obj) == 2:\n                    sub_string = sub_value_obj[0]\n                    parameters = sub_value_obj[1]\n                    if not isinstance(sub_string, six.string_types):\n                        message = 'Subs first element should be of type string for {0}'\n                        matches.append(RuleMatch(\n                            tree + [0], message.format('/'.join(map(str, tree)))))\n                    if not isinstance(parameters, dict):\n                        message = 'Subs second element should be an object for {0}'\n                        matches.append(RuleMatch(\n                            tree + [1], message.format('/'.join(map(str, tree)))))\n                    else:\n                        matches.extend(self._test_string(cfn, sub_string, parameters, tree + [0]))\n                        matches.extend(self._test_parameters(parameters, cfn, tree))\n                else:\n                    message = 'Sub should be an array of 2 for {0}'\n                    matches.append(RuleMatch(\n                        tree, message.format('/'.join(map(str, tree)))))\n            elif isinstance(sub_value_obj, dict):\n                if len(sub_value_obj) == 1:\n                    for key, _ in sub_value_obj.items():\n                        if not key == 'Fn::Transform':\n                            message = 'Sub should be a string or array of 2 items for {0}'\n                            matches.append(RuleMatch(\n                                tree, message.format('/'.join(map(str, tree)))))\n                else:\n                    message = 'Sub should be a string or array of 2 items for {0}'\n                    matches.append(RuleMatch(\n                        tree, message.format('/'.join(map(str, tree)))))\n            else:\n                message = 'Sub should be a string or array of 2 items for {0}'\n                matches.append(RuleMatch(\n                    tree, message.format('/'.join(map(str, tree)))))\n\n        return matches\n", "path": "src/cfnlint/rules/functions/Sub.py"}], "after_files": [{"content": "\"\"\"\nCopyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\nSPDX-License-Identifier: MIT-0\n\"\"\"\nimport six\nfrom cfnlint.rules import CloudFormationLintRule\nfrom cfnlint.rules import RuleMatch\nimport cfnlint.helpers\n\n\nclass GetAtt(CloudFormationLintRule):\n    \"\"\"Check if GetAtt values are correct\"\"\"\n    id = 'E1010'\n    shortdesc = 'GetAtt validation of parameters'\n    description = 'Validates that GetAtt parameters are to valid resources and properties of those resources'\n    source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html'\n    tags = ['functions', 'getatt']\n\n    def __init__(self):\n        \"\"\"Init \"\"\"\n        super(GetAtt, self).__init__()\n        self.propertytypes = []\n        self.resourcetypes = []\n\n    def initialize(self, cfn):\n        resourcespecs = cfnlint.helpers.RESOURCE_SPECS[cfn.regions[0]]\n        self.resourcetypes = resourcespecs['ResourceTypes']\n        self.propertytypes = resourcespecs['PropertyTypes']\n\n    def match(self, cfn):\n        matches = []\n\n        getatts = cfn.search_deep_keys('Fn::GetAtt')\n        valid_getatts = cfn.get_valid_getatts()\n\n        valid_attribute_functions = ['Ref']\n\n        for getatt in getatts:\n            if len(getatt[-1]) < 2:\n                message = 'Invalid GetAtt for {0}'\n                matches.append(RuleMatch(getatt, message.format('/'.join(map(str, getatt[:-1])))))\n                continue\n            if isinstance(getatt[-1], six.string_types):\n                resname, restype = getatt[-1].split('.', 1)\n            else:\n                resname = None\n                restype = None\n                if isinstance(getatt[-1][1], six.string_types):\n                    resname = getatt[-1][0]\n                    restype = '.'.join(getatt[-1][1:])\n                elif isinstance(getatt[-1][1], dict):\n                    # You can ref the secondary part of a getatt\n\n                    resname = getatt[-1][0]\n                    restype = getatt[-1][1]\n                    if len(restype) == 1:\n                        for k in restype.keys():\n                            if k not in valid_attribute_functions:\n                                message = 'GetAtt only supports functions \"{0}\" for attributes at {1}'\n                                matches.append(\n                                    RuleMatch(\n                                        getatt,\n                                        message.format(\n                                            ', '.join(map(str, valid_attribute_functions)),\n                                            '/'.join(map(str, getatt[:-1])))))\n                    else:\n                        message = 'Invalid GetAtt structure {0} at {1}'\n                        matches.append(RuleMatch(getatt, message.format(\n                            getatt[-1], '/'.join(map(str, getatt[:-1])))))\n\n                    # setting restype to None as we can't validate that anymore\n                    restype = None\n                else:\n                    message = 'Invalid GetAtt structure {0} at {1}'\n                    matches.append(RuleMatch(getatt, message.format(\n                        getatt[-1], '/'.join(map(str, getatt[:-1])))))\n\n            # only check resname if its set.  if it isn't it is because of bad structure\n            # and an error is already provided\n            if resname:\n                if resname in valid_getatts:\n                    if restype is not None:\n                        # Check for maps\n                        restypeparts = restype.split('.')\n                        if restypeparts[0] in valid_getatts[resname] and len(restypeparts) >= 2:\n                            if valid_getatts[resname][restypeparts[0]].get('Type') != 'Map':\n                                message = 'Invalid GetAtt {0}.{1} for resource {2}'\n                                matches.append(RuleMatch(\n                                    getatt[:-1], message.format(resname, restype, getatt[1])))\n                            else:\n                                continue\n                        if restype not in valid_getatts[resname] and '*' not in valid_getatts[resname]:\n                            message = 'Invalid GetAtt {0}.{1} for resource {2}'\n                            matches.append(RuleMatch(\n                                getatt[:-1], message.format(resname, restype, getatt[1])))\n                else:\n                    message = 'Invalid GetAtt {0}.{1} for resource {2}'\n                    matches.append(RuleMatch(getatt, message.format(resname, restype, getatt[1])))\n\n        return matches\n", "path": "src/cfnlint/rules/functions/GetAtt.py"}, {"content": "\"\"\"\nCopyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\nSPDX-License-Identifier: MIT-0\n\"\"\"\nimport six\nfrom cfnlint.helpers import PSEUDOPARAMS, VALID_PARAMETER_TYPES_LIST\nfrom cfnlint.rules import CloudFormationLintRule\nfrom cfnlint.rules import RuleMatch\n\n\nclass Sub(CloudFormationLintRule):\n    \"\"\"Check if Sub values are correct\"\"\"\n    id = 'E1019'\n    shortdesc = 'Sub validation of parameters'\n    description = 'Making sure the sub function is properly configured'\n    source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html'\n    tags = ['functions', 'sub']\n\n    def _test_string(self, cfn, sub_string, parameters, tree):\n        \"\"\"Test if a string has appropriate parameters\"\"\"\n\n        matches = []\n        string_params = cfn.get_sub_parameters(sub_string)\n\n        for string_param in string_params:\n            if isinstance(string_param, (six.string_types)):\n                matches.extend(self._test_parameter(string_param, cfn, parameters, tree))\n\n        return matches\n\n    def _get_parameters(self, cfn):\n        \"\"\"Get all Parameter Names\"\"\"\n        results = {}\n        parameters = cfn.template.get('Parameters', {})\n        if isinstance(parameters, dict):\n            for param_name, param_values in parameters.items():\n                # This rule isn't here to check the Types but we need\n                # something valid if it doesn't exist\n                results[param_name] = param_values.get('Type', 'String')\n\n        return results\n\n    def _test_parameters(self, parameters, cfn, tree):\n        \"\"\"Check parameters for appropriate configuration\"\"\"\n\n        supported_functions = [\n            'Fn::Base64',\n            'Fn::FindInMap',\n            'Fn::GetAZs',\n            'Fn::GetAtt',\n            'Fn::If',\n            'Fn::ImportValue',\n            'Fn::Join',\n            'Fn::Select',\n            'Fn::Sub',\n            'Ref',\n        ]\n\n        matches = []\n        for parameter_name, parameter_value_obj in parameters.items():\n            param_tree = tree[:] + [parameter_name]\n            if isinstance(parameter_value_obj, dict):\n                if len(parameter_value_obj) == 1:\n                    for key, value in parameter_value_obj.items():\n                        if key not in supported_functions:\n                            message = 'Sub parameter should use a valid function for {0}'\n                            matches.append(RuleMatch(\n                                param_tree, message.format('/'.join(map(str, tree)))))\n                        elif key in ['Ref']:\n                            matches.extend(self._test_parameter(value, cfn, {}, tree))\n                        elif key in ['Fn::GetAtt']:\n                            if isinstance(value, list):\n                                # Only test this if all the items are a string\n                                if_all_strings = True\n                                for v in value:\n                                    if not isinstance(v, six.string_types):\n                                        # skip things got too complex\n                                        if_all_strings = False\n                                if if_all_strings:\n                                    matches.extend(self._test_parameter(\n                                        '.'.join(value), cfn, {}, tree))\n                            elif isinstance(value, six.string_types):\n                                matches.extend(self._test_parameter(value, cfn, {}, tree))\n                else:\n                    message = 'Sub parameter should be an object of 1 for {0}'\n                    matches.append(RuleMatch(\n                        param_tree, message.format('/'.join(map(str, tree)))))\n            elif not isinstance(parameter_value_obj, six.string_types):\n                message = 'Sub parameter should be an object of 1 or string for {0}'\n                matches.append(RuleMatch(\n                    param_tree, message.format('/'.join(map(str, tree)))))\n\n        return matches\n\n    def _test_parameter(self, parameter, cfn, parameters, tree):\n        \"\"\" Test a parameter \"\"\"\n\n        matches = []\n        get_atts = cfn.get_valid_getatts()\n\n        valid_params = list(PSEUDOPARAMS)\n        valid_params.extend(cfn.get_resource_names())\n        template_parameters = self._get_parameters(cfn)\n\n        for key, _ in parameters.items():\n            valid_params.append(key)\n\n        if parameter not in valid_params:\n            found = False\n            if parameter in template_parameters:\n                found = True\n                if template_parameters.get(parameter) in VALID_PARAMETER_TYPES_LIST:\n                    message = 'Fn::Sub cannot use list {0} at {1}'\n                    matches.append(RuleMatch(\n                        tree, message.format(parameter, '/'.join(map(str, tree)))))\n            for resource, attributes in get_atts.items():\n                for attribute_name, attribute_values in attributes.items():\n                    if resource == parameter.split('.')[0]:\n                        if attribute_name == '*':\n                            found = True\n                        elif attribute_name == '.'.join(parameter.split('.')[1:]):\n                            if attribute_values.get('Type') == 'List':\n                                message = 'Fn::Sub cannot use list {0} at {1}'\n                                matches.append(RuleMatch(\n                                    tree, message.format(parameter, '/'.join(map(str, tree)))))\n                            found = True\n                        else:\n                            if attribute_name == parameter.split('.')[1] and attribute_values.get('Type') == 'Map':\n                                found = True\n\n            if not found:\n                message = 'Parameter {0} for Fn::Sub not found at {1}'\n                matches.append(RuleMatch(\n                    tree, message.format(parameter, '/'.join(map(str, tree)))))\n\n        return matches\n\n    def match(self, cfn):\n        matches = []\n\n        sub_objs = cfn.search_deep_keys('Fn::Sub')\n\n        for sub_obj in sub_objs:\n            sub_value_obj = sub_obj[-1]\n            tree = sub_obj[:-1]\n            if isinstance(sub_value_obj, six.string_types):\n                matches.extend(self._test_string(cfn, sub_value_obj, {}, tree))\n            elif isinstance(sub_value_obj, list):\n                if len(sub_value_obj) == 2:\n                    sub_string = sub_value_obj[0]\n                    parameters = sub_value_obj[1]\n                    if not isinstance(sub_string, six.string_types):\n                        message = 'Subs first element should be of type string for {0}'\n                        matches.append(RuleMatch(\n                            tree + [0], message.format('/'.join(map(str, tree)))))\n                    if not isinstance(parameters, dict):\n                        message = 'Subs second element should be an object for {0}'\n                        matches.append(RuleMatch(\n                            tree + [1], message.format('/'.join(map(str, tree)))))\n                    else:\n                        matches.extend(self._test_string(cfn, sub_string, parameters, tree + [0]))\n                        matches.extend(self._test_parameters(parameters, cfn, tree))\n                else:\n                    message = 'Sub should be an array of 2 for {0}'\n                    matches.append(RuleMatch(\n                        tree, message.format('/'.join(map(str, tree)))))\n            elif isinstance(sub_value_obj, dict):\n                if len(sub_value_obj) == 1:\n                    for key, _ in sub_value_obj.items():\n                        if not key == 'Fn::Transform':\n                            message = 'Sub should be a string or array of 2 items for {0}'\n                            matches.append(RuleMatch(\n                                tree, message.format('/'.join(map(str, tree)))))\n                else:\n                    message = 'Sub should be a string or array of 2 items for {0}'\n                    matches.append(RuleMatch(\n                        tree, message.format('/'.join(map(str, tree)))))\n            else:\n                message = 'Sub should be a string or array of 2 items for {0}'\n                matches.append(RuleMatch(\n                    tree, message.format('/'.join(map(str, tree)))))\n\n        return matches\n", "path": "src/cfnlint/rules/functions/Sub.py"}]}