We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
AWS Route 53 checks (CKV2_AWS_38 and CKV_AWS_39) false positive in Terraform Plan framework
**Describe the issue**
AWS Route 53 DNSSEC and Query Logging checks flag as false positive in Terraform Plan framework.

**Examples**
Use Hashicorp's basic example to reproduce

**Version (please complete the following information):**
 - 2.1.226

**Additional context**
Create a new file/project using the Hashicorp example given
Run a vanilla check: `checkov -f foo.tf --quiet --compact -c CKV2_AWS_39`. Observe no issues.
Create a plan and format it with json: `terraform plan -out tfplan.bin && terraform state show tfplan.bin > tfplan.json`
Check it with terraform_plan framework: `checkov -f tfplan.json --framework terraform_plan --compact --quiet -c CKV2_AWS_39`. Observe it fail.

This same behavior occurs with CKV2_AWS_38 as well using the same basic example, and again--only with Terraform Plan: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_hosted_zone_dnssec


--- 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/terraform/plan_parser.py`
Content:
```
1 from __future__ import annotations
2 
3 import itertools
4 from typing import Optional, Tuple, Dict, List, Any
5 
6 from checkov.common.parsers.node import DictNode, ListNode
7 from checkov.terraform.context_parsers.tf_plan import parse
8 
9 SIMPLE_TYPES = (str, int, float, bool)
10 TF_PLAN_RESOURCE_ADDRESS = "__address__"
11 TF_PLAN_RESOURCE_CHANGE_ACTIONS = "__change_actions__"
12 
13 
14 def _is_simple_type(obj: Any) -> bool:
15     if obj is None:
16         return True
17     if isinstance(obj, SIMPLE_TYPES):
18         return True
19     return False
20 
21 
22 def _is_list_of_simple_types(obj: Any) -> bool:
23     if not isinstance(obj, list):
24         return False
25     for i in obj:
26         if not _is_simple_type(i):
27             return False
28     return True
29 
30 
31 def _is_list_of_dicts(obj: Any) -> bool:
32     if not isinstance(obj, list):
33         return False
34     for i in obj:
35         if isinstance(i, dict):
36             return True
37     return False
38 
39 
40 def _hclify(obj: DictNode, conf: Optional[DictNode] = None, parent_key: Optional[str] = None) -> Dict[str, List[Any]]:
41     ret_dict = {}
42     if not isinstance(obj, dict):
43         raise Exception("this method receives only dicts")
44     if hasattr(obj, "start_mark") and hasattr(obj, "end_mark"):
45         obj["start_line"] = obj.start_mark.line
46         obj["end_line"] = obj.end_mark.line
47     for key, value in obj.items():
48         if _is_simple_type(value) or _is_list_of_simple_types(value):
49             if parent_key == "tags":
50                 ret_dict[key] = value
51             else:
52                 ret_dict[key] = _clean_simple_type_list([value])
53 
54         if _is_list_of_dicts(value):
55             child_list = []
56             conf_val = conf.get(key, []) if conf else []
57             for internal_val, internal_conf_val in itertools.zip_longest(value, conf_val):
58                 if isinstance(internal_val, dict):
59                     child_list.append(_hclify(internal_val, internal_conf_val, parent_key=key))
60             if key == "tags":
61                 ret_dict[key] = [child_list]
62             else:
63                 ret_dict[key] = child_list
64         if isinstance(value, dict):
65             child_dict = _hclify(value, parent_key=key)
66             if parent_key == "tags":
67                 ret_dict[key] = child_dict
68             else:
69                 ret_dict[key] = [child_dict]
70     if conf and isinstance(conf, dict):
71         found_ref = False
72         for conf_key in conf.keys() - obj.keys():
73             ref = next((x for x in conf[conf_key].get("references", []) if not x.startswith(("var.", "local."))), None)
74             if ref:
75                 ret_dict[conf_key] = [ref]
76                 found_ref = True
77         if not found_ref:
78             for value in conf.values():
79                 if isinstance(value, dict) and "references" in value.keys():
80                     ret_dict["references_"] = value["references"]
81 
82     return ret_dict
83 
84 
85 def _prepare_resource_block(
86     resource: DictNode, conf: Optional[DictNode], resource_changes: dict[str, dict[str, Any]]
87 ) -> tuple[dict[str, dict[str, Any]], bool]:
88     """hclify resource if pre-conditions met.
89 
90     :param resource: tf planned_values resource block
91     :param conf: tf configuration resource block
92     :param resource_changes: tf resource_changes block
93 
94     :returns:
95         - resource_block: a list of strings representing the header columns
96         - prepared: whether conditions met to prepare data
97     """
98 
99     resource_block: Dict[str, Dict[str, Any]] = {}
100     resource_block[resource["type"]] = {}
101     prepared = False
102     mode = ""
103     if "mode" in resource:
104         mode = resource.get("mode")
105     # Rare cases where data block appears in resources with same name as resource block and only partial values
106     # and where *_module resources don't have values field
107     if mode == "managed" and "values" in resource:
108         expressions = conf.get("expressions") if conf else None
109 
110         resource_conf = _hclify(resource["values"], expressions)
111         resource_address = resource.get("address")
112         resource_conf[TF_PLAN_RESOURCE_ADDRESS] = resource_address
113 
114         changes = resource_changes.get(resource_address)
115         if changes:
116             resource_conf[TF_PLAN_RESOURCE_CHANGE_ACTIONS] = changes.get("change", {}).get("actions") or []
117 
118         resource_block[resource["type"]][resource.get("name", "default")] = resource_conf
119         prepared = True
120     return resource_block, prepared
121 
122 
123 def _find_child_modules(
124     child_modules: ListNode, resource_changes: dict[str, dict[str, Any]], root_module_conf: dict[str, Any]
125 ) -> List[Dict[str, Dict[str, Any]]]:
126     """ Find all child modules if any. Including any amount of nested child modules.
127 
128     :param child_modules: list of terraform child_module objects
129     :param resource_changes: a resource address to resource changes dict
130     :param root_module_conf: configuration block of the root module
131     :returns:
132         list of terraform resource blocks
133     """
134 
135     resource_blocks = []
136     for child_module in child_modules:
137         nested_child_modules = child_module.get("child_modules", [])
138         if nested_child_modules:
139             nested_blocks = _find_child_modules(
140                 child_modules=nested_child_modules,
141                 resource_changes=resource_changes,
142                 root_module_conf=root_module_conf
143             )
144             for resource in nested_blocks:
145                 resource_blocks.append(resource)
146 
147         module_address = child_module.get("address", "")
148         module_call_resources = _get_module_call_resources(
149             module_address=module_address,
150             root_module_conf=root_module_conf,
151         )
152 
153         for resource in child_module.get("resources", []):
154             module_call_conf = None
155             if module_address and module_call_resources:
156                 module_call_conf = next(
157                     (
158                         module_call_resource
159                         for module_call_resource in module_call_resources
160                         if f"{module_address}.{module_call_resource['address']}" == resource["address"]
161                     ),
162                     None
163                 )
164 
165             resource_block, prepared = _prepare_resource_block(
166                 resource=resource,
167                 conf=module_call_conf,
168                 resource_changes=resource_changes,
169             )
170             if prepared is True:
171                 resource_blocks.append(resource_block)
172     return resource_blocks
173 
174 
175 def _get_module_call_resources(module_address: str, root_module_conf: dict[str, Any]) -> list[dict[str, Any]]:
176     """Extracts the resources from the 'module_calls' block under 'configuration'"""
177 
178     for module_name in module_address.split("."):
179         if module_name == "module":
180             # module names are always prefixed with 'module.', therefore skip it
181             continue
182         root_module_conf = root_module_conf.get("module_calls", {}).get(module_name, {}).get("module", {})
183 
184     return root_module_conf.get("resources", [])
185 
186 
187 def _get_resource_changes(template: dict[str, Any]) -> dict[str, dict[str, Any]]:
188     """Returns a resource address to resource changes dict"""
189 
190     resource_changes_map = {}
191 
192     resource_changes = template.get("resource_changes")
193     if resource_changes and isinstance(resource_changes, list):
194         resource_changes_map = {
195             change.get("address", ""): change
196             for change in resource_changes
197         }
198 
199     return resource_changes_map
200 
201 
202 def parse_tf_plan(tf_plan_file: str, out_parsing_errors: Dict[str, str]) -> Tuple[Optional[Dict[str, Any]], Optional[List[Tuple[int, str]]]]:
203     """
204     :type tf_plan_file: str - path to plan file
205     :rtype: tf_definition dictionary and template_lines of the plan file
206     """
207     tf_definition: Dict[str, Any] = {"resource": []}
208     template, template_lines = parse(tf_plan_file, out_parsing_errors)
209     if not template:
210         return None, None
211 
212     resource_changes = _get_resource_changes(template=template)
213 
214     for resource in template.get("planned_values", {}).get("root_module", {}).get("resources", []):
215         conf = next(
216             (
217                 x
218                 for x in template.get("configuration", {}).get("root_module", {}).get("resources", [])
219                 if x["type"] == resource["type"] and x["name"] == resource["name"]
220             ),
221             None,
222         )
223         resource_block, prepared = _prepare_resource_block(
224             resource=resource,
225             conf=conf,
226             resource_changes=resource_changes,
227         )
228         if prepared is True:
229             tf_definition["resource"].append(resource_block)
230     child_modules = template.get("planned_values", {}).get("root_module", {}).get("child_modules", [])
231     root_module_conf = template.get("configuration", {}).get("root_module", {})
232     # Terraform supports modules within modules so we need to search
233     # in nested modules to find all resource blocks
234     resource_blocks = _find_child_modules(
235         child_modules=child_modules,
236         resource_changes=resource_changes,
237         root_module_conf=root_module_conf,
238     )
239     for resource in resource_blocks:
240         tf_definition["resource"].append(resource)
241     return tf_definition, template_lines
242 
243 
244 def _clean_simple_type_list(value_list: List[Any]) -> List[Any]:
245     """
246     Given a list of simple types return a cleaned list of simple types.
247     Converts booleans that are input as strings back to booleans to maintain consistent expectations for later evaluation.
248     Sometimes Terraform Plan will output Map values as strings regardless of boolean input.
249     """
250     for i in range(len(value_list)):
251         if isinstance(value_list[i], str):
252             lower_case_value = value_list[i].lower()
253             if lower_case_value == "true":
254                 value_list[i] = True
255             if lower_case_value == "false":
256                 value_list[i] = False
257     return value_list
258
```
--- 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/terraform/plan_parser.py b/checkov/terraform/plan_parser.py
--- a/checkov/terraform/plan_parser.py
+++ b/checkov/terraform/plan_parser.py
@@ -70,7 +70,11 @@
     if conf and isinstance(conf, dict):
         found_ref = False
         for conf_key in conf.keys() - obj.keys():
-            ref = next((x for x in conf[conf_key].get("references", []) if not x.startswith(("var.", "local."))), None)
+            conf_value = conf[conf_key]
+            if not isinstance(conf_value, dict):
+                continue
+
+            ref = next((x for x in conf_value.get("references", []) if not x.startswith(("var.", "local."))), None)
             if ref:
                 ret_dict[conf_key] = [ref]
                 found_ref = True
@@ -104,10 +108,10 @@
         mode = resource.get("mode")
     # Rare cases where data block appears in resources with same name as resource block and only partial values
     # and where *_module resources don't have values field
-    if mode == "managed" and "values" in resource:
+    if mode == "managed":
         expressions = conf.get("expressions") if conf else None
 
-        resource_conf = _hclify(resource["values"], expressions)
+        resource_conf = _hclify(resource.get("values", {}), expressions)
         resource_address = resource.get("address")
         resource_conf[TF_PLAN_RESOURCE_ADDRESS] = resource_address

{"golden_diff": "diff --git a/checkov/terraform/plan_parser.py b/checkov/terraform/plan_parser.py\n--- a/checkov/terraform/plan_parser.py\n+++ b/checkov/terraform/plan_parser.py\n@@ -70,7 +70,11 @@\n     if conf and isinstance(conf, dict):\n         found_ref = False\n         for conf_key in conf.keys() - obj.keys():\n-            ref = next((x for x in conf[conf_key].get(\"references\", []) if not x.startswith((\"var.\", \"local.\"))), None)\n+            conf_value = conf[conf_key]\n+            if not isinstance(conf_value, dict):\n+                continue\n+\n+            ref = next((x for x in conf_value.get(\"references\", []) if not x.startswith((\"var.\", \"local.\"))), None)\n             if ref:\n                 ret_dict[conf_key] = [ref]\n                 found_ref = True\n@@ -104,10 +108,10 @@\n         mode = resource.get(\"mode\")\n     # Rare cases where data block appears in resources with same name as resource block and only partial values\n     # and where *_module resources don't have values field\n-    if mode == \"managed\" and \"values\" in resource:\n+    if mode == \"managed\":\n         expressions = conf.get(\"expressions\") if conf else None\n \n-        resource_conf = _hclify(resource[\"values\"], expressions)\n+        resource_conf = _hclify(resource.get(\"values\", {}), expressions)\n         resource_address = resource.get(\"address\")\n         resource_conf[TF_PLAN_RESOURCE_ADDRESS] = resource_address\n", "issue": "AWS Route 53 checks (CKV2_AWS_38 and CKV_AWS_39) false positive in Terraform Plan framework\n**Describe the issue**\r\nAWS Route 53 DNSSEC and Query Logging checks flag as false positive in Terraform Plan framework.\r\n\r\n**Examples**\r\nUse Hashicorp's basic example to reproduce\r\n\r\n**Version (please complete the following information):**\r\n - 2.1.226\r\n\r\n**Additional context**\r\nCreate a new file/project using the Hashicorp example given\r\nRun a vanilla check: `checkov -f foo.tf --quiet --compact -c CKV2_AWS_39`. Observe no issues.\r\nCreate a plan and format it with json: `terraform plan -out tfplan.bin && terraform state show tfplan.bin > tfplan.json`\r\nCheck it with terraform_plan framework: `checkov -f tfplan.json --framework terraform_plan --compact --quiet -c CKV2_AWS_39`. Observe it fail.\r\n\r\nThis same behavior occurs with CKV2_AWS_38 as well using the same basic example, and again--only with Terraform Plan: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_hosted_zone_dnssec\r\n\n", "before_files": [{"content": "from __future__ import annotations\n\nimport itertools\nfrom typing import Optional, Tuple, Dict, List, Any\n\nfrom checkov.common.parsers.node import DictNode, ListNode\nfrom checkov.terraform.context_parsers.tf_plan import parse\n\nSIMPLE_TYPES = (str, int, float, bool)\nTF_PLAN_RESOURCE_ADDRESS = \"__address__\"\nTF_PLAN_RESOURCE_CHANGE_ACTIONS = \"__change_actions__\"\n\n\ndef _is_simple_type(obj: Any) -> bool:\n    if obj is None:\n        return True\n    if isinstance(obj, SIMPLE_TYPES):\n        return True\n    return False\n\n\ndef _is_list_of_simple_types(obj: Any) -> bool:\n    if not isinstance(obj, list):\n        return False\n    for i in obj:\n        if not _is_simple_type(i):\n            return False\n    return True\n\n\ndef _is_list_of_dicts(obj: Any) -> bool:\n    if not isinstance(obj, list):\n        return False\n    for i in obj:\n        if isinstance(i, dict):\n            return True\n    return False\n\n\ndef _hclify(obj: DictNode, conf: Optional[DictNode] = None, parent_key: Optional[str] = None) -> Dict[str, List[Any]]:\n    ret_dict = {}\n    if not isinstance(obj, dict):\n        raise Exception(\"this method receives only dicts\")\n    if hasattr(obj, \"start_mark\") and hasattr(obj, \"end_mark\"):\n        obj[\"start_line\"] = obj.start_mark.line\n        obj[\"end_line\"] = obj.end_mark.line\n    for key, value in obj.items():\n        if _is_simple_type(value) or _is_list_of_simple_types(value):\n            if parent_key == \"tags\":\n                ret_dict[key] = value\n            else:\n                ret_dict[key] = _clean_simple_type_list([value])\n\n        if _is_list_of_dicts(value):\n            child_list = []\n            conf_val = conf.get(key, []) if conf else []\n            for internal_val, internal_conf_val in itertools.zip_longest(value, conf_val):\n                if isinstance(internal_val, dict):\n                    child_list.append(_hclify(internal_val, internal_conf_val, parent_key=key))\n            if key == \"tags\":\n                ret_dict[key] = [child_list]\n            else:\n                ret_dict[key] = child_list\n        if isinstance(value, dict):\n            child_dict = _hclify(value, parent_key=key)\n            if parent_key == \"tags\":\n                ret_dict[key] = child_dict\n            else:\n                ret_dict[key] = [child_dict]\n    if conf and isinstance(conf, dict):\n        found_ref = False\n        for conf_key in conf.keys() - obj.keys():\n            ref = next((x for x in conf[conf_key].get(\"references\", []) if not x.startswith((\"var.\", \"local.\"))), None)\n            if ref:\n                ret_dict[conf_key] = [ref]\n                found_ref = True\n        if not found_ref:\n            for value in conf.values():\n                if isinstance(value, dict) and \"references\" in value.keys():\n                    ret_dict[\"references_\"] = value[\"references\"]\n\n    return ret_dict\n\n\ndef _prepare_resource_block(\n    resource: DictNode, conf: Optional[DictNode], resource_changes: dict[str, dict[str, Any]]\n) -> tuple[dict[str, dict[str, Any]], bool]:\n    \"\"\"hclify resource if pre-conditions met.\n\n    :param resource: tf planned_values resource block\n    :param conf: tf configuration resource block\n    :param resource_changes: tf resource_changes block\n\n    :returns:\n        - resource_block: a list of strings representing the header columns\n        - prepared: whether conditions met to prepare data\n    \"\"\"\n\n    resource_block: Dict[str, Dict[str, Any]] = {}\n    resource_block[resource[\"type\"]] = {}\n    prepared = False\n    mode = \"\"\n    if \"mode\" in resource:\n        mode = resource.get(\"mode\")\n    # Rare cases where data block appears in resources with same name as resource block and only partial values\n    # and where *_module resources don't have values field\n    if mode == \"managed\" and \"values\" in resource:\n        expressions = conf.get(\"expressions\") if conf else None\n\n        resource_conf = _hclify(resource[\"values\"], expressions)\n        resource_address = resource.get(\"address\")\n        resource_conf[TF_PLAN_RESOURCE_ADDRESS] = resource_address\n\n        changes = resource_changes.get(resource_address)\n        if changes:\n            resource_conf[TF_PLAN_RESOURCE_CHANGE_ACTIONS] = changes.get(\"change\", {}).get(\"actions\") or []\n\n        resource_block[resource[\"type\"]][resource.get(\"name\", \"default\")] = resource_conf\n        prepared = True\n    return resource_block, prepared\n\n\ndef _find_child_modules(\n    child_modules: ListNode, resource_changes: dict[str, dict[str, Any]], root_module_conf: dict[str, Any]\n) -> List[Dict[str, Dict[str, Any]]]:\n    \"\"\" Find all child modules if any. Including any amount of nested child modules.\n\n    :param child_modules: list of terraform child_module objects\n    :param resource_changes: a resource address to resource changes dict\n    :param root_module_conf: configuration block of the root module\n    :returns:\n        list of terraform resource blocks\n    \"\"\"\n\n    resource_blocks = []\n    for child_module in child_modules:\n        nested_child_modules = child_module.get(\"child_modules\", [])\n        if nested_child_modules:\n            nested_blocks = _find_child_modules(\n                child_modules=nested_child_modules,\n                resource_changes=resource_changes,\n                root_module_conf=root_module_conf\n            )\n            for resource in nested_blocks:\n                resource_blocks.append(resource)\n\n        module_address = child_module.get(\"address\", \"\")\n        module_call_resources = _get_module_call_resources(\n            module_address=module_address,\n            root_module_conf=root_module_conf,\n        )\n\n        for resource in child_module.get(\"resources\", []):\n            module_call_conf = None\n            if module_address and module_call_resources:\n                module_call_conf = next(\n                    (\n                        module_call_resource\n                        for module_call_resource in module_call_resources\n                        if f\"{module_address}.{module_call_resource['address']}\" == resource[\"address\"]\n                    ),\n                    None\n                )\n\n            resource_block, prepared = _prepare_resource_block(\n                resource=resource,\n                conf=module_call_conf,\n                resource_changes=resource_changes,\n            )\n            if prepared is True:\n                resource_blocks.append(resource_block)\n    return resource_blocks\n\n\ndef _get_module_call_resources(module_address: str, root_module_conf: dict[str, Any]) -> list[dict[str, Any]]:\n    \"\"\"Extracts the resources from the 'module_calls' block under 'configuration'\"\"\"\n\n    for module_name in module_address.split(\".\"):\n        if module_name == \"module\":\n            # module names are always prefixed with 'module.', therefore skip it\n            continue\n        root_module_conf = root_module_conf.get(\"module_calls\", {}).get(module_name, {}).get(\"module\", {})\n\n    return root_module_conf.get(\"resources\", [])\n\n\ndef _get_resource_changes(template: dict[str, Any]) -> dict[str, dict[str, Any]]:\n    \"\"\"Returns a resource address to resource changes dict\"\"\"\n\n    resource_changes_map = {}\n\n    resource_changes = template.get(\"resource_changes\")\n    if resource_changes and isinstance(resource_changes, list):\n        resource_changes_map = {\n            change.get(\"address\", \"\"): change\n            for change in resource_changes\n        }\n\n    return resource_changes_map\n\n\ndef parse_tf_plan(tf_plan_file: str, out_parsing_errors: Dict[str, str]) -> Tuple[Optional[Dict[str, Any]], Optional[List[Tuple[int, str]]]]:\n    \"\"\"\n    :type tf_plan_file: str - path to plan file\n    :rtype: tf_definition dictionary and template_lines of the plan file\n    \"\"\"\n    tf_definition: Dict[str, Any] = {\"resource\": []}\n    template, template_lines = parse(tf_plan_file, out_parsing_errors)\n    if not template:\n        return None, None\n\n    resource_changes = _get_resource_changes(template=template)\n\n    for resource in template.get(\"planned_values\", {}).get(\"root_module\", {}).get(\"resources\", []):\n        conf = next(\n            (\n                x\n                for x in template.get(\"configuration\", {}).get(\"root_module\", {}).get(\"resources\", [])\n                if x[\"type\"] == resource[\"type\"] and x[\"name\"] == resource[\"name\"]\n            ),\n            None,\n        )\n        resource_block, prepared = _prepare_resource_block(\n            resource=resource,\n            conf=conf,\n            resource_changes=resource_changes,\n        )\n        if prepared is True:\n            tf_definition[\"resource\"].append(resource_block)\n    child_modules = template.get(\"planned_values\", {}).get(\"root_module\", {}).get(\"child_modules\", [])\n    root_module_conf = template.get(\"configuration\", {}).get(\"root_module\", {})\n    # Terraform supports modules within modules so we need to search\n    # in nested modules to find all resource blocks\n    resource_blocks = _find_child_modules(\n        child_modules=child_modules,\n        resource_changes=resource_changes,\n        root_module_conf=root_module_conf,\n    )\n    for resource in resource_blocks:\n        tf_definition[\"resource\"].append(resource)\n    return tf_definition, template_lines\n\n\ndef _clean_simple_type_list(value_list: List[Any]) -> List[Any]:\n    \"\"\"\n    Given a list of simple types return a cleaned list of simple types.\n    Converts booleans that are input as strings back to booleans to maintain consistent expectations for later evaluation.\n    Sometimes Terraform Plan will output Map values as strings regardless of boolean input.\n    \"\"\"\n    for i in range(len(value_list)):\n        if isinstance(value_list[i], str):\n            lower_case_value = value_list[i].lower()\n            if lower_case_value == \"true\":\n                value_list[i] = True\n            if lower_case_value == \"false\":\n                value_list[i] = False\n    return value_list\n", "path": "checkov/terraform/plan_parser.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport itertools\nfrom typing import Optional, Tuple, Dict, List, Any\n\nfrom checkov.common.parsers.node import DictNode, ListNode\nfrom checkov.terraform.context_parsers.tf_plan import parse\n\nSIMPLE_TYPES = (str, int, float, bool)\nTF_PLAN_RESOURCE_ADDRESS = \"__address__\"\nTF_PLAN_RESOURCE_CHANGE_ACTIONS = \"__change_actions__\"\n\n\ndef _is_simple_type(obj: Any) -> bool:\n    if obj is None:\n        return True\n    if isinstance(obj, SIMPLE_TYPES):\n        return True\n    return False\n\n\ndef _is_list_of_simple_types(obj: Any) -> bool:\n    if not isinstance(obj, list):\n        return False\n    for i in obj:\n        if not _is_simple_type(i):\n            return False\n    return True\n\n\ndef _is_list_of_dicts(obj: Any) -> bool:\n    if not isinstance(obj, list):\n        return False\n    for i in obj:\n        if isinstance(i, dict):\n            return True\n    return False\n\n\ndef _hclify(obj: DictNode, conf: Optional[DictNode] = None, parent_key: Optional[str] = None) -> Dict[str, List[Any]]:\n    ret_dict = {}\n    if not isinstance(obj, dict):\n        raise Exception(\"this method receives only dicts\")\n    if hasattr(obj, \"start_mark\") and hasattr(obj, \"end_mark\"):\n        obj[\"start_line\"] = obj.start_mark.line\n        obj[\"end_line\"] = obj.end_mark.line\n    for key, value in obj.items():\n        if _is_simple_type(value) or _is_list_of_simple_types(value):\n            if parent_key == \"tags\":\n                ret_dict[key] = value\n            else:\n                ret_dict[key] = _clean_simple_type_list([value])\n\n        if _is_list_of_dicts(value):\n            child_list = []\n            conf_val = conf.get(key, []) if conf else []\n            for internal_val, internal_conf_val in itertools.zip_longest(value, conf_val):\n                if isinstance(internal_val, dict):\n                    child_list.append(_hclify(internal_val, internal_conf_val, parent_key=key))\n            if key == \"tags\":\n                ret_dict[key] = [child_list]\n            else:\n                ret_dict[key] = child_list\n        if isinstance(value, dict):\n            child_dict = _hclify(value, parent_key=key)\n            if parent_key == \"tags\":\n                ret_dict[key] = child_dict\n            else:\n                ret_dict[key] = [child_dict]\n    if conf and isinstance(conf, dict):\n        found_ref = False\n        for conf_key in conf.keys() - obj.keys():\n            conf_value = conf[conf_key]\n            if not isinstance(conf_value, dict):\n                continue\n\n            ref = next((x for x in conf_value.get(\"references\", []) if not x.startswith((\"var.\", \"local.\"))), None)\n            if ref:\n                ret_dict[conf_key] = [ref]\n                found_ref = True\n        if not found_ref:\n            for value in conf.values():\n                if isinstance(value, dict) and \"references\" in value.keys():\n                    ret_dict[\"references_\"] = value[\"references\"]\n\n    return ret_dict\n\n\ndef _prepare_resource_block(\n    resource: DictNode, conf: Optional[DictNode], resource_changes: dict[str, dict[str, Any]]\n) -> tuple[dict[str, dict[str, Any]], bool]:\n    \"\"\"hclify resource if pre-conditions met.\n\n    :param resource: tf planned_values resource block\n    :param conf: tf configuration resource block\n    :param resource_changes: tf resource_changes block\n\n    :returns:\n        - resource_block: a list of strings representing the header columns\n        - prepared: whether conditions met to prepare data\n    \"\"\"\n\n    resource_block: Dict[str, Dict[str, Any]] = {}\n    resource_block[resource[\"type\"]] = {}\n    prepared = False\n    mode = \"\"\n    if \"mode\" in resource:\n        mode = resource.get(\"mode\")\n    # Rare cases where data block appears in resources with same name as resource block and only partial values\n    # and where *_module resources don't have values field\n    if mode == \"managed\":\n        expressions = conf.get(\"expressions\") if conf else None\n\n        resource_conf = _hclify(resource.get(\"values\", {}), expressions)\n        resource_address = resource.get(\"address\")\n        resource_conf[TF_PLAN_RESOURCE_ADDRESS] = resource_address\n\n        changes = resource_changes.get(resource_address)\n        if changes:\n            resource_conf[TF_PLAN_RESOURCE_CHANGE_ACTIONS] = changes.get(\"change\", {}).get(\"actions\") or []\n\n        resource_block[resource[\"type\"]][resource.get(\"name\", \"default\")] = resource_conf\n        prepared = True\n    return resource_block, prepared\n\n\ndef _find_child_modules(\n    child_modules: ListNode, resource_changes: dict[str, dict[str, Any]], root_module_conf: dict[str, Any]\n) -> List[Dict[str, Dict[str, Any]]]:\n    \"\"\" Find all child modules if any. Including any amount of nested child modules.\n\n    :param child_modules: list of terraform child_module objects\n    :param resource_changes: a resource address to resource changes dict\n    :param root_module_conf: configuration block of the root module\n    :returns:\n        list of terraform resource blocks\n    \"\"\"\n\n    resource_blocks = []\n    for child_module in child_modules:\n        nested_child_modules = child_module.get(\"child_modules\", [])\n        if nested_child_modules:\n            nested_blocks = _find_child_modules(\n                child_modules=nested_child_modules,\n                resource_changes=resource_changes,\n                root_module_conf=root_module_conf\n            )\n            for resource in nested_blocks:\n                resource_blocks.append(resource)\n\n        module_address = child_module.get(\"address\", \"\")\n        module_call_resources = _get_module_call_resources(\n            module_address=module_address,\n            root_module_conf=root_module_conf,\n        )\n\n        for resource in child_module.get(\"resources\", []):\n            module_call_conf = None\n            if module_address and module_call_resources:\n                module_call_conf = next(\n                    (\n                        module_call_resource\n                        for module_call_resource in module_call_resources\n                        if f\"{module_address}.{module_call_resource['address']}\" == resource[\"address\"]\n                    ),\n                    None\n                )\n\n            resource_block, prepared = _prepare_resource_block(\n                resource=resource,\n                conf=module_call_conf,\n                resource_changes=resource_changes,\n            )\n            if prepared is True:\n                resource_blocks.append(resource_block)\n    return resource_blocks\n\n\ndef _get_module_call_resources(module_address: str, root_module_conf: dict[str, Any]) -> list[dict[str, Any]]:\n    \"\"\"Extracts the resources from the 'module_calls' block under 'configuration'\"\"\"\n\n    for module_name in module_address.split(\".\"):\n        if module_name == \"module\":\n            # module names are always prefixed with 'module.', therefore skip it\n            continue\n        root_module_conf = root_module_conf.get(\"module_calls\", {}).get(module_name, {}).get(\"module\", {})\n\n    return root_module_conf.get(\"resources\", [])\n\n\ndef _get_resource_changes(template: dict[str, Any]) -> dict[str, dict[str, Any]]:\n    \"\"\"Returns a resource address to resource changes dict\"\"\"\n\n    resource_changes_map = {}\n\n    resource_changes = template.get(\"resource_changes\")\n    if resource_changes and isinstance(resource_changes, list):\n        resource_changes_map = {\n            change.get(\"address\", \"\"): change\n            for change in resource_changes\n        }\n\n    return resource_changes_map\n\n\ndef parse_tf_plan(tf_plan_file: str, out_parsing_errors: Dict[str, str]) -> Tuple[Optional[Dict[str, Any]], Optional[List[Tuple[int, str]]]]:\n    \"\"\"\n    :type tf_plan_file: str - path to plan file\n    :rtype: tf_definition dictionary and template_lines of the plan file\n    \"\"\"\n    tf_definition: Dict[str, Any] = {\"resource\": []}\n    template, template_lines = parse(tf_plan_file, out_parsing_errors)\n    if not template:\n        return None, None\n\n    resource_changes = _get_resource_changes(template=template)\n\n    for resource in template.get(\"planned_values\", {}).get(\"root_module\", {}).get(\"resources\", []):\n        conf = next(\n            (\n                x\n                for x in template.get(\"configuration\", {}).get(\"root_module\", {}).get(\"resources\", [])\n                if x[\"type\"] == resource[\"type\"] and x[\"name\"] == resource[\"name\"]\n            ),\n            None,\n        )\n        resource_block, prepared = _prepare_resource_block(\n            resource=resource,\n            conf=conf,\n            resource_changes=resource_changes,\n        )\n        if prepared is True:\n            tf_definition[\"resource\"].append(resource_block)\n    child_modules = template.get(\"planned_values\", {}).get(\"root_module\", {}).get(\"child_modules\", [])\n    root_module_conf = template.get(\"configuration\", {}).get(\"root_module\", {})\n    # Terraform supports modules within modules so we need to search\n    # in nested modules to find all resource blocks\n    resource_blocks = _find_child_modules(\n        child_modules=child_modules,\n        resource_changes=resource_changes,\n        root_module_conf=root_module_conf,\n    )\n    for resource in resource_blocks:\n        tf_definition[\"resource\"].append(resource)\n    return tf_definition, template_lines\n\n\ndef _clean_simple_type_list(value_list: List[Any]) -> List[Any]:\n    \"\"\"\n    Given a list of simple types return a cleaned list of simple types.\n    Converts booleans that are input as strings back to booleans to maintain consistent expectations for later evaluation.\n    Sometimes Terraform Plan will output Map values as strings regardless of boolean input.\n    \"\"\"\n    for i in range(len(value_list)):\n        if isinstance(value_list[i], str):\n            lower_case_value = value_list[i].lower()\n            if lower_case_value == \"true\":\n                value_list[i] = True\n            if lower_case_value == \"false\":\n                value_list[i] = False\n    return value_list\n", "path": "checkov/terraform/plan_parser.py"}]}