We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Resources that contain scalar fields named "dynamic" not parsing properly
**Describe the issue**

Running checkov against a Terraform resource which includes a scalar field called "dynamic" which _is not_ an actual HCL `dynamic` block causes a crash.

**Examples**

Using the below resource definition:

```hcl
resource "elasticsearch_index_template" "logstash" {
  body = jsonencode(
    {
      index_patterns = [
        "test-*",
      ]
      mappings = {
        properties = {
          foo = {
            # Note that "dynamic" here is not HCL dynamic block, but rather an elasticsearch property declared using HCL
            dynamic = true
            properties = {
              bar = {
                type = "string"
              }
            }
          }
        }
      }
    }
  )

  name = "test"
}
```

We found this bug while attempting to write a custom check to flag up use of the `provisioner` block which, since it is a [Terraform configuration](https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax) available to all `resource` blocks, requires a custom check where `supported_resources = ("*")`:

```python
from typing import Any
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
from checkov.common.models.enums import CheckResult, CheckCategories


class DynamicTest(BaseResourceCheck):
    def __init__(self) -> None:
        name = "Testing crash where dynamic is a scalar value"
        id = "CKV_TEST_1"
        supported_resources = ("*",)
        categories = (CheckCategories.GENERAL_SECURITY,)
        super().__init__(
            name=name,
            id=id,
            categories=categories,
            supported_resources=supported_resources,
        )

    def scan_resource_conf(self, conf: dict[str, list[Any]]) -> CheckResult:
        return CheckResult.PASSED


check = DynamicTest()
```

We can then run checkov:

```shell
$ checkov -f test.tf --external-checks-dir=./checks
```

**Exception Trace**

The relevant part of the trace caused by this issue is below:

```
2023-10-13 10:22:17,018 [MainThread  ] [DEBUG]  Running check: Testing crash where dynamic is a scalar value on file /test.tf
2023-10-13 10:22:17,019 [MainThread  ] [ERROR]  Failed to run check CKV_TEST_1 on /test.tf:elasticsearch_index_template.test
Traceback (most recent call last):
  File "/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/common/checks/base_check.py", line 75, in run
    check_result["result"] = self.scan_entity_conf(entity_configuration, entity_type)
  File "/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/checks/resource/base_resource_check.py", line 42, in scan_entity_conf
    handle_dynamic_values(conf)
  File "/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/parser_functions.py", line 186, in handle_dynamic_values
    has_dynamic_block = handle_dynamic_values(conf_block[0], has_dynamic_block)
  File "/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/parser_functions.py", line 182, in handle_dynamic_values
    has_dynamic_block = handle_dynamic_values(conf_block, has_dynamic_block)
  File "/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/parser_functions.py", line 182, in handle_dynamic_values
    has_dynamic_block = handle_dynamic_values(conf_block, has_dynamic_block)
  File "/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/parser_functions.py", line 182, in handle_dynamic_values
    has_dynamic_block = handle_dynamic_values(conf_block, has_dynamic_block)
  File "/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/parser_functions.py", line 189, in handle_dynamic_values
    return process_dynamic_values(conf) or has_dynamic_block
  File "/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/parser_functions.py", line 195, in process_dynamic_values
    for dynamic_element in conf.get("dynamic", {}):
TypeError: 'bool' object is not iterable
2023-10-13 10:22:17,020 [MainThread  ] [INFO ]  Entity configuration: {'__end_line__': 24, '__start_line__': 1, 'body': [{'index_patterns': ['test-*'], 'mappings': {'properties': {'foo': {'dynamic': True, 'properties': {'bar': {'type': 'string'}}}}}}], 'name': ['test'], '__address__': 'elasticsearch_index_template.test'}
```

**Desktop (please complete the following information):**

 - OS: MacOS 13.4.1 (Ventura)
 - Checkov Version: 2.5.8

**Additional context**

