def is_acceptable_module_param(value: Any) -> bool: """ This function determines if a value should be passed to a module as a parameter. We don't want to pass unresolved var, local or module references because they can't be resolved from the module, so they need to be resolved prior to being passed down. """ value_type = type(value) if value_type is dict: for k, v in value.items(): if not is_acceptable_module_param( v) or not is_acceptable_module_param(k): return False return True if value_type is set or value_type is list: for v in value: if not is_acceptable_module_param(v): return False return True if not value_type is str: return True for vbm in find_var_blocks(value): if vbm.is_simple_var(): return False return True
def remove_interpolation(original_str: str, var_to_clean: Optional[str] = None, escape_unrendered=True) -> str: # get all variable references in string # remove from the string all ${} or '${}' occurrences var_blocks = find_var_blocks(original_str) var_blocks.reverse() for block in var_blocks: if ( block.full_str.startswith("${") and block.full_str.endswith("}") and (not var_to_clean or block.var_only == var_to_clean) ): full_str_start = original_str.find(block.full_str) full_str_end = full_str_start + len(block.full_str) if ( full_str_start > 0 and full_str_end <= len(original_str) - 2 and original_str[full_str_start - 1] == "'" and original_str[full_str_start - 1] == original_str[full_str_end] and "." in block.full_str ): # checking if ${} is wrapped with '' like : '${}' original_str = original_str[:full_str_start - 1] + block.full_str + original_str[full_str_end + 1:] if escape_unrendered: block.var_only = f"'{block.var_only}'" original_str = original_str.replace(block.full_str, block.var_only) return original_str
def _is_variable_dependant(value: Any) -> bool: if not isinstance(value, str): return False if "${" not in value: return False if find_var_blocks(value): return True return False
def _is_variable_dependant(value: Any) -> bool: if not isinstance(value, str): return False if value.startswith(("var.", "local.", "module.")): return True if value.startswith(PROVIDER_PREFIXES): return True if "${" not in value: return False if find_var_blocks(value): return True return False
def process_items_helper(key_value_iterator, data_map, context, allow_str_bool_translation: bool): made_change = False for key, value in list(key_value_iterator()): # Copy to list to allow deletion new_context = f"{context}/{key}" if len(context) != 0 else key if isinstance(value, str): altered_value = value had_var_block_match = False # The way in which matches are processed is important as they are ordered and may contain # portions of one another. For example: # ${merge(local.common_tags,local.common_data_tags,{'Name': 'my-thing-${var.ENVIRONMENT}-${var.REGION}'})} # In this case, we expect blocks similar to this: # 1) ${var.ENVIRONMENT} # 2) ${var.REGION} # 3) ${merge(local.common_tags,local.common_data_tags,{'Name': 'my-thing-${var.ENVIRONMENT}-${var.REGION}'})} # If either of the first two are replaced, we can still process the outer eval block # if the substitutions made to the earlier vars are also made to the later. That allows # knowing what the string should really look like so substitutions can be made properly. # If this proves not to work well, the other option is to abort the later (because # the full string isn't found in the value anymore) and come back to it on another # processor loop. This works... but requires another processor loop. # (If you're thinking we should make a DAG and do this properly... you're probably right.) prev_matches: List[Tuple[str, str]] = [] # original value -> replaced for match in find_var_blocks(value): # Update what's expected in the match, see comment above for prev_match in prev_matches: match.replace(prev_match[0], str(prev_match[1])) var_base = match.var_only # Expressions such as (from variable definition): # type = string # are turned into: # "type = ${string}" if var_base in _SIMPLE_TYPES and match.full_str == value: altered_value = var_base had_var_block_match = True prev_matches.append((match.full_str, var_base)) else: replaced = _handle_single_var_pattern(var_base, var_value_and_file_map, locals_values, resource_list, module_list, module_data_retrieval, eval_map_by_var_name, new_context, value, root_directory) if replaced != var_base: if match.full_str == altered_value: altered_value = replaced else: replace_str = f"'{match.full_str}'" if isinstance(replaced, str) or replace_str not in altered_value: replace_str = match.full_str altered_value = altered_value.replace(replace_str, str(replaced)) prev_matches.append((match.full_str, replaced)) had_var_block_match = True if not had_var_block_match: if allow_str_bool_translation and value == "true": altered_value = True elif allow_str_bool_translation and value == "false": altered_value = False if value != altered_value: LOGGER.debug(f"Resolve: %s --> %s", value, altered_value) data_map[key] = altered_value made_change = True elif isinstance(value, dict): if self._process_vars_and_locals_loop(value, eval_map_by_var_name, relative_file_path, var_value_and_file_map, locals_values, resource_list, module_list, module_data_retrieval, root_directory, new_context): made_change = True elif isinstance(value, list): if len(value) > 0 and value[0] != value: if process_items_helper(lambda: enumerate(value), value, new_context, True): made_change = True # Some special cases that should be pruned from datasets if value == [None] or value == [{}] or value == [[]] or len(value) == 0: del data_map[key] return made_change