We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
!GetAtt reference lint error
Hello,

Getting validation error on `AWS::ElasticLoadBalancingV2::Listener` property `LoadBalancerArn` when the `LoadBalancerArn` is referenced using !GetAtt nested-stack-name.Outputs.LoadbalancerArn

`[cfn-lint] E3008:CloudFormation stack outputs need to be strings not lists at Resources/ApiGwNlbListener/Properties/LoadBalancerArn/Fn::GetAtt
`

--- 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/resources/properties/ValueRefGetAtt.py`
Content:
```
1 """
2   Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 
4   Permission is hereby granted, free of charge, to any person obtaining a copy of this
5   software and associated documentation files (the "Software"), to deal in the Software
6   without restriction, including without limitation the rights to use, copy, modify,
7   merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
8   permit persons to whom the Software is furnished to do so.
9 
10   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
11   INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
12   PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
13   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
14   OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
15   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16 """
17 import six
18 from cfnlint import CloudFormationLintRule
19 from cfnlint import RuleMatch
20 from cfnlint.helpers import RESOURCE_SPECS
21 import cfnlint.helpers
22 
23 
24 class ValueRefGetAtt(CloudFormationLintRule):
25     """Check if Resource Properties are correct"""
26     id = 'E3008'
27     shortdesc = 'Check values of properties for valid Refs and GetAtts'
28     description = 'Checks resource properties for Ref and GetAtt values'
29     tags = ['resources']
30 
31     def initialize(self, cfn):
32         """Initialize the rule"""
33         for resource_type_spec in RESOURCE_SPECS.get(cfn.regions[0]).get('ResourceTypes'):
34             self.resource_property_types.append(resource_type_spec)
35         for property_type_spec in RESOURCE_SPECS.get(cfn.regions[0]).get('PropertyTypes'):
36             self.resource_sub_property_types.append(property_type_spec)
37 
38     def is_value_a_list(self, path, property_name):
39         """
40             Determines if the value checked is a list or a value in a list
41             We need to handle conditions in the path that could be nested, etc.
42             ['Resources', 'LoadBalancer', 'Properties', 'Subnets', 'Fn::If', 2, 'Fn::If', 2]
43             Numbers preceeded by a Fn::If should be removed and check repeated.
44         """
45         if path[-1] != property_name:
46             # Property doesn't match the property name
47             # Check if its a number and a condition
48             if isinstance(path[-1], int) and path[-2] == 'Fn::If':
49                 return self.is_value_a_list(path[:-2], property_name)
50 
51             return False
52 
53         return True
54 
55     def check_value_ref(self, value, path, **kwargs):
56         """Check Ref"""
57         matches = list()
58         cfn = kwargs.get('cfn')
59         value_specs = kwargs.get('value_specs', {}).get('Ref')
60         list_value_specs = kwargs.get('list_value_specs', {}).get('Ref')
61         property_type = kwargs.get('property_type')
62         property_name = kwargs.get('property_name')
63         if path[-1] == 'Ref' and property_type == 'List' and self.is_value_a_list(path[:-1], property_name):
64             specs = list_value_specs
65         else:
66             specs = value_specs
67 
68         if not specs:
69             # If no Ref's are specified, just skip
70             # Opposite of GetAtt you will always have a Ref to a Parameter so if this is
71             # None it just hasn't been defined and we can skip
72             return matches
73 
74         if value in cfn.template.get('Parameters', {}):
75             param = cfn.template.get('Parameters').get(value, {})
76             parameter_type = param.get('Type')
77             valid_parameter_types = []
78             for parameter in specs.get('Parameters'):
79                 for param_type in RESOURCE_SPECS.get(cfn.regions[0]).get('ParameterTypes').get(parameter):
80                     valid_parameter_types.append(param_type)
81 
82             if not specs.get('Parameters'):
83                 message = 'Property "{0}" has no valid Refs to Parameters at {1}'
84                 matches.append(RuleMatch(path, message.format(property_name, '/'.join(map(str, path)))))
85             elif parameter_type not in valid_parameter_types:
86                 message = 'Property "{0}" can Ref to parameter of types [{1}] at {2}'
87                 matches.append(
88                     RuleMatch(
89                         path,
90                         message.format(
91                             property_name,
92                             ', '.join(map(str, valid_parameter_types)),
93                             '/'.join(map(str, path)))))
94         if value in cfn.template.get('Resources', {}):
95             resource = cfn.template.get('Resources').get(value, {})
96             resource_type = resource.get('Type')
97             if not specs.get('Resources'):
98                 message = 'Property "{0}" has no valid Refs to Resources at {1}'
99                 matches.append(RuleMatch(path, message.format(property_name, '/'.join(map(str, path)))))
100             elif resource_type not in specs.get('Resources'):
101                 message = 'Property "{0}" can Ref to resources of types [{1}] at {2}'
102                 matches.append(
103                     RuleMatch(
104                         path,
105                         message.format(
106                             property_name,
107                             ', '.join(map(str, specs.get('Resources'))),
108                             '/'.join(map(str, path)))))
109 
110         return matches
111 
112     def check_value_getatt(self, value, path, **kwargs):
113         """Check GetAtt"""
114         matches = []
115         cfn = kwargs.get('cfn')
116         value_specs = kwargs.get('value_specs', {}).get('GetAtt')
117         list_value_specs = kwargs.get('list_value_specs', {}).get('GetAtt')
118         property_type = kwargs.get('property_type')
119         property_name = kwargs.get('property_name')
120         # You can sometimes get a list or a string with . in it
121         if isinstance(value, list):
122             resource_name = value[0]
123             resource_attribute = value[1:]
124         elif isinstance(value, six.string_types):
125             resource_name = value.split('.')[0]
126             resource_attribute = value.split('.')[1:]
127         is_value_a_list = self.is_value_a_list(path[:-1], property_name)
128         if path[-1] == 'Fn::GetAtt' and property_type == 'List' and is_value_a_list:
129             specs = list_value_specs
130         else:
131             specs = value_specs
132 
133         resource_type = cfn.template.get('Resources', {}).get(resource_name, {}).get('Type')
134 
135         if cfnlint.helpers.is_custom_resource(resource_type):
136             #  A custom resource voids the spec.  Move on
137             return matches
138 
139         if resource_type == 'AWS::CloudFormation::Stack' and resource_attribute[0] == 'Outputs':
140             # Nested Stack Outputs
141             # if its a string type we are good and return matches
142             # if its a list its a failure as Outputs can only be strings
143 
144             if is_value_a_list:
145                 message = 'CloudFormation stack outputs need to be strings not lists at {0}'
146                 matches.append(RuleMatch(path, message.format('/'.join(map(str, path)))))
147 
148             return matches
149 
150         if specs is None:
151             # GetAtt specs aren't specified skip
152             return matches
153         if not specs:
154             # GetAtt is specified but empty so there are no valid options
155             message = 'Property "{0}" has no valid Fn::GetAtt options at {1}'
156             matches.append(RuleMatch(path, message.format(property_name, '/'.join(map(str, path)))))
157             return matches
158 
159         if resource_type not in specs:
160             message = 'Property "{0}" can Fn::GetAtt to a resource of types [{1}] at {2}'
161             matches.append(
162                 RuleMatch(
163                     path,
164                     message.format(
165                         property_name,
166                         ', '.join(map(str, specs)),
167                         '/'.join(map(str, path)))))
168         elif '.'.join(map(str, resource_attribute)) != specs[resource_type]:
169             message = 'Property "{0}" can Fn::GetAtt to a resource attribute "{1}" at {2}'
170             matches.append(
171                 RuleMatch(
172                     path,
173                     message.format(
174                         property_name,
175                         specs[resource_type],
176                         '/'.join(map(str, path)))))
177 
178         return matches
179 
180     def check(self, cfn, properties, value_specs, property_specs, path):
181         """Check itself"""
182         matches = list()
183         for p_value, p_path in properties.items_safe(path[:]):
184             for prop in p_value:
185                 if prop in value_specs:
186                     value = value_specs.get(prop).get('Value', {})
187                     if value:
188                         value_type = value.get('ValueType', '')
189                         list_value_type = value.get('ListValueType', '')
190                         property_type = property_specs.get('Properties').get(prop).get('Type')
191                         matches.extend(
192                             cfn.check_value(
193                                 p_value, prop, p_path,
194                                 check_ref=self.check_value_ref,
195                                 check_get_att=self.check_value_getatt,
196                                 value_specs=RESOURCE_SPECS.get(cfn.regions[0]).get('ValueTypes').get(value_type, {}),
197                                 list_value_specs=RESOURCE_SPECS.get(cfn.regions[0]).get('ValueTypes').get(list_value_type, {}),
198                                 cfn=cfn, property_type=property_type, property_name=prop
199                             )
200                         )
201 
202         return matches
203 
204     def match_resource_sub_properties(self, properties, property_type, path, cfn):
205         """Match for sub properties"""
206         matches = list()
207 
208         specs = RESOURCE_SPECS.get(cfn.regions[0]).get('PropertyTypes').get(property_type, {}).get('Properties', {})
209         property_specs = RESOURCE_SPECS.get(cfn.regions[0]).get('PropertyTypes').get(property_type)
210         matches.extend(self.check(cfn, properties, specs, property_specs, path))
211 
212         return matches
213 
214     def match_resource_properties(self, properties, resource_type, path, cfn):
215         """Check CloudFormation Properties"""
216         matches = list()
217 
218         specs = RESOURCE_SPECS.get(cfn.regions[0]).get('ResourceTypes').get(resource_type, {}).get('Properties', {})
219         resource_specs = RESOURCE_SPECS.get(cfn.regions[0]).get('ResourceTypes').get(resource_type)
220         matches.extend(self.check(cfn, properties, specs, resource_specs, path))
221 
222         return matches
223
```
--- 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/resources/properties/ValueRefGetAtt.py b/src/cfnlint/rules/resources/properties/ValueRefGetAtt.py
--- a/src/cfnlint/rules/resources/properties/ValueRefGetAtt.py
+++ b/src/cfnlint/rules/resources/properties/ValueRefGetAtt.py
@@ -140,8 +140,7 @@
             # Nested Stack Outputs
             # if its a string type we are good and return matches
             # if its a list its a failure as Outputs can only be strings
-
-            if is_value_a_list:
+            if is_value_a_list and property_type == 'List':
                 message = 'CloudFormation stack outputs need to be strings not lists at {0}'
                 matches.append(RuleMatch(path, message.format('/'.join(map(str, path)))))

{"golden_diff": "diff --git a/src/cfnlint/rules/resources/properties/ValueRefGetAtt.py b/src/cfnlint/rules/resources/properties/ValueRefGetAtt.py\n--- a/src/cfnlint/rules/resources/properties/ValueRefGetAtt.py\n+++ b/src/cfnlint/rules/resources/properties/ValueRefGetAtt.py\n@@ -140,8 +140,7 @@\n             # Nested Stack Outputs\n             # if its a string type we are good and return matches\n             # if its a list its a failure as Outputs can only be strings\n-\n-            if is_value_a_list:\n+            if is_value_a_list and property_type == 'List':\n                 message = 'CloudFormation stack outputs need to be strings not lists at {0}'\n                 matches.append(RuleMatch(path, message.format('/'.join(map(str, path)))))\n", "issue": "!GetAtt reference lint error\nHello,\r\n\r\nGetting validation error on `AWS::ElasticLoadBalancingV2::Listener` property `LoadBalancerArn` when the `LoadBalancerArn` is referenced using !GetAtt nested-stack-name.Outputs.LoadbalancerArn\r\n\r\n`[cfn-lint] E3008:CloudFormation stack outputs need to be strings not lists at Resources/ApiGwNlbListener/Properties/LoadBalancerArn/Fn::GetAtt\r\n`\n", "before_files": [{"content": "\"\"\"\n  Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy of this\n  software and associated documentation files (the \"Software\"), to deal in the Software\n  without restriction, including without limitation the rights to use, copy, modify,\n  merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n  permit persons to whom the Software is furnished to do so.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\"\"\"\nimport six\nfrom cfnlint import CloudFormationLintRule\nfrom cfnlint import RuleMatch\nfrom cfnlint.helpers import RESOURCE_SPECS\nimport cfnlint.helpers\n\n\nclass ValueRefGetAtt(CloudFormationLintRule):\n    \"\"\"Check if Resource Properties are correct\"\"\"\n    id = 'E3008'\n    shortdesc = 'Check values of properties for valid Refs and GetAtts'\n    description = 'Checks resource properties for Ref and GetAtt values'\n    tags = ['resources']\n\n    def initialize(self, cfn):\n        \"\"\"Initialize the rule\"\"\"\n        for resource_type_spec in RESOURCE_SPECS.get(cfn.regions[0]).get('ResourceTypes'):\n            self.resource_property_types.append(resource_type_spec)\n        for property_type_spec in RESOURCE_SPECS.get(cfn.regions[0]).get('PropertyTypes'):\n            self.resource_sub_property_types.append(property_type_spec)\n\n    def is_value_a_list(self, path, property_name):\n        \"\"\"\n            Determines if the value checked is a list or a value in a list\n            We need to handle conditions in the path that could be nested, etc.\n            ['Resources', 'LoadBalancer', 'Properties', 'Subnets', 'Fn::If', 2, 'Fn::If', 2]\n            Numbers preceeded by a Fn::If should be removed and check repeated.\n        \"\"\"\n        if path[-1] != property_name:\n            # Property doesn't match the property name\n            # Check if its a number and a condition\n            if isinstance(path[-1], int) and path[-2] == 'Fn::If':\n                return self.is_value_a_list(path[:-2], property_name)\n\n            return False\n\n        return True\n\n    def check_value_ref(self, value, path, **kwargs):\n        \"\"\"Check Ref\"\"\"\n        matches = list()\n        cfn = kwargs.get('cfn')\n        value_specs = kwargs.get('value_specs', {}).get('Ref')\n        list_value_specs = kwargs.get('list_value_specs', {}).get('Ref')\n        property_type = kwargs.get('property_type')\n        property_name = kwargs.get('property_name')\n        if path[-1] == 'Ref' and property_type == 'List' and self.is_value_a_list(path[:-1], property_name):\n            specs = list_value_specs\n        else:\n            specs = value_specs\n\n        if not specs:\n            # If no Ref's are specified, just skip\n            # Opposite of GetAtt you will always have a Ref to a Parameter so if this is\n            # None it just hasn't been defined and we can skip\n            return matches\n\n        if value in cfn.template.get('Parameters', {}):\n            param = cfn.template.get('Parameters').get(value, {})\n            parameter_type = param.get('Type')\n            valid_parameter_types = []\n            for parameter in specs.get('Parameters'):\n                for param_type in RESOURCE_SPECS.get(cfn.regions[0]).get('ParameterTypes').get(parameter):\n                    valid_parameter_types.append(param_type)\n\n            if not specs.get('Parameters'):\n                message = 'Property \"{0}\" has no valid Refs to Parameters at {1}'\n                matches.append(RuleMatch(path, message.format(property_name, '/'.join(map(str, path)))))\n            elif parameter_type not in valid_parameter_types:\n                message = 'Property \"{0}\" can Ref to parameter of types [{1}] at {2}'\n                matches.append(\n                    RuleMatch(\n                        path,\n                        message.format(\n                            property_name,\n                            ', '.join(map(str, valid_parameter_types)),\n                            '/'.join(map(str, path)))))\n        if value in cfn.template.get('Resources', {}):\n            resource = cfn.template.get('Resources').get(value, {})\n            resource_type = resource.get('Type')\n            if not specs.get('Resources'):\n                message = 'Property \"{0}\" has no valid Refs to Resources at {1}'\n                matches.append(RuleMatch(path, message.format(property_name, '/'.join(map(str, path)))))\n            elif resource_type not in specs.get('Resources'):\n                message = 'Property \"{0}\" can Ref to resources of types [{1}] at {2}'\n                matches.append(\n                    RuleMatch(\n                        path,\n                        message.format(\n                            property_name,\n                            ', '.join(map(str, specs.get('Resources'))),\n                            '/'.join(map(str, path)))))\n\n        return matches\n\n    def check_value_getatt(self, value, path, **kwargs):\n        \"\"\"Check GetAtt\"\"\"\n        matches = []\n        cfn = kwargs.get('cfn')\n        value_specs = kwargs.get('value_specs', {}).get('GetAtt')\n        list_value_specs = kwargs.get('list_value_specs', {}).get('GetAtt')\n        property_type = kwargs.get('property_type')\n        property_name = kwargs.get('property_name')\n        # You can sometimes get a list or a string with . in it\n        if isinstance(value, list):\n            resource_name = value[0]\n            resource_attribute = value[1:]\n        elif isinstance(value, six.string_types):\n            resource_name = value.split('.')[0]\n            resource_attribute = value.split('.')[1:]\n        is_value_a_list = self.is_value_a_list(path[:-1], property_name)\n        if path[-1] == 'Fn::GetAtt' and property_type == 'List' and is_value_a_list:\n            specs = list_value_specs\n        else:\n            specs = value_specs\n\n        resource_type = cfn.template.get('Resources', {}).get(resource_name, {}).get('Type')\n\n        if cfnlint.helpers.is_custom_resource(resource_type):\n            #  A custom resource voids the spec.  Move on\n            return matches\n\n        if resource_type == 'AWS::CloudFormation::Stack' and resource_attribute[0] == 'Outputs':\n            # Nested Stack Outputs\n            # if its a string type we are good and return matches\n            # if its a list its a failure as Outputs can only be strings\n\n            if is_value_a_list:\n                message = 'CloudFormation stack outputs need to be strings not lists at {0}'\n                matches.append(RuleMatch(path, message.format('/'.join(map(str, path)))))\n\n            return matches\n\n        if specs is None:\n            # GetAtt specs aren't specified skip\n            return matches\n        if not specs:\n            # GetAtt is specified but empty so there are no valid options\n            message = 'Property \"{0}\" has no valid Fn::GetAtt options at {1}'\n            matches.append(RuleMatch(path, message.format(property_name, '/'.join(map(str, path)))))\n            return matches\n\n        if resource_type not in specs:\n            message = 'Property \"{0}\" can Fn::GetAtt to a resource of types [{1}] at {2}'\n            matches.append(\n                RuleMatch(\n                    path,\n                    message.format(\n                        property_name,\n                        ', '.join(map(str, specs)),\n                        '/'.join(map(str, path)))))\n        elif '.'.join(map(str, resource_attribute)) != specs[resource_type]:\n            message = 'Property \"{0}\" can Fn::GetAtt to a resource attribute \"{1}\" at {2}'\n            matches.append(\n                RuleMatch(\n                    path,\n                    message.format(\n                        property_name,\n                        specs[resource_type],\n                        '/'.join(map(str, path)))))\n\n        return matches\n\n    def check(self, cfn, properties, value_specs, property_specs, path):\n        \"\"\"Check itself\"\"\"\n        matches = list()\n        for p_value, p_path in properties.items_safe(path[:]):\n            for prop in p_value:\n                if prop in value_specs:\n                    value = value_specs.get(prop).get('Value', {})\n                    if value:\n                        value_type = value.get('ValueType', '')\n                        list_value_type = value.get('ListValueType', '')\n                        property_type = property_specs.get('Properties').get(prop).get('Type')\n                        matches.extend(\n                            cfn.check_value(\n                                p_value, prop, p_path,\n                                check_ref=self.check_value_ref,\n                                check_get_att=self.check_value_getatt,\n                                value_specs=RESOURCE_SPECS.get(cfn.regions[0]).get('ValueTypes').get(value_type, {}),\n                                list_value_specs=RESOURCE_SPECS.get(cfn.regions[0]).get('ValueTypes').get(list_value_type, {}),\n                                cfn=cfn, property_type=property_type, property_name=prop\n                            )\n                        )\n\n        return matches\n\n    def match_resource_sub_properties(self, properties, property_type, path, cfn):\n        \"\"\"Match for sub properties\"\"\"\n        matches = list()\n\n        specs = RESOURCE_SPECS.get(cfn.regions[0]).get('PropertyTypes').get(property_type, {}).get('Properties', {})\n        property_specs = RESOURCE_SPECS.get(cfn.regions[0]).get('PropertyTypes').get(property_type)\n        matches.extend(self.check(cfn, properties, specs, property_specs, path))\n\n        return matches\n\n    def match_resource_properties(self, properties, resource_type, path, cfn):\n        \"\"\"Check CloudFormation Properties\"\"\"\n        matches = list()\n\n        specs = RESOURCE_SPECS.get(cfn.regions[0]).get('ResourceTypes').get(resource_type, {}).get('Properties', {})\n        resource_specs = RESOURCE_SPECS.get(cfn.regions[0]).get('ResourceTypes').get(resource_type)\n        matches.extend(self.check(cfn, properties, specs, resource_specs, path))\n\n        return matches\n", "path": "src/cfnlint/rules/resources/properties/ValueRefGetAtt.py"}], "after_files": [{"content": "\"\"\"\n  Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy of this\n  software and associated documentation files (the \"Software\"), to deal in the Software\n  without restriction, including without limitation the rights to use, copy, modify,\n  merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n  permit persons to whom the Software is furnished to do so.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\"\"\"\nimport six\nfrom cfnlint import CloudFormationLintRule\nfrom cfnlint import RuleMatch\nfrom cfnlint.helpers import RESOURCE_SPECS\nimport cfnlint.helpers\n\n\nclass ValueRefGetAtt(CloudFormationLintRule):\n    \"\"\"Check if Resource Properties are correct\"\"\"\n    id = 'E3008'\n    shortdesc = 'Check values of properties for valid Refs and GetAtts'\n    description = 'Checks resource properties for Ref and GetAtt values'\n    tags = ['resources']\n\n    def initialize(self, cfn):\n        \"\"\"Initialize the rule\"\"\"\n        for resource_type_spec in RESOURCE_SPECS.get(cfn.regions[0]).get('ResourceTypes'):\n            self.resource_property_types.append(resource_type_spec)\n        for property_type_spec in RESOURCE_SPECS.get(cfn.regions[0]).get('PropertyTypes'):\n            self.resource_sub_property_types.append(property_type_spec)\n\n    def is_value_a_list(self, path, property_name):\n        \"\"\"\n            Determines if the value checked is a list or a value in a list\n            We need to handle conditions in the path that could be nested, etc.\n            ['Resources', 'LoadBalancer', 'Properties', 'Subnets', 'Fn::If', 2, 'Fn::If', 2]\n            Numbers preceeded by a Fn::If should be removed and check repeated.\n        \"\"\"\n        if path[-1] != property_name:\n            # Property doesn't match the property name\n            # Check if its a number and a condition\n            if isinstance(path[-1], int) and path[-2] == 'Fn::If':\n                return self.is_value_a_list(path[:-2], property_name)\n\n            return False\n\n        return True\n\n    def check_value_ref(self, value, path, **kwargs):\n        \"\"\"Check Ref\"\"\"\n        matches = list()\n        cfn = kwargs.get('cfn')\n        value_specs = kwargs.get('value_specs', {}).get('Ref')\n        list_value_specs = kwargs.get('list_value_specs', {}).get('Ref')\n        property_type = kwargs.get('property_type')\n        property_name = kwargs.get('property_name')\n        if path[-1] == 'Ref' and property_type == 'List' and self.is_value_a_list(path[:-1], property_name):\n            specs = list_value_specs\n        else:\n            specs = value_specs\n\n        if not specs:\n            # If no Ref's are specified, just skip\n            # Opposite of GetAtt you will always have a Ref to a Parameter so if this is\n            # None it just hasn't been defined and we can skip\n            return matches\n\n        if value in cfn.template.get('Parameters', {}):\n            param = cfn.template.get('Parameters').get(value, {})\n            parameter_type = param.get('Type')\n            valid_parameter_types = []\n            for parameter in specs.get('Parameters'):\n                for param_type in RESOURCE_SPECS.get(cfn.regions[0]).get('ParameterTypes').get(parameter):\n                    valid_parameter_types.append(param_type)\n\n            if not specs.get('Parameters'):\n                message = 'Property \"{0}\" has no valid Refs to Parameters at {1}'\n                matches.append(RuleMatch(path, message.format(property_name, '/'.join(map(str, path)))))\n            elif parameter_type not in valid_parameter_types:\n                message = 'Property \"{0}\" can Ref to parameter of types [{1}] at {2}'\n                matches.append(\n                    RuleMatch(\n                        path,\n                        message.format(\n                            property_name,\n                            ', '.join(map(str, valid_parameter_types)),\n                            '/'.join(map(str, path)))))\n        if value in cfn.template.get('Resources', {}):\n            resource = cfn.template.get('Resources').get(value, {})\n            resource_type = resource.get('Type')\n            if not specs.get('Resources'):\n                message = 'Property \"{0}\" has no valid Refs to Resources at {1}'\n                matches.append(RuleMatch(path, message.format(property_name, '/'.join(map(str, path)))))\n            elif resource_type not in specs.get('Resources'):\n                message = 'Property \"{0}\" can Ref to resources of types [{1}] at {2}'\n                matches.append(\n                    RuleMatch(\n                        path,\n                        message.format(\n                            property_name,\n                            ', '.join(map(str, specs.get('Resources'))),\n                            '/'.join(map(str, path)))))\n\n        return matches\n\n    def check_value_getatt(self, value, path, **kwargs):\n        \"\"\"Check GetAtt\"\"\"\n        matches = []\n        cfn = kwargs.get('cfn')\n        value_specs = kwargs.get('value_specs', {}).get('GetAtt')\n        list_value_specs = kwargs.get('list_value_specs', {}).get('GetAtt')\n        property_type = kwargs.get('property_type')\n        property_name = kwargs.get('property_name')\n        # You can sometimes get a list or a string with . in it\n        if isinstance(value, list):\n            resource_name = value[0]\n            resource_attribute = value[1:]\n        elif isinstance(value, six.string_types):\n            resource_name = value.split('.')[0]\n            resource_attribute = value.split('.')[1:]\n        is_value_a_list = self.is_value_a_list(path[:-1], property_name)\n        if path[-1] == 'Fn::GetAtt' and property_type == 'List' and is_value_a_list:\n            specs = list_value_specs\n        else:\n            specs = value_specs\n\n        resource_type = cfn.template.get('Resources', {}).get(resource_name, {}).get('Type')\n\n        if cfnlint.helpers.is_custom_resource(resource_type):\n            #  A custom resource voids the spec.  Move on\n            return matches\n\n        if resource_type == 'AWS::CloudFormation::Stack' and resource_attribute[0] == 'Outputs':\n            # Nested Stack Outputs\n            # if its a string type we are good and return matches\n            # if its a list its a failure as Outputs can only be strings\n            if is_value_a_list and property_type == 'List':\n                message = 'CloudFormation stack outputs need to be strings not lists at {0}'\n                matches.append(RuleMatch(path, message.format('/'.join(map(str, path)))))\n\n            return matches\n\n        if specs is None:\n            # GetAtt specs aren't specified skip\n            return matches\n        if not specs:\n            # GetAtt is specified but empty so there are no valid options\n            message = 'Property \"{0}\" has no valid Fn::GetAtt options at {1}'\n            matches.append(RuleMatch(path, message.format(property_name, '/'.join(map(str, path)))))\n            return matches\n\n        if resource_type not in specs:\n            message = 'Property \"{0}\" can Fn::GetAtt to a resource of types [{1}] at {2}'\n            matches.append(\n                RuleMatch(\n                    path,\n                    message.format(\n                        property_name,\n                        ', '.join(map(str, specs)),\n                        '/'.join(map(str, path)))))\n        elif '.'.join(map(str, resource_attribute)) != specs[resource_type]:\n            message = 'Property \"{0}\" can Fn::GetAtt to a resource attribute \"{1}\" at {2}'\n            matches.append(\n                RuleMatch(\n                    path,\n                    message.format(\n                        property_name,\n                        specs[resource_type],\n                        '/'.join(map(str, path)))))\n\n        return matches\n\n    def check(self, cfn, properties, value_specs, property_specs, path):\n        \"\"\"Check itself\"\"\"\n        matches = list()\n        for p_value, p_path in properties.items_safe(path[:]):\n            for prop in p_value:\n                if prop in value_specs:\n                    value = value_specs.get(prop).get('Value', {})\n                    if value:\n                        value_type = value.get('ValueType', '')\n                        list_value_type = value.get('ListValueType', '')\n                        property_type = property_specs.get('Properties').get(prop).get('Type')\n                        matches.extend(\n                            cfn.check_value(\n                                p_value, prop, p_path,\n                                check_ref=self.check_value_ref,\n                                check_get_att=self.check_value_getatt,\n                                value_specs=RESOURCE_SPECS.get(cfn.regions[0]).get('ValueTypes').get(value_type, {}),\n                                list_value_specs=RESOURCE_SPECS.get(cfn.regions[0]).get('ValueTypes').get(list_value_type, {}),\n                                cfn=cfn, property_type=property_type, property_name=prop\n                            )\n                        )\n\n        return matches\n\n    def match_resource_sub_properties(self, properties, property_type, path, cfn):\n        \"\"\"Match for sub properties\"\"\"\n        matches = list()\n\n        specs = RESOURCE_SPECS.get(cfn.regions[0]).get('PropertyTypes').get(property_type, {}).get('Properties', {})\n        property_specs = RESOURCE_SPECS.get(cfn.regions[0]).get('PropertyTypes').get(property_type)\n        matches.extend(self.check(cfn, properties, specs, property_specs, path))\n\n        return matches\n\n    def match_resource_properties(self, properties, resource_type, path, cfn):\n        \"\"\"Check CloudFormation Properties\"\"\"\n        matches = list()\n\n        specs = RESOURCE_SPECS.get(cfn.regions[0]).get('ResourceTypes').get(resource_type, {}).get('Properties', {})\n        resource_specs = RESOURCE_SPECS.get(cfn.regions[0]).get('ResourceTypes').get(resource_type)\n        matches.extend(self.check(cfn, properties, specs, resource_specs, path))\n\n        return matches\n", "path": "src/cfnlint/rules/resources/properties/ValueRefGetAtt.py"}]}