I believe this is due to the [process_dynamic_values](https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/parser_functions.py#L194) method, which directly fetches the "dynamic" property of `conf` but does not verify its Iterable-ness before trying to loop through and parse its elements.


--- 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/parser_functions.py`
Content:
```
1 from __future__ import annotations
2 
3 import json
4 import logging
5 from collections.abc import Hashable
6 from typing import Dict, List, Union, Any, Callable
7 
8 from checkov.common.util.data_structures_utils import pickle_deepcopy
9 from checkov.common.util.type_forcers import convert_str_to_bool
10 from checkov.common.util.parser_utils import eval_string, split_merge_args, string_to_native, to_string
11 
12 #
13 # Functions defined in this file implement terraform functions.
14 #
15 # Inputs:
16 #   - First arg (unnamed) - the value string provided to the function
17 #   - "var_resolver" - function pointer to resolve variable/local references and such
18 #   - "function_name" - name of the function being called (mainly useful for error reporting when a
19 #                       function isn't defined)
20 # These may be expanded over time, so accepting kwargs (via `**`) is recommended.
21 #
22 # If the value cannot be processed, `FUNCTION_FAILED` should be returned.
23 #
24 
25 FUNCTION_FAILED = "____FUNCTION_FAILED____"
26 
27 
28 def merge(original: str, var_resolver: Callable[[Any], Any], **_: Any) -> dict[Hashable, Any] | str:
29     # https://www.terraform.io/docs/language/functions/merge.html
30     args = split_merge_args(original)
31     if args is None:
32         return FUNCTION_FAILED
33     merged_map = {}
34     for arg in args:
35         if arg.startswith("{"):
36             arg_value = string_to_native(arg)
37             if arg_value is None:
38                 return FUNCTION_FAILED
39         else:
40             arg_value = var_resolver(arg)
41         if isinstance(arg_value, dict):
42             merged_map.update(arg_value)
43         else:
44             return FUNCTION_FAILED  # don't know what this is, blow out
45     return merged_map
46 
47 
48 def concat(original: str, var_resolver: Callable[[Any], Any], **_: Any) -> list[Any] | str:
49     # https://www.terraform.io/docs/language/functions/concat.html
50     args = split_merge_args(original)
51     if args is None:
52         return FUNCTION_FAILED
53     merged_list = []
54     for arg in args:
55         if arg.startswith("["):
56             value = eval_string(arg)
57             if value is None:
58                 logging.debug("Unable to convert to list: %s", arg)
59                 return FUNCTION_FAILED
60         else:
61             value = var_resolver(arg)
62         if isinstance(value, list):
63             merged_list.extend(value)
64         else:
65             return FUNCTION_FAILED  # don't know what this is, blow out
66     return merged_list
67 
68 
69 def tobool(original: Union[bool, str], **_: Any) -> Union[bool, str]:
70     # https://www.terraform.io/docs/configuration/functions/tobool.html
71     bool_value = convert_str_to_bool(original)
72     return bool_value if isinstance(bool_value, bool) else FUNCTION_FAILED
73 
74 
75 def tonumber(original: str, **_: Any) -> float | str:
76     # https://www.terraform.io/docs/configuration/functions/tonumber.html
77     if original.startswith('"') and original.endswith('"'):
78         original = original[1:-1]
79     try:
80         if "." in original:
81             return float(original)
82         else:
83             return int(original)
84     except ValueError:
85         return FUNCTION_FAILED
86 
87 
88 def tostring(original: str, **_: Any) -> bool | str:
89     # Indicates a safe string, all good
90     if original.startswith('"') and original.endswith('"'):
91         return original[1:-1]
92     # Otherwise, need to check for valid types (number or bool)
93     bool_value = convert_str_to_bool(original)
94     if isinstance(bool_value, bool):
95         return bool_value
96     else:
97         try:
98             if "." in original:
99                 return str(float(original))
100             else:
101                 return str(int(original))
102         except ValueError:
103             return FUNCTION_FAILED  # no change
104 
105 
106 def tolist(original: str, **_: Any) -> list[Any] | str:
107     # https://www.terraform.io/docs/configuration/functions/tolist.html
108     altered_value = eval_string(original)
109     if altered_value is None:
110         return FUNCTION_FAILED
111     return altered_value if isinstance(altered_value, list) else list(altered_value)
112 
113 
114 def toset(original: str, **_: Any) -> set[Any] | str:
115     # https://www.terraform.io/docs/configuration/functions/toset.html
116     altered_value = eval_string(original)
117     if altered_value is None:
118         return FUNCTION_FAILED
119     return altered_value if isinstance(altered_value, set) else set(altered_value)
120 
121 
122 def tomap(original: str, **_: Any) -> dict[Hashable, Any] | str:
123     # https://www.terraform.io/docs/language/functions/tomap.html
124     original = original.replace(":", "=")     # converted to colons by parser #shrug
125 
126     altered_value = eval_string(original)
127     if altered_value is None or not isinstance(altered_value, dict):
128         return FUNCTION_FAILED
129     return _check_map_type_consistency(altered_value)
130 
131 
132 def map(original: str, **_: Any) -> dict[Hashable, Any] | str:
133     # https://www.terraform.io/docs/language/functions/map.html
134 
135     # NOTE: Splitting by commas is annoying due to possible commas in strings. To avoid
136     #       the issue, act like it's a list (to allow comma separation) and let the HCL
137     #       parser deal with it. Then iterating the list is easy.
138     converted_to_list = eval_string(f"[{original}]")
139     if converted_to_list is None or len(converted_to_list) & 1:       # none or odd number of args
140         return FUNCTION_FAILED
141 
142     return create_map(converted_to_list)
143 
144 
145 def create_map(lst: list[Any]) -> dict[Hashable, Any]:
146     new_map = {}
147     for i in range(0, len(lst), 2):
148         new_map[lst[i]] = lst[i + 1]
149     return _check_map_type_consistency(new_map)
150 
151 
152 def _check_map_type_consistency(value: dict[Hashable, Any]) -> dict[Hashable, Any]:
153     # If there is a string and anything else, convert to string
154     had_string = False
155     had_something_else = False
156     for k, v in value.items():
157         if v == "${True}":
158             value[k] = True
159             v = True
160         elif v == "${False}":
161             value[k] = False
162             v = False
163 
164         if isinstance(v, str):
165             had_string = True
166             if had_something_else:
167                 break
168         else:
169             had_something_else = True
170             if had_string:
171                 break
172     if had_string and had_something_else:
173         value = {k: to_string(v) for k, v in value.items()}
174     return value
175 
176 
177 def handle_dynamic_values(conf: Dict[str, List[Any]], has_dynamic_block: bool = False) -> bool:
178     # recursively search for blocks that are dynamic
179     for block_name in conf.keys():
180         conf_block = conf[block_name]
181         if isinstance(conf_block, dict):
182             has_dynamic_block = handle_dynamic_values(conf_block, has_dynamic_block)
183 
184         # if the configuration is a block element, search down again.
185         if conf_block and isinstance(conf_block, list) and isinstance(conf_block[0], dict):
186             has_dynamic_block = handle_dynamic_values(conf_block[0], has_dynamic_block)
187 
188     # if a dynamic block exists somewhere in the resource it will return True
189     return process_dynamic_values(conf) or has_dynamic_block
190 
191 
192 def process_dynamic_values(conf: Dict[str, List[Any]]) -> bool:
193     has_dynamic_block = False
194     for dynamic_element in conf.get("dynamic", {}):
195         if isinstance(dynamic_element, str):
196             try:
197                 dynamic_element = json.loads(dynamic_element)
198             except Exception:
199                 dynamic_element = {}
200 
201         for element_name, element_value in dynamic_element.items():
202             if "content" in element_value:
203                 if element_name in conf:
204                     if not isinstance(conf[element_name], list):
205                         conf[element_name] = [conf[element_name]]
206                     if isinstance(element_value["content"], list):
207                         conf[element_name].extend(element_value["content"])
208                     else:
209                         conf[element_name].append(element_value["content"])
210 
211                 else:
212                     conf[element_name] = pickle_deepcopy(element_value["content"])
213             else:
214                 # this should be the result of a successful dynamic block rendering
215                 # in some cases a whole dict is added, which doesn't have a list around it
216                 conf[element_name] = element_value if isinstance(element_value, list) else [element_value]
217 
218         has_dynamic_block = True
219 
220     return has_dynamic_block
221
```
--- 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/parser_functions.py b/checkov/terraform/parser_functions.py
--- a/checkov/terraform/parser_functions.py
+++ b/checkov/terraform/parser_functions.py
@@ -7,7 +7,12 @@
 
 from checkov.common.util.data_structures_utils import pickle_deepcopy
 from checkov.common.util.type_forcers import convert_str_to_bool
-from checkov.common.util.parser_utils import eval_string, split_merge_args, string_to_native, to_string
+from checkov.common.util.parser_utils import (
+    eval_string,
+    split_merge_args,
+    string_to_native,
+    to_string,
+)
 
 #
 # Functions defined in this file implement terraform functions.
@@ -121,7 +126,7 @@
 
 def tomap(original: str, **_: Any) -> dict[Hashable, Any] | str:
     # https://www.terraform.io/docs/language/functions/tomap.html
-    original = original.replace(":", "=")     # converted to colons by parser #shrug
+    original = original.replace(":", "=")  # converted to colons by parser #shrug
 
     altered_value = eval_string(original)
     if altered_value is None or not isinstance(altered_value, dict):
@@ -136,7 +141,7 @@
     #       the issue, act like it's a list (to allow comma separation) and let the HCL
     #       parser deal with it. Then iterating the list is easy.
     converted_to_list = eval_string(f"[{original}]")
-    if converted_to_list is None or len(converted_to_list) & 1:       # none or odd number of args
+    if converted_to_list is None or len(converted_to_list) & 1:  # none or odd number of args
         return FUNCTION_FAILED
 
     return create_map(converted_to_list)
@@ -190,8 +195,13 @@
 
 
 def process_dynamic_values(conf: Dict[str, List[Any]]) -> bool:
+    dynamic_conf: Union[List[Any], Dict[str, List[Any]]] = conf.get("dynamic", {})
+
+    if not isinstance(dynamic_conf, list):
+        return False
+
     has_dynamic_block = False
-    for dynamic_element in conf.get("dynamic", {}):
+    for dynamic_element in dynamic_conf:
         if isinstance(dynamic_element, str):
             try:
                 dynamic_element = json.loads(dynamic_element)

{"golden_diff": "diff --git a/checkov/terraform/parser_functions.py b/checkov/terraform/parser_functions.py\n--- a/checkov/terraform/parser_functions.py\n+++ b/checkov/terraform/parser_functions.py\n@@ -7,7 +7,12 @@\n \n from checkov.common.util.data_structures_utils import pickle_deepcopy\n from checkov.common.util.type_forcers import convert_str_to_bool\n-from checkov.common.util.parser_utils import eval_string, split_merge_args, string_to_native, to_string\n+from checkov.common.util.parser_utils import (\n+    eval_string,\n+    split_merge_args,\n+    string_to_native,\n+    to_string,\n+)\n \n #\n # Functions defined in this file implement terraform functions.\n@@ -121,7 +126,7 @@\n \n def tomap(original: str, **_: Any) -> dict[Hashable, Any] | str:\n     # https://www.terraform.io/docs/language/functions/tomap.html\n-    original = original.replace(\":\", \"=\")     # converted to colons by parser #shrug\n+    original = original.replace(\":\", \"=\")  # converted to colons by parser #shrug\n \n     altered_value = eval_string(original)\n     if altered_value is None or not isinstance(altered_value, dict):\n@@ -136,7 +141,7 @@\n     #       the issue, act like it's a list (to allow comma separation) and let the HCL\n     #       parser deal with it. Then iterating the list is easy.\n     converted_to_list = eval_string(f\"[{original}]\")\n-    if converted_to_list is None or len(converted_to_list) & 1:       # none or odd number of args\n+    if converted_to_list is None or len(converted_to_list) & 1:  # none or odd number of args\n         return FUNCTION_FAILED\n \n     return create_map(converted_to_list)\n@@ -190,8 +195,13 @@\n \n \n def process_dynamic_values(conf: Dict[str, List[Any]]) -> bool:\n+    dynamic_conf: Union[List[Any], Dict[str, List[Any]]] = conf.get(\"dynamic\", {})\n+\n+    if not isinstance(dynamic_conf, list):\n+        return False\n+\n     has_dynamic_block = False\n-    for dynamic_element in conf.get(\"dynamic\", {}):\n+    for dynamic_element in dynamic_conf:\n         if isinstance(dynamic_element, str):\n             try:\n                 dynamic_element = json.loads(dynamic_element)\n", "issue": "Resources that contain scalar fields named \"dynamic\" not parsing properly\n**Describe the issue**\r\n\r\nRunning checkov against a Terraform resource which includes a scalar field called \"dynamic\" which _is not_ an actual HCL `dynamic` block causes a crash.\r\n\r\n**Examples**\r\n\r\nUsing the below resource definition:\r\n\r\n```hcl\r\nresource \"elasticsearch_index_template\" \"logstash\" {\r\n  body = jsonencode(\r\n    {\r\n      index_patterns = [\r\n        \"test-*\",\r\n      ]\r\n      mappings = {\r\n        properties = {\r\n          foo = {\r\n            # Note that \"dynamic\" here is not HCL dynamic block, but rather an elasticsearch property declared using HCL\r\n            dynamic = true\r\n            properties = {\r\n              bar = {\r\n                type = \"string\"\r\n              }\r\n            }\r\n          }\r\n        }\r\n      }\r\n    }\r\n  )\r\n\r\n  name = \"test\"\r\n}\r\n```\r\n\r\nWe found this bug while attempting to write a custom check to flag up use of the `provisioner` block which, since it is a [Terraform configuration](https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax) available to all `resource` blocks, requires a custom check where `supported_resources = (\"*\")`:\r\n\r\n```python\r\nfrom typing import Any\r\nfrom checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck\r\nfrom checkov.common.models.enums import CheckResult, CheckCategories\r\n\r\n\r\nclass DynamicTest(BaseResourceCheck):\r\n    def __init__(self) -> None:\r\n        name = \"Testing crash where dynamic is a scalar value\"\r\n        id = \"CKV_TEST_1\"\r\n        supported_resources = (\"*\",)\r\n        categories = (CheckCategories.GENERAL_SECURITY,)\r\n        super().__init__(\r\n            name=name,\r\n            id=id,\r\n            categories=categories,\r\n            supported_resources=supported_resources,\r\n        )\r\n\r\n    def scan_resource_conf(self, conf: dict[str, list[Any]]) -> CheckResult:\r\n        return CheckResult.PASSED\r\n\r\n\r\ncheck = DynamicTest()\r\n```\r\n\r\nWe can then run checkov:\r\n\r\n```shell\r\n$ checkov -f test.tf --external-checks-dir=./checks\r\n```\r\n\r\n**Exception Trace**\r\n\r\nThe relevant part of the trace caused by this issue is below:\r\n\r\n```\r\n2023-10-13 10:22:17,018 [MainThread  ] [DEBUG]  Running check: Testing crash where dynamic is a scalar value on file /test.tf\r\n2023-10-13 10:22:17,019 [MainThread  ] [ERROR]  Failed to run check CKV_TEST_1 on /test.tf:elasticsearch_index_template.test\r\nTraceback (most recent call last):\r\n  File \"/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/common/checks/base_check.py\", line 75, in run\r\n    check_result[\"result\"] = self.scan_entity_conf(entity_configuration, entity_type)\r\n  File \"/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/checks/resource/base_resource_check.py\", line 42, in scan_entity_conf\r\n    handle_dynamic_values(conf)\r\n  File \"/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/parser_functions.py\", line 186, in handle_dynamic_values\r\n    has_dynamic_block = handle_dynamic_values(conf_block[0], has_dynamic_block)\r\n  File \"/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/parser_functions.py\", line 182, in handle_dynamic_values\r\n    has_dynamic_block = handle_dynamic_values(conf_block, has_dynamic_block)\r\n  File \"/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/parser_functions.py\", line 182, in handle_dynamic_values\r\n    has_dynamic_block = handle_dynamic_values(conf_block, has_dynamic_block)\r\n  File \"/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/parser_functions.py\", line 182, in handle_dynamic_values\r\n    has_dynamic_block = handle_dynamic_values(conf_block, has_dynamic_block)\r\n  File \"/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/parser_functions.py\", line 189, in handle_dynamic_values\r\n    return process_dynamic_values(conf) or has_dynamic_block\r\n  File \"/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/parser_functions.py\", line 195, in process_dynamic_values\r\n    for dynamic_element in conf.get(\"dynamic\", {}):\r\nTypeError: 'bool' object is not iterable\r\n2023-10-13 10:22:17,020 [MainThread  ] [INFO ]  Entity configuration: {'__end_line__': 24, '__start_line__': 1, 'body': [{'index_patterns': ['test-*'], 'mappings': {'properties': {'foo': {'dynamic': True, 'properties': {'bar': {'type': 'string'}}}}}}], 'name': ['test'], '__address__': 'elasticsearch_index_template.test'}\r\n```\r\n\r\n**Desktop (please complete the following information):**\r\n\r\n - OS: MacOS 13.4.1 (Ventura)\r\n - Checkov Version: 2.5.8\r\n\r\n**Additional context**\r\n\r\nI believe this is due to the [process_dynamic_values](https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/parser_functions.py#L194) method, which directly fetches the \"dynamic\" property of `conf` but does not verify its Iterable-ness before trying to loop through and parse its elements.\r\n\n", "before_files": [{"content": "from __future__ import annotations\n\nimport json\nimport logging\nfrom collections.abc import Hashable\nfrom typing import Dict, List, Union, Any, Callable\n\nfrom checkov.common.util.data_structures_utils import pickle_deepcopy\nfrom checkov.common.util.type_forcers import convert_str_to_bool\nfrom checkov.common.util.parser_utils import eval_string, split_merge_args, string_to_native, to_string\n\n#\n# Functions defined in this file implement terraform functions.\n#\n# Inputs:\n#   - First arg (unnamed) - the value string provided to the function\n#   - \"var_resolver\" - function pointer to resolve variable/local references and such\n#   - \"function_name\" - name of the function being called (mainly useful for error reporting when a\n#                       function isn't defined)\n# These may be expanded over time, so accepting kwargs (via `**`) is recommended.\n#\n# If the value cannot be processed, `FUNCTION_FAILED` should be returned.\n#\n\nFUNCTION_FAILED = \"____FUNCTION_FAILED____\"\n\n\ndef merge(original: str, var_resolver: Callable[[Any], Any], **_: Any) -> dict[Hashable, Any] | str:\n    # https://www.terraform.io/docs/language/functions/merge.html\n    args = split_merge_args(original)\n    if args is None:\n        return FUNCTION_FAILED\n    merged_map = {}\n    for arg in args:\n        if arg.startswith(\"{\"):\n            arg_value = string_to_native(arg)\n            if arg_value is None:\n                return FUNCTION_FAILED\n        else:\n            arg_value = var_resolver(arg)\n        if isinstance(arg_value, dict):\n            merged_map.update(arg_value)\n        else:\n            return FUNCTION_FAILED  # don't know what this is, blow out\n    return merged_map\n\n\ndef concat(original: str, var_resolver: Callable[[Any], Any], **_: Any) -> list[Any] | str:\n    # https://www.terraform.io/docs/language/functions/concat.html\n    args = split_merge_args(original)\n    if args is None:\n        return FUNCTION_FAILED\n    merged_list = []\n    for arg in args:\n        if arg.startswith(\"[\"):\n            value = eval_string(arg)\n            if value is None:\n                logging.debug(\"Unable to convert to list: %s\", arg)\n                return FUNCTION_FAILED\n        else:\n            value = var_resolver(arg)\n        if isinstance(value, list):\n            merged_list.extend(value)\n        else:\n            return FUNCTION_FAILED  # don't know what this is, blow out\n    return merged_list\n\n\ndef tobool(original: Union[bool, str], **_: Any) -> Union[bool, str]:\n    # https://www.terraform.io/docs/configuration/functions/tobool.html\n    bool_value = convert_str_to_bool(original)\n    return bool_value if isinstance(bool_value, bool) else FUNCTION_FAILED\n\n\ndef tonumber(original: str, **_: Any) -> float | str:\n    # https://www.terraform.io/docs/configuration/functions/tonumber.html\n    if original.startswith('\"') and original.endswith('\"'):\n        original = original[1:-1]\n    try:\n        if \".\" in original:\n            return float(original)\n        else:\n            return int(original)\n    except ValueError:\n        return FUNCTION_FAILED\n\n\ndef tostring(original: str, **_: Any) -> bool | str:\n    # Indicates a safe string, all good\n    if original.startswith('\"') and original.endswith('\"'):\n        return original[1:-1]\n    # Otherwise, need to check for valid types (number or bool)\n    bool_value = convert_str_to_bool(original)\n    if isinstance(bool_value, bool):\n        return bool_value\n    else:\n        try:\n            if \".\" in original:\n                return str(float(original))\n            else:\n                return str(int(original))\n        except ValueError:\n            return FUNCTION_FAILED  # no change\n\n\ndef tolist(original: str, **_: Any) -> list[Any] | str:\n    # https://www.terraform.io/docs/configuration/functions/tolist.html\n    altered_value = eval_string(original)\n    if altered_value is None:\n        return FUNCTION_FAILED\n    return altered_value if isinstance(altered_value, list) else list(altered_value)\n\n\ndef toset(original: str, **_: Any) -> set[Any] | str:\n    # https://www.terraform.io/docs/configuration/functions/toset.html\n    altered_value = eval_string(original)\n    if altered_value is None:\n        return FUNCTION_FAILED\n    return altered_value if isinstance(altered_value, set) else set(altered_value)\n\n\ndef tomap(original: str, **_: Any) -> dict[Hashable, Any] | str:\n    # https://www.terraform.io/docs/language/functions/tomap.html\n    original = original.replace(\":\", \"=\")     # converted to colons by parser #shrug\n\n    altered_value = eval_string(original)\n    if altered_value is None or not isinstance(altered_value, dict):\n        return FUNCTION_FAILED\n    return _check_map_type_consistency(altered_value)\n\n\ndef map(original: str, **_: Any) -> dict[Hashable, Any] | str:\n    # https://www.terraform.io/docs/language/functions/map.html\n\n    # NOTE: Splitting by commas is annoying due to possible commas in strings. To avoid\n    #       the issue, act like it's a list (to allow comma separation) and let the HCL\n    #       parser deal with it. Then iterating the list is easy.\n    converted_to_list = eval_string(f\"[{original}]\")\n    if converted_to_list is None or len(converted_to_list) & 1:       # none or odd number of args\n        return FUNCTION_FAILED\n\n    return create_map(converted_to_list)\n\n\ndef create_map(lst: list[Any]) -> dict[Hashable, Any]:\n    new_map = {}\n    for i in range(0, len(lst), 2):\n        new_map[lst[i]] = lst[i + 1]\n    return _check_map_type_consistency(new_map)\n\n\ndef _check_map_type_consistency(value: dict[Hashable, Any]) -> dict[Hashable, Any]:\n    # If there is a string and anything else, convert to string\n    had_string = False\n    had_something_else = False\n    for k, v in value.items():\n        if v == \"${True}\":\n            value[k] = True\n            v = True\n        elif v == \"${False}\":\n            value[k] = False\n            v = False\n\n        if isinstance(v, str):\n            had_string = True\n            if had_something_else:\n                break\n        else:\n            had_something_else = True\n            if had_string:\n                break\n    if had_string and had_something_else:\n        value = {k: to_string(v) for k, v in value.items()}\n    return value\n\n\ndef handle_dynamic_values(conf: Dict[str, List[Any]], has_dynamic_block: bool = False) -> bool:\n    # recursively search for blocks that are dynamic\n    for block_name in conf.keys():\n        conf_block = conf[block_name]\n        if isinstance(conf_block, dict):\n            has_dynamic_block = handle_dynamic_values(conf_block, has_dynamic_block)\n\n        # if the configuration is a block element, search down again.\n        if conf_block and isinstance(conf_block, list) and isinstance(conf_block[0], dict):\n            has_dynamic_block = handle_dynamic_values(conf_block[0], has_dynamic_block)\n\n    # if a dynamic block exists somewhere in the resource it will return True\n    return process_dynamic_values(conf) or has_dynamic_block\n\n\ndef process_dynamic_values(conf: Dict[str, List[Any]]) -> bool:\n    has_dynamic_block = False\n    for dynamic_element in conf.get(\"dynamic\", {}):\n        if isinstance(dynamic_element, str):\n            try:\n                dynamic_element = json.loads(dynamic_element)\n            except Exception:\n                dynamic_element = {}\n\n        for element_name, element_value in dynamic_element.items():\n            if \"content\" in element_value:\n                if element_name in conf:\n                    if not isinstance(conf[element_name], list):\n                        conf[element_name] = [conf[element_name]]\n                    if isinstance(element_value[\"content\"], list):\n                        conf[element_name].extend(element_value[\"content\"])\n                    else:\n                        conf[element_name].append(element_value[\"content\"])\n\n                else:\n                    conf[element_name] = pickle_deepcopy(element_value[\"content\"])\n            else:\n                # this should be the result of a successful dynamic block rendering\n                # in some cases a whole dict is added, which doesn't have a list around it\n                conf[element_name] = element_value if isinstance(element_value, list) else [element_value]\n\n        has_dynamic_block = True\n\n    return has_dynamic_block\n", "path": "checkov/terraform/parser_functions.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport json\nimport logging\nfrom collections.abc import Hashable\nfrom typing import Dict, List, Union, Any, Callable\n\nfrom checkov.common.util.data_structures_utils import pickle_deepcopy\nfrom checkov.common.util.type_forcers import convert_str_to_bool\nfrom checkov.common.util.parser_utils import (\n    eval_string,\n    split_merge_args,\n    string_to_native,\n    to_string,\n)\n\n#\n# Functions defined in this file implement terraform functions.\n#\n# Inputs:\n#   - First arg (unnamed) - the value string provided to the function\n#   - \"var_resolver\" - function pointer to resolve variable/local references and such\n#   - \"function_name\" - name of the function being called (mainly useful for error reporting when a\n#                       function isn't defined)\n# These may be expanded over time, so accepting kwargs (via `**`) is recommended.\n#\n# If the value cannot be processed, `FUNCTION_FAILED` should be returned.\n#\n\nFUNCTION_FAILED = \"____FUNCTION_FAILED____\"\n\n\ndef merge(original: str, var_resolver: Callable[[Any], Any], **_: Any) -> dict[Hashable, Any] | str:\n    # https://www.terraform.io/docs/language/functions/merge.html\n    args = split_merge_args(original)\n    if args is None:\n        return FUNCTION_FAILED\n    merged_map = {}\n    for arg in args:\n        if arg.startswith(\"{\"):\n            arg_value = string_to_native(arg)\n            if arg_value is None:\n                return FUNCTION_FAILED\n        else:\n            arg_value = var_resolver(arg)\n        if isinstance(arg_value, dict):\n            merged_map.update(arg_value)\n        else:\n            return FUNCTION_FAILED  # don't know what this is, blow out\n    return merged_map\n\n\ndef concat(original: str, var_resolver: Callable[[Any], Any], **_: Any) -> list[Any] | str:\n    # https://www.terraform.io/docs/language/functions/concat.html\n    args = split_merge_args(original)\n    if args is None:\n        return FUNCTION_FAILED\n    merged_list = []\n    for arg in args:\n        if arg.startswith(\"[\"):\n            value = eval_string(arg)\n            if value is None:\n                logging.debug(\"Unable to convert to list: %s\", arg)\n                return FUNCTION_FAILED\n        else:\n            value = var_resolver(arg)\n        if isinstance(value, list):\n            merged_list.extend(value)\n        else:\n            return FUNCTION_FAILED  # don't know what this is, blow out\n    return merged_list\n\n\ndef tobool(original: Union[bool, str], **_: Any) -> Union[bool, str]:\n    # https://www.terraform.io/docs/configuration/functions/tobool.html\n    bool_value = convert_str_to_bool(original)\n    return bool_value if isinstance(bool_value, bool) else FUNCTION_FAILED\n\n\ndef tonumber(original: str, **_: Any) -> float | str:\n    # https://www.terraform.io/docs/configuration/functions/tonumber.html\n    if original.startswith('\"') and original.endswith('\"'):\n        original = original[1:-1]\n    try:\n        if \".\" in original:\n            return float(original)\n        else:\n            return int(original)\n    except ValueError:\n        return FUNCTION_FAILED\n\n\ndef tostring(original: str, **_: Any) -> bool | str:\n    # Indicates a safe string, all good\n    if original.startswith('\"') and original.endswith('\"'):\n        return original[1:-1]\n    # Otherwise, need to check for valid types (number or bool)\n    bool_value = convert_str_to_bool(original)\n    if isinstance(bool_value, bool):\n        return bool_value\n    else:\n        try:\n            if \".\" in original:\n                return str(float(original))\n            else:\n                return str(int(original))\n        except ValueError:\n            return FUNCTION_FAILED  # no change\n\n\ndef tolist(original: str, **_: Any) -> list[Any] | str:\n    # https://www.terraform.io/docs/configuration/functions/tolist.html\n    altered_value = eval_string(original)\n    if altered_value is None:\n        return FUNCTION_FAILED\n    return altered_value if isinstance(altered_value, list) else list(altered_value)\n\n\ndef toset(original: str, **_: Any) -> set[Any] | str:\n    # https://www.terraform.io/docs/configuration/functions/toset.html\n    altered_value = eval_string(original)\n    if altered_value is None:\n        return FUNCTION_FAILED\n    return altered_value if isinstance(altered_value, set) else set(altered_value)\n\n\ndef tomap(original: str, **_: Any) -> dict[Hashable, Any] | str:\n    # https://www.terraform.io/docs/language/functions/tomap.html\n    original = original.replace(\":\", \"=\")  # converted to colons by parser #shrug\n\n    altered_value = eval_string(original)\n    if altered_value is None or not isinstance(altered_value, dict):\n        return FUNCTION_FAILED\n    return _check_map_type_consistency(altered_value)\n\n\ndef map(original: str, **_: Any) -> dict[Hashable, Any] | str:\n    # https://www.terraform.io/docs/language/functions/map.html\n\n    # NOTE: Splitting by commas is annoying due to possible commas in strings. To avoid\n    #       the issue, act like it's a list (to allow comma separation) and let the HCL\n    #       parser deal with it. Then iterating the list is easy.\n    converted_to_list = eval_string(f\"[{original}]\")\n    if converted_to_list is None or len(converted_to_list) & 1:  # none or odd number of args\n        return FUNCTION_FAILED\n\n    return create_map(converted_to_list)\n\n\ndef create_map(lst: list[Any]) -> dict[Hashable, Any]:\n    new_map = {}\n    for i in range(0, len(lst), 2):\n        new_map[lst[i]] = lst[i + 1]\n    return _check_map_type_consistency(new_map)\n\n\ndef _check_map_type_consistency(value: dict[Hashable, Any]) -> dict[Hashable, Any]:\n    # If there is a string and anything else, convert to string\n    had_string = False\n    had_something_else = False\n    for k, v in value.items():\n        if v == \"${True}\":\n            value[k] = True\n            v = True\n        elif v == \"${False}\":\n            value[k] = False\n            v = False\n\n        if isinstance(v, str):\n            had_string = True\n            if had_something_else:\n                break\n        else:\n            had_something_else = True\n            if had_string:\n                break\n    if had_string and had_something_else:\n        value = {k: to_string(v) for k, v in value.items()}\n    return value\n\n\ndef handle_dynamic_values(conf: Dict[str, List[Any]], has_dynamic_block: bool = False) -> bool:\n    # recursively search for blocks that are dynamic\n    for block_name in conf.keys():\n        conf_block = conf[block_name]\n        if isinstance(conf_block, dict):\n            has_dynamic_block = handle_dynamic_values(conf_block, has_dynamic_block)\n\n        # if the configuration is a block element, search down again.\n        if conf_block and isinstance(conf_block, list) and isinstance(conf_block[0], dict):\n            has_dynamic_block = handle_dynamic_values(conf_block[0], has_dynamic_block)\n\n    # if a dynamic block exists somewhere in the resource it will return True\n    return process_dynamic_values(conf) or has_dynamic_block\n\n\ndef process_dynamic_values(conf: Dict[str, List[Any]]) -> bool:\n    dynamic_conf: Union[List[Any], Dict[str, List[Any]]] = conf.get(\"dynamic\", {})\n\n    if not isinstance(dynamic_conf, list):\n        return False\n\n    has_dynamic_block = False\n    for dynamic_element in dynamic_conf:\n        if isinstance(dynamic_element, str):\n            try:\n                dynamic_element = json.loads(dynamic_element)\n            except Exception:\n                dynamic_element = {}\n\n        for element_name, element_value in dynamic_element.items():\n            if \"content\" in element_value:\n                if element_name in conf:\n                    if not isinstance(conf[element_name], list):\n                        conf[element_name] = [conf[element_name]]\n                    if isinstance(element_value[\"content\"], list):\n                        conf[element_name].extend(element_value[\"content\"])\n                    else:\n                        conf[element_name].append(element_value[\"content\"])\n\n                else:\n                    conf[element_name] = pickle_deepcopy(element_value[\"content\"])\n            else:\n                # this should be the result of a successful dynamic block rendering\n                # in some cases a whole dict is added, which doesn't have a list around it\n                conf[element_name] = element_value if isinstance(element_value, list) else [element_value]\n\n        has_dynamic_block = True\n\n    return has_dynamic_block\n", "path": "checkov/terraform/parser_functions.py"}]}