We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Error validando funciones en los tags
**Describe the issue**
We are trying to execute checkov in a template which makes use of a function (IF)  in the tags:

        - 'Fn::If':
           - cPdnEnv
           -
             Key: "Schedule"
             Value: "VISOR-02"
           - !Ref "AWS::NoValue"    

but it throws the following error message:

`Failed to parse tags for entity {'rEC2Instance': ...`

We updated to the latest version of checkov but the error continues

According to the AWS [documentation](https://docs.aws.amazon.com/es_es/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-conditions.html#intrinsic-function-reference-conditions-if), this IF function can be used on tags.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `checkov/cloudformation/cfn_utils.py`
Content:
```
1 from __future__ import annotations
2 
3 import logging
4 import os
5 from typing import Optional, List, Tuple, Dict, Any, Union
6 
7 import dpath.util
8 
9 from checkov.cloudformation.checks.resource.base_registry import Registry
10 from checkov.cloudformation.checks.resource.registry import cfn_registry
11 from checkov.cloudformation.context_parser import ContextParser, ENDLINE, STARTLINE
12 from checkov.cloudformation.parser import parse, TemplateSections
13 from checkov.common.parallelizer.parallel_runner import parallel_runner
14 from checkov.common.parsers.node import DictNode, ListNode, StrNode
15 from checkov.common.runners.base_runner import filter_ignored_paths
16 from checkov.runner_filter import RunnerFilter
17 from checkov.common.models.consts import YAML_COMMENT_MARK
18 
19 CF_POSSIBLE_ENDINGS = frozenset([".yml", ".yaml", ".json", ".template"])
20 
21 
22 def get_resource_tags(entity: Dict[StrNode, DictNode], registry: Registry = cfn_registry) -> Optional[Dict[str, str]]:
23     entity_details = registry.extract_entity_details(entity)
24 
25     if not entity_details:
26         return None
27 
28     entity_config = entity_details[-1]
29 
30     if not isinstance(entity_config, dict):
31         return None
32 
33     try:
34         properties = entity_config.get("Properties")
35         if properties:
36             tags = properties.get("Tags")
37             if tags:
38                 return parse_entity_tags(tags)
39     except Exception:
40         logging.warning(f"Failed to parse tags for entity {entity}")
41 
42     return None
43 
44 
45 def parse_entity_tags(tags: Union[ListNode, Dict[str, Any]]) -> Optional[Dict[str, str]]:
46     if isinstance(tags, ListNode):
47         tag_dict = {get_entity_value_as_string(tag["Key"]): get_entity_value_as_string(tag["Value"]) for tag in tags}
48         return tag_dict
49     elif isinstance(tags, dict):
50         tag_dict = {
51             get_entity_value_as_string(key): get_entity_value_as_string(value)
52             for key, value in tags.items()
53             if key not in (STARTLINE, ENDLINE)
54         }
55         return tag_dict
56     return None
57 
58 
59 def get_entity_value_as_string(value: Any) -> str:
60     """
61     Handles different type of entities with possible CFN function substitutions. Returns the simplest possible string value
62     (without performing any function calls).
63 
64     Examples:
65     Key: Value  # returns simple string
66 
67     Key: !Ref ${AWS::AccountId}-data  # returns ${AWS::AccountId}-data
68 
69     Key:
70     - ${account}-data
71     - account: !Ref ${AWS::AccountId}
72 
73     # returns ${account}-data
74 
75     :param value:
76     :return:
77     """
78     if isinstance(value, dict):
79         (function, value) = next(iter(value.items()))
80         # If the value is a long-form function, then the first element is the template string (technically str_node)
81         # Otherwise the dict value is the template string
82         if isinstance(value, list):
83             if "Join" in function:
84                 # Join looks like !Join [, [V1, V2, V3]]
85                 join_str = str(value[0])
86                 return join_str.join([str(v) for v in value[1]])
87             else:
88                 return str(value[0])
89         else:
90             return str(value)
91     else:
92         return str(value)
93 
94 
95 def get_folder_definitions(
96         root_folder: str, excluded_paths: list[str] | None, out_parsing_errors: dict[str, str] | None = None
97 ) -> tuple[dict[str, DictNode], dict[str, list[tuple[int, str]]]]:
98     out_parsing_errors = {} if out_parsing_errors is None else out_parsing_errors
99     files_list = []
100     for root, d_names, f_names in os.walk(root_folder):
101         filter_ignored_paths(root, d_names, excluded_paths)
102         filter_ignored_paths(root, f_names, excluded_paths)
103         for file in f_names:
104             file_ending = os.path.splitext(file)[1]
105             if file_ending in CF_POSSIBLE_ENDINGS:
106                 files_list.append(os.path.join(root, file))
107 
108     definitions, definitions_raw = get_files_definitions(files_list, out_parsing_errors)
109     return definitions, definitions_raw
110 
111 
112 def build_definitions_context(
113         definitions: Dict[str, DictNode], definitions_raw: Dict[str, List[Tuple[int, str]]]
114 ) -> Dict[str, Dict[str, Any]]:
115     definitions_context: Dict[str, Dict[str, Any]] = {}
116     # iterate on the files
117     for file_path, file_path_definitions in definitions.items():
118         # iterate on the definitions (Parameters, Resources, Outputs...)
119         for file_path_definition, definition in file_path_definitions.items():
120             if (
121                     isinstance(file_path_definition, StrNode)
122                     and file_path_definition.upper() in TemplateSections.__members__
123                     and isinstance(definition, DictNode)
124             ):
125                 # iterate on the actual objects of each definition
126                 for attribute, attr_value in definition.items():
127                     if isinstance(attr_value, DictNode):
128                         start_line = attr_value.start_mark.line
129                         end_line = attr_value.end_mark.line
130                         # fix lines number for yaml and json files
131                         first_line_index = 0
132                         while not str.strip(definitions_raw[file_path][first_line_index][1]):
133                             first_line_index += 1
134                         # check if the file is a json file
135                         if str.strip(definitions_raw[file_path][first_line_index][1])[0] == "{":
136                             start_line += 1
137                             end_line += 1
138                         else:
139                             # add resource comments to definition lines
140                             current_line = str.strip(definitions_raw[file_path][start_line - 1][1])
141                             while not current_line or current_line[0] == YAML_COMMENT_MARK:
142                                 start_line -= 1
143                                 current_line = str.strip(definitions_raw[file_path][start_line - 1][1])
144 
145                             # remove next resource comments from definition lines
146                             current_line = str.strip(definitions_raw[file_path][end_line - 1][1])
147                             while not current_line or current_line[0] == YAML_COMMENT_MARK:
148                                 end_line -= 1
149                                 current_line = str.strip(definitions_raw[file_path][end_line - 1][1])
150 
151                         code_lines = definitions_raw[file_path][start_line - 1: end_line]
152                         dpath.new(
153                             definitions_context,
154                             [file_path, str(file_path_definition), str(attribute)],
155                             {"start_line": start_line, "end_line": end_line, "code_lines": code_lines},
156                         )
157                         if file_path_definition.upper() == TemplateSections.RESOURCES.value.upper():
158                             skipped_checks = ContextParser.collect_skip_comments(
159                                 entity_code_lines=code_lines,
160                                 resource_config=attr_value,
161                             )
162                             dpath.new(
163                                 definitions_context,
164                                 [file_path, str(file_path_definition), str(attribute), "skipped_checks"],
165                                 skipped_checks,
166                             )
167     return definitions_context
168 
169 
170 def create_definitions(
171         root_folder: str,
172         files: list[str] | None = None,
173         runner_filter: RunnerFilter | None = None,
174         out_parsing_errors: dict[str, str] | None = None
175 ) -> tuple[dict[str, DictNode], dict[str, list[tuple[int, str]]]]:
176     runner_filter = runner_filter or RunnerFilter()
177     out_parsing_errors = {} if out_parsing_errors is None else out_parsing_errors
178     definitions: dict[str, DictNode] = {}
179     definitions_raw: dict[str, list[tuple[int, str]]] = {}
180     if files:
181         files_list = [file for file in files if os.path.splitext(file)[1] in CF_POSSIBLE_ENDINGS]
182         definitions, definitions_raw = get_files_definitions(files_list, out_parsing_errors)
183 
184     if root_folder:
185         definitions, definitions_raw = get_folder_definitions(root_folder, runner_filter.excluded_paths,
186                                                               out_parsing_errors)
187 
188     return definitions, definitions_raw
189 
190 
191 def get_files_definitions(files: List[str], out_parsing_errors: Dict[str, str], filepath_fn=None) \
192         -> Tuple[Dict[str, DictNode], Dict[str, List[Tuple[int, str]]]]:
193     def _parse_file(file):
194         parsing_errors = {}
195         result = parse(file, parsing_errors)
196         return (file, result), parsing_errors
197 
198     results = parallel_runner.run_function(_parse_file, files)
199 
200     definitions = {}
201     definitions_raw = {}
202     for result, parsing_errors in results:
203         out_parsing_errors.update(parsing_errors)
204         (file, parse_result) = result
205         path = filepath_fn(file) if filepath_fn else file
206         try:
207             template, template_lines = parse_result
208             if isinstance(template, DictNode) and isinstance(template.get("Resources"), DictNode):
209                 if validate_properties_in_resources_are_dict(template):
210                     definitions[path] = template
211                     definitions_raw[path] = template_lines
212                 else:
213                     out_parsing_errors.update({file: 'Resource Properties is not a dictionary'})
214             else:
215                 if parsing_errors:
216                     logging.debug(f'File {file} had the following parsing errors: {parsing_errors}')
217                 logging.debug(f"Parsed file {file} incorrectly {template}")
218         except (TypeError, ValueError):
219             logging.warning(f"CloudFormation skipping {file} as it is not a valid CF template")
220             continue
221 
222     return definitions, definitions_raw
223 
224 
225 def validate_properties_in_resources_are_dict(template: DictNode) -> bool:
226     template_resources = template.get("Resources")
227     for resource_name, resource in template_resources.items():
228         if 'Properties' in resource and not isinstance(resource['Properties'], DictNode) or "." in resource_name:
229             return False
230     return True
231
```
--- 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/checkov/cloudformation/cfn_utils.py b/checkov/cloudformation/cfn_utils.py
--- a/checkov/cloudformation/cfn_utils.py
+++ b/checkov/cloudformation/cfn_utils.py
@@ -17,6 +17,7 @@
 from checkov.common.models.consts import YAML_COMMENT_MARK
 
 CF_POSSIBLE_ENDINGS = frozenset([".yml", ".yaml", ".json", ".template"])
+TAG_FIELD_NAMES = ("Key", "Value")
 
 
 def get_resource_tags(entity: Dict[StrNode, DictNode], registry: Registry = cfn_registry) -> Optional[Dict[str, str]]:
@@ -43,8 +44,12 @@
 
 
 def parse_entity_tags(tags: Union[ListNode, Dict[str, Any]]) -> Optional[Dict[str, str]]:
-    if isinstance(tags, ListNode):
-        tag_dict = {get_entity_value_as_string(tag["Key"]): get_entity_value_as_string(tag["Value"]) for tag in tags}
+    if isinstance(tags, list):
+        tag_dict = {
+            get_entity_value_as_string(tag["Key"]): get_entity_value_as_string(tag["Value"])
+            for tag in tags
+            if all(field in tag for field in TAG_FIELD_NAMES)
+        }
         return tag_dict
     elif isinstance(tags, dict):
         tag_dict = {

{"golden_diff": "diff --git a/checkov/cloudformation/cfn_utils.py b/checkov/cloudformation/cfn_utils.py\n--- a/checkov/cloudformation/cfn_utils.py\n+++ b/checkov/cloudformation/cfn_utils.py\n@@ -17,6 +17,7 @@\n from checkov.common.models.consts import YAML_COMMENT_MARK\n \n CF_POSSIBLE_ENDINGS = frozenset([\".yml\", \".yaml\", \".json\", \".template\"])\n+TAG_FIELD_NAMES = (\"Key\", \"Value\")\n \n \n def get_resource_tags(entity: Dict[StrNode, DictNode], registry: Registry = cfn_registry) -> Optional[Dict[str, str]]:\n@@ -43,8 +44,12 @@\n \n \n def parse_entity_tags(tags: Union[ListNode, Dict[str, Any]]) -> Optional[Dict[str, str]]:\n-    if isinstance(tags, ListNode):\n-        tag_dict = {get_entity_value_as_string(tag[\"Key\"]): get_entity_value_as_string(tag[\"Value\"]) for tag in tags}\n+    if isinstance(tags, list):\n+        tag_dict = {\n+            get_entity_value_as_string(tag[\"Key\"]): get_entity_value_as_string(tag[\"Value\"])\n+            for tag in tags\n+            if all(field in tag for field in TAG_FIELD_NAMES)\n+        }\n         return tag_dict\n     elif isinstance(tags, dict):\n         tag_dict = {\n", "issue": "Error validando funciones en los tags\n**Describe the issue**\r\nWe are trying to execute checkov in a template which makes use of a function (IF)  in the tags:\r\n\r\n        - 'Fn::If':\r\n           - cPdnEnv\r\n           -\r\n             Key: \"Schedule\"\r\n             Value: \"VISOR-02\"\r\n           - !Ref \"AWS::NoValue\"    \r\n\r\nbut it throws the following error message:\r\n\r\n`Failed to parse tags for entity {'rEC2Instance': ...`\r\n\r\nWe updated to the latest version of checkov but the error continues\r\n\r\nAccording to the AWS [documentation](https://docs.aws.amazon.com/es_es/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-conditions.html#intrinsic-function-reference-conditions-if), this IF function can be used on tags.\r\n\n", "before_files": [{"content": "from __future__ import annotations\n\nimport logging\nimport os\nfrom typing import Optional, List, Tuple, Dict, Any, Union\n\nimport dpath.util\n\nfrom checkov.cloudformation.checks.resource.base_registry import Registry\nfrom checkov.cloudformation.checks.resource.registry import cfn_registry\nfrom checkov.cloudformation.context_parser import ContextParser, ENDLINE, STARTLINE\nfrom checkov.cloudformation.parser import parse, TemplateSections\nfrom checkov.common.parallelizer.parallel_runner import parallel_runner\nfrom checkov.common.parsers.node import DictNode, ListNode, StrNode\nfrom checkov.common.runners.base_runner import filter_ignored_paths\nfrom checkov.runner_filter import RunnerFilter\nfrom checkov.common.models.consts import YAML_COMMENT_MARK\n\nCF_POSSIBLE_ENDINGS = frozenset([\".yml\", \".yaml\", \".json\", \".template\"])\n\n\ndef get_resource_tags(entity: Dict[StrNode, DictNode], registry: Registry = cfn_registry) -> Optional[Dict[str, str]]:\n    entity_details = registry.extract_entity_details(entity)\n\n    if not entity_details:\n        return None\n\n    entity_config = entity_details[-1]\n\n    if not isinstance(entity_config, dict):\n        return None\n\n    try:\n        properties = entity_config.get(\"Properties\")\n        if properties:\n            tags = properties.get(\"Tags\")\n            if tags:\n                return parse_entity_tags(tags)\n    except Exception:\n        logging.warning(f\"Failed to parse tags for entity {entity}\")\n\n    return None\n\n\ndef parse_entity_tags(tags: Union[ListNode, Dict[str, Any]]) -> Optional[Dict[str, str]]:\n    if isinstance(tags, ListNode):\n        tag_dict = {get_entity_value_as_string(tag[\"Key\"]): get_entity_value_as_string(tag[\"Value\"]) for tag in tags}\n        return tag_dict\n    elif isinstance(tags, dict):\n        tag_dict = {\n            get_entity_value_as_string(key): get_entity_value_as_string(value)\n            for key, value in tags.items()\n            if key not in (STARTLINE, ENDLINE)\n        }\n        return tag_dict\n    return None\n\n\ndef get_entity_value_as_string(value: Any) -> str:\n    \"\"\"\n    Handles different type of entities with possible CFN function substitutions. Returns the simplest possible string value\n    (without performing any function calls).\n\n    Examples:\n    Key: Value  # returns simple string\n\n    Key: !Ref ${AWS::AccountId}-data  # returns ${AWS::AccountId}-data\n\n    Key:\n    - ${account}-data\n    - account: !Ref ${AWS::AccountId}\n\n    # returns ${account}-data\n\n    :param value:\n    :return:\n    \"\"\"\n    if isinstance(value, dict):\n        (function, value) = next(iter(value.items()))\n        # If the value is a long-form function, then the first element is the template string (technically str_node)\n        # Otherwise the dict value is the template string\n        if isinstance(value, list):\n            if \"Join\" in function:\n                # Join looks like !Join [, [V1, V2, V3]]\n                join_str = str(value[0])\n                return join_str.join([str(v) for v in value[1]])\n            else:\n                return str(value[0])\n        else:\n            return str(value)\n    else:\n        return str(value)\n\n\ndef get_folder_definitions(\n        root_folder: str, excluded_paths: list[str] | None, out_parsing_errors: dict[str, str] | None = None\n) -> tuple[dict[str, DictNode], dict[str, list[tuple[int, str]]]]:\n    out_parsing_errors = {} if out_parsing_errors is None else out_parsing_errors\n    files_list = []\n    for root, d_names, f_names in os.walk(root_folder):\n        filter_ignored_paths(root, d_names, excluded_paths)\n        filter_ignored_paths(root, f_names, excluded_paths)\n        for file in f_names:\n            file_ending = os.path.splitext(file)[1]\n            if file_ending in CF_POSSIBLE_ENDINGS:\n                files_list.append(os.path.join(root, file))\n\n    definitions, definitions_raw = get_files_definitions(files_list, out_parsing_errors)\n    return definitions, definitions_raw\n\n\ndef build_definitions_context(\n        definitions: Dict[str, DictNode], definitions_raw: Dict[str, List[Tuple[int, str]]]\n) -> Dict[str, Dict[str, Any]]:\n    definitions_context: Dict[str, Dict[str, Any]] = {}\n    # iterate on the files\n    for file_path, file_path_definitions in definitions.items():\n        # iterate on the definitions (Parameters, Resources, Outputs...)\n        for file_path_definition, definition in file_path_definitions.items():\n            if (\n                    isinstance(file_path_definition, StrNode)\n                    and file_path_definition.upper() in TemplateSections.__members__\n                    and isinstance(definition, DictNode)\n            ):\n                # iterate on the actual objects of each definition\n                for attribute, attr_value in definition.items():\n                    if isinstance(attr_value, DictNode):\n                        start_line = attr_value.start_mark.line\n                        end_line = attr_value.end_mark.line\n                        # fix lines number for yaml and json files\n                        first_line_index = 0\n                        while not str.strip(definitions_raw[file_path][first_line_index][1]):\n                            first_line_index += 1\n                        # check if the file is a json file\n                        if str.strip(definitions_raw[file_path][first_line_index][1])[0] == \"{\":\n                            start_line += 1\n                            end_line += 1\n                        else:\n                            # add resource comments to definition lines\n                            current_line = str.strip(definitions_raw[file_path][start_line - 1][1])\n                            while not current_line or current_line[0] == YAML_COMMENT_MARK:\n                                start_line -= 1\n                                current_line = str.strip(definitions_raw[file_path][start_line - 1][1])\n\n                            # remove next resource comments from definition lines\n                            current_line = str.strip(definitions_raw[file_path][end_line - 1][1])\n                            while not current_line or current_line[0] == YAML_COMMENT_MARK:\n                                end_line -= 1\n                                current_line = str.strip(definitions_raw[file_path][end_line - 1][1])\n\n                        code_lines = definitions_raw[file_path][start_line - 1: end_line]\n                        dpath.new(\n                            definitions_context,\n                            [file_path, str(file_path_definition), str(attribute)],\n                            {\"start_line\": start_line, \"end_line\": end_line, \"code_lines\": code_lines},\n                        )\n                        if file_path_definition.upper() == TemplateSections.RESOURCES.value.upper():\n                            skipped_checks = ContextParser.collect_skip_comments(\n                                entity_code_lines=code_lines,\n                                resource_config=attr_value,\n                            )\n                            dpath.new(\n                                definitions_context,\n                                [file_path, str(file_path_definition), str(attribute), \"skipped_checks\"],\n                                skipped_checks,\n                            )\n    return definitions_context\n\n\ndef create_definitions(\n        root_folder: str,\n        files: list[str] | None = None,\n        runner_filter: RunnerFilter | None = None,\n        out_parsing_errors: dict[str, str] | None = None\n) -> tuple[dict[str, DictNode], dict[str, list[tuple[int, str]]]]:\n    runner_filter = runner_filter or RunnerFilter()\n    out_parsing_errors = {} if out_parsing_errors is None else out_parsing_errors\n    definitions: dict[str, DictNode] = {}\n    definitions_raw: dict[str, list[tuple[int, str]]] = {}\n    if files:\n        files_list = [file for file in files if os.path.splitext(file)[1] in CF_POSSIBLE_ENDINGS]\n        definitions, definitions_raw = get_files_definitions(files_list, out_parsing_errors)\n\n    if root_folder:\n        definitions, definitions_raw = get_folder_definitions(root_folder, runner_filter.excluded_paths,\n                                                              out_parsing_errors)\n\n    return definitions, definitions_raw\n\n\ndef get_files_definitions(files: List[str], out_parsing_errors: Dict[str, str], filepath_fn=None) \\\n        -> Tuple[Dict[str, DictNode], Dict[str, List[Tuple[int, str]]]]:\n    def _parse_file(file):\n        parsing_errors = {}\n        result = parse(file, parsing_errors)\n        return (file, result), parsing_errors\n\n    results = parallel_runner.run_function(_parse_file, files)\n\n    definitions = {}\n    definitions_raw = {}\n    for result, parsing_errors in results:\n        out_parsing_errors.update(parsing_errors)\n        (file, parse_result) = result\n        path = filepath_fn(file) if filepath_fn else file\n        try:\n            template, template_lines = parse_result\n            if isinstance(template, DictNode) and isinstance(template.get(\"Resources\"), DictNode):\n                if validate_properties_in_resources_are_dict(template):\n                    definitions[path] = template\n                    definitions_raw[path] = template_lines\n                else:\n                    out_parsing_errors.update({file: 'Resource Properties is not a dictionary'})\n            else:\n                if parsing_errors:\n                    logging.debug(f'File {file} had the following parsing errors: {parsing_errors}')\n                logging.debug(f\"Parsed file {file} incorrectly {template}\")\n        except (TypeError, ValueError):\n            logging.warning(f\"CloudFormation skipping {file} as it is not a valid CF template\")\n            continue\n\n    return definitions, definitions_raw\n\n\ndef validate_properties_in_resources_are_dict(template: DictNode) -> bool:\n    template_resources = template.get(\"Resources\")\n    for resource_name, resource in template_resources.items():\n        if 'Properties' in resource and not isinstance(resource['Properties'], DictNode) or \".\" in resource_name:\n            return False\n    return True\n", "path": "checkov/cloudformation/cfn_utils.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport logging\nimport os\nfrom typing import Optional, List, Tuple, Dict, Any, Union\n\nimport dpath.util\n\nfrom checkov.cloudformation.checks.resource.base_registry import Registry\nfrom checkov.cloudformation.checks.resource.registry import cfn_registry\nfrom checkov.cloudformation.context_parser import ContextParser, ENDLINE, STARTLINE\nfrom checkov.cloudformation.parser import parse, TemplateSections\nfrom checkov.common.parallelizer.parallel_runner import parallel_runner\nfrom checkov.common.parsers.node import DictNode, ListNode, StrNode\nfrom checkov.common.runners.base_runner import filter_ignored_paths\nfrom checkov.runner_filter import RunnerFilter\nfrom checkov.common.models.consts import YAML_COMMENT_MARK\n\nCF_POSSIBLE_ENDINGS = frozenset([\".yml\", \".yaml\", \".json\", \".template\"])\nTAG_FIELD_NAMES = (\"Key\", \"Value\")\n\n\ndef get_resource_tags(entity: Dict[StrNode, DictNode], registry: Registry = cfn_registry) -> Optional[Dict[str, str]]:\n    entity_details = registry.extract_entity_details(entity)\n\n    if not entity_details:\n        return None\n\n    entity_config = entity_details[-1]\n\n    if not isinstance(entity_config, dict):\n        return None\n\n    try:\n        properties = entity_config.get(\"Properties\")\n        if properties:\n            tags = properties.get(\"Tags\")\n            if tags:\n                return parse_entity_tags(tags)\n    except Exception:\n        logging.warning(f\"Failed to parse tags for entity {entity}\")\n\n    return None\n\n\ndef parse_entity_tags(tags: Union[ListNode, Dict[str, Any]]) -> Optional[Dict[str, str]]:\n    if isinstance(tags, list):\n        tag_dict = {\n            get_entity_value_as_string(tag[\"Key\"]): get_entity_value_as_string(tag[\"Value\"])\n            for tag in tags\n            if all(field in tag for field in TAG_FIELD_NAMES)\n        }\n        return tag_dict\n    elif isinstance(tags, dict):\n        tag_dict = {\n            get_entity_value_as_string(key): get_entity_value_as_string(value)\n            for key, value in tags.items()\n            if key not in (STARTLINE, ENDLINE)\n        }\n        return tag_dict\n    return None\n\n\ndef get_entity_value_as_string(value: Any) -> str:\n    \"\"\"\n    Handles different type of entities with possible CFN function substitutions. Returns the simplest possible string value\n    (without performing any function calls).\n\n    Examples:\n    Key: Value  # returns simple string\n\n    Key: !Ref ${AWS::AccountId}-data  # returns ${AWS::AccountId}-data\n\n    Key:\n    - ${account}-data\n    - account: !Ref ${AWS::AccountId}\n\n    # returns ${account}-data\n\n    :param value:\n    :return:\n    \"\"\"\n    if isinstance(value, dict):\n        (function, value) = next(iter(value.items()))\n        # If the value is a long-form function, then the first element is the template string (technically str_node)\n        # Otherwise the dict value is the template string\n        if isinstance(value, list):\n            if \"Join\" in function:\n                # Join looks like !Join [, [V1, V2, V3]]\n                join_str = str(value[0])\n                return join_str.join([str(v) for v in value[1]])\n            else:\n                return str(value[0])\n        else:\n            return str(value)\n    else:\n        return str(value)\n\n\ndef get_folder_definitions(\n        root_folder: str, excluded_paths: list[str] | None, out_parsing_errors: dict[str, str] | None = None\n) -> tuple[dict[str, DictNode], dict[str, list[tuple[int, str]]]]:\n    out_parsing_errors = {} if out_parsing_errors is None else out_parsing_errors\n    files_list = []\n    for root, d_names, f_names in os.walk(root_folder):\n        filter_ignored_paths(root, d_names, excluded_paths)\n        filter_ignored_paths(root, f_names, excluded_paths)\n        for file in f_names:\n            file_ending = os.path.splitext(file)[1]\n            if file_ending in CF_POSSIBLE_ENDINGS:\n                files_list.append(os.path.join(root, file))\n\n    definitions, definitions_raw = get_files_definitions(files_list, out_parsing_errors)\n    return definitions, definitions_raw\n\n\ndef build_definitions_context(\n        definitions: Dict[str, DictNode], definitions_raw: Dict[str, List[Tuple[int, str]]]\n) -> Dict[str, Dict[str, Any]]:\n    definitions_context: Dict[str, Dict[str, Any]] = {}\n    # iterate on the files\n    for file_path, file_path_definitions in definitions.items():\n        # iterate on the definitions (Parameters, Resources, Outputs...)\n        for file_path_definition, definition in file_path_definitions.items():\n            if (\n                    isinstance(file_path_definition, StrNode)\n                    and file_path_definition.upper() in TemplateSections.__members__\n                    and isinstance(definition, DictNode)\n            ):\n                # iterate on the actual objects of each definition\n                for attribute, attr_value in definition.items():\n                    if isinstance(attr_value, DictNode):\n                        start_line = attr_value.start_mark.line\n                        end_line = attr_value.end_mark.line\n                        # fix lines number for yaml and json files\n                        first_line_index = 0\n                        while not str.strip(definitions_raw[file_path][first_line_index][1]):\n                            first_line_index += 1\n                        # check if the file is a json file\n                        if str.strip(definitions_raw[file_path][first_line_index][1])[0] == \"{\":\n                            start_line += 1\n                            end_line += 1\n                        else:\n                            # add resource comments to definition lines\n                            current_line = str.strip(definitions_raw[file_path][start_line - 1][1])\n                            while not current_line or current_line[0] == YAML_COMMENT_MARK:\n                                start_line -= 1\n                                current_line = str.strip(definitions_raw[file_path][start_line - 1][1])\n\n                            # remove next resource comments from definition lines\n                            current_line = str.strip(definitions_raw[file_path][end_line - 1][1])\n                            while not current_line or current_line[0] == YAML_COMMENT_MARK:\n                                end_line -= 1\n                                current_line = str.strip(definitions_raw[file_path][end_line - 1][1])\n\n                        code_lines = definitions_raw[file_path][start_line - 1: end_line]\n                        dpath.new(\n                            definitions_context,\n                            [file_path, str(file_path_definition), str(attribute)],\n                            {\"start_line\": start_line, \"end_line\": end_line, \"code_lines\": code_lines},\n                        )\n                        if file_path_definition.upper() == TemplateSections.RESOURCES.value.upper():\n                            skipped_checks = ContextParser.collect_skip_comments(\n                                entity_code_lines=code_lines,\n                                resource_config=attr_value,\n                            )\n                            dpath.new(\n                                definitions_context,\n                                [file_path, str(file_path_definition), str(attribute), \"skipped_checks\"],\n                                skipped_checks,\n                            )\n    return definitions_context\n\n\ndef create_definitions(\n        root_folder: str,\n        files: list[str] | None = None,\n        runner_filter: RunnerFilter | None = None,\n        out_parsing_errors: dict[str, str] | None = None\n) -> tuple[dict[str, DictNode], dict[str, list[tuple[int, str]]]]:\n    runner_filter = runner_filter or RunnerFilter()\n    out_parsing_errors = {} if out_parsing_errors is None else out_parsing_errors\n    definitions: dict[str, DictNode] = {}\n    definitions_raw: dict[str, list[tuple[int, str]]] = {}\n    if files:\n        files_list = [file for file in files if os.path.splitext(file)[1] in CF_POSSIBLE_ENDINGS]\n        definitions, definitions_raw = get_files_definitions(files_list, out_parsing_errors)\n\n    if root_folder:\n        definitions, definitions_raw = get_folder_definitions(root_folder, runner_filter.excluded_paths,\n                                                              out_parsing_errors)\n\n    return definitions, definitions_raw\n\n\ndef get_files_definitions(files: List[str], out_parsing_errors: Dict[str, str], filepath_fn=None) \\\n        -> Tuple[Dict[str, DictNode], Dict[str, List[Tuple[int, str]]]]:\n    def _parse_file(file):\n        parsing_errors = {}\n        result = parse(file, parsing_errors)\n        return (file, result), parsing_errors\n\n    results = parallel_runner.run_function(_parse_file, files)\n\n    definitions = {}\n    definitions_raw = {}\n    for result, parsing_errors in results:\n        out_parsing_errors.update(parsing_errors)\n        (file, parse_result) = result\n        path = filepath_fn(file) if filepath_fn else file\n        try:\n            template, template_lines = parse_result\n            if isinstance(template, DictNode) and isinstance(template.get(\"Resources\"), DictNode):\n                if validate_properties_in_resources_are_dict(template):\n                    definitions[path] = template\n                    definitions_raw[path] = template_lines\n                else:\n                    out_parsing_errors.update({file: 'Resource Properties is not a dictionary'})\n            else:\n                if parsing_errors:\n                    logging.debug(f'File {file} had the following parsing errors: {parsing_errors}')\n                logging.debug(f\"Parsed file {file} incorrectly {template}\")\n        except (TypeError, ValueError):\n            logging.warning(f\"CloudFormation skipping {file} as it is not a valid CF template\")\n            continue\n\n    return definitions, definitions_raw\n\n\ndef validate_properties_in_resources_are_dict(template: DictNode) -> bool:\n    template_resources = template.get(\"Resources\")\n    for resource_name, resource in template_resources.items():\n        if 'Properties' in resource and not isinstance(resource['Properties'], DictNode) or \".\" in resource_name:\n            return False\n    return True\n", "path": "checkov/cloudformation/cfn_utils.py"}]}