def _handle_single_var_pattern(orig_variable: str, var_value_and_file_map: Dict[str, Tuple[Any, str]], locals_values: Dict[str, Any], resource_list: Optional[List[Dict[str, Any]]], module_list: Optional[List[Dict[str, Any]]], module_data_retrieval: Callable[[str], Dict[str, Any]], eval_map_by_var_name: Dict[str, EvaluationContext], context, orig_variable_full, root_directory: str) -> Any: ternary_info = _is_ternary(orig_variable) if ternary_info: return _process_ternary(orig_variable, ternary_info[0], ternary_info[1]) if orig_variable.startswith("module."): if not module_list: return orig_variable # Reference to module outputs, example: 'module.bucket.bucket_name' ref_tokens = orig_variable.split(".") if len(ref_tokens) != 3: return orig_variable # fail safe, can the length ever be something other than 3? try: ref_list = jmespath.search(f"[].{ref_tokens[1]}.{RESOLVED_MODULE_ENTRY_NAME}[]", module_list) # ^^^^^^^^^^^^^ module name if not ref_list or not isinstance(ref_list, list): return orig_variable for ref in ref_list: module_data = module_data_retrieval(ref) if not module_data: continue result = _handle_indexing(ref_tokens[2], lambda r: jmespath.search(f"output[].{ref_tokens[2]}.value[] | [0]", module_data)) if result: logging.debug("Resolved module ref: %s --> %s", orig_variable, result) return result except ValueError: pass return orig_variable elif orig_variable == "True": return True elif orig_variable == "False": return False elif orig_variable.startswith("var."): var_name = orig_variable[4:] var_value_and_file = _handle_indexing(var_name, lambda r: var_value_and_file_map.get(r), value_is_a_tuple=True) if var_value_and_file is not None: var_value, var_file = var_value_and_file eval_context = eval_map_by_var_name.get(var_name) if eval_context is None: eval_map_by_var_name[var_name] = EvaluationContext(os.path.relpath(var_file, root_directory), var_value, [VarReference(var_name, orig_variable_full, context)]) else: eval_context.definitions.append(VarReference(var_name, orig_variable_full, context)) return var_value elif orig_variable.startswith("local."): var_value = _handle_indexing(orig_variable[6:], lambda r: locals_values.get(r)) if var_value is not None: return var_value elif orig_variable.startswith("to") and orig_variable.endswith(")"): # https://www.terraform.io/docs/configuration/functions/tobool.html if orig_variable.startswith("tobool("): bool_variable = orig_variable[7:-1].lower() bool_value = convert_str_to_bool(bool_variable) if isinstance(bool_value, bool): return bool_value else: return orig_variable # https://www.terraform.io/docs/configuration/functions/tolist.html elif orig_variable.startswith("tolist("): altered_value = _eval_string(orig_variable[7:-1]) if altered_value is None: return orig_variable return altered_value if isinstance(altered_value, list) else list(altered_value) # NOTE: tomap as handled outside this loop (see below) # https://www.terraform.io/docs/configuration/functions/tonumber.html elif orig_variable.startswith("tonumber("): num_variable = orig_variable[9:-1] if num_variable.startswith('"') and num_variable.endswith('"'): num_variable = num_variable[1:-1] try: if "." in num_variable: return float(num_variable) else: return int(num_variable) except ValueError: return orig_variable # https://www.terraform.io/docs/configuration/functions/toset.html elif orig_variable.startswith("toset("): altered_value = _eval_string(orig_variable[6:-1]) if altered_value is None: return orig_variable return set(altered_value) # https://www.terraform.io/docs/configuration/functions/tostring.html elif orig_variable.startswith("tostring("): altered_value = orig_variable[9:-1] # Indicates a safe string, all good if altered_value.startswith('"') and altered_value.endswith('"'): return altered_value[1:-1] # Otherwise, need to check for valid types (number or bool) bool_value = convert_str_to_bool(altered_value) if isinstance(bool_value, bool): return bool_value else: try: if "." in altered_value: return str(float(altered_value)) else: return str(int(altered_value)) except ValueError: return orig_variable # no change elif orig_variable.startswith("merge(") and orig_variable.endswith(")"): altered_value = orig_variable[6:-1] args = split_merge_args(altered_value) if args is None: return orig_variable merged_map = {} for arg in args: if arg.startswith("{"): value = _map_string_to_native(arg) if value is None: return orig_variable else: value = _handle_single_var_pattern(arg, var_value_and_file_map, locals_values, resource_list, module_list, module_data_retrieval, eval_map_by_var_name, context, arg, root_directory) if isinstance(value, dict): merged_map.update(value) else: return orig_variable # don't know what this is, blow out return merged_map # TODO - format() support, still in progress # elif orig_variable.startswith("format(") and orig_variable.endswith(")"): # format_tokens = orig_variable[7:-1].split(",") # return format_tokens[0].format([_to_native_value(t) for t in format_tokens[1:]]) elif _RESOURCE_REF_PATTERN.match(orig_variable): # Reference to resources, example: 'aws_s3_bucket.example.bucket' # TODO: handle index into map/list try: result = jmespath.search(f"[].{orig_variable}[] | [0]", resource_list) except ValueError: pass else: if result is not None: return result return orig_variable # fall back to no change