def evaluate_non_rendered_values(self) -> None: for vertex in self.local_graph.vertices: changed_attributes = {} attributes: Dict[str, Any] = {} vertex.get_origin_attributes(attributes) for attribute in filter( lambda attr: attr not in reserved_attribute_names and not attribute_has_nested_attributes(attr, vertex.attributes), vertex.attributes, ): curr_val = vertex.attributes.get(attribute) lst_curr_val = curr_val if not isinstance(lst_curr_val, list): lst_curr_val = [lst_curr_val] if len(lst_curr_val) > 0 and isinstance(lst_curr_val[0], Tree): lst_curr_val[0] = str(lst_curr_val[0]) evaluated_lst = [] for inner_val in lst_curr_val: if ( isinstance(inner_val, str) and not any(c in inner_val for c in ["{", "}", "[", "]", "="]) or attribute in ATTRIBUTES_NO_EVAL ): evaluated_lst.append(inner_val) continue evaluated = self.evaluate_value(inner_val) evaluated_lst.append(evaluated) evaluated = evaluated_lst if not isinstance(curr_val, list): evaluated = evaluated_lst[0] if evaluated != curr_val: vertex.update_inner_attribute(attribute, vertex.attributes, evaluated) changed_attributes[attribute] = evaluated self.local_graph.update_vertex_config(vertex, changed_attributes)
def test__attribute_has_nested_attributes_list(self): attributes = { 'most_recent': [True], 'filter': [{ 'name': 'name', 'values': ['amzn-ami-hvm-*-x86_64-gp2'] }, { 'name': 'owner-alias', 'values': ['amazon'] }], 'filter.0': { 'name': 'name', 'values': ['amzn-ami-hvm-*-x86_64-gp2'] }, 'filter.0.name': 'name', 'filter.0.values': ['amzn-ami-hvm-*-x86_64-gp2'], 'filter.0.values.0': 'amzn-ami-hvm-*-x86_64-gp2', 'filter.1': { 'name': 'owner-alias', 'values': ['amazon'] }, 'filter.1.name': 'owner-alias', 'filter.1.values': ['amazon'], 'filter.1.values.0': 'amazon' } self.assertTrue( attribute_has_nested_attributes(attribute_key='filter', attributes=attributes)) self.assertTrue( attribute_has_nested_attributes(attribute_key='filter.1.values', attributes=attributes)) self.assertFalse( attribute_has_nested_attributes(attribute_key='filter.1.values.0', attributes=attributes))
def test__attribute_has_nested_attributes_dictionary(self): attributes = { 'name': ['${var.lb_name}'], 'internal': [True], 'security_groups': ['${var.lb_security_group_ids}'], 'subnets': ['${var.subnet_id}'], 'enable_deletion_protection': [True], 'tags': { 'Terraform': True, 'Environment': 'sophi-staging' }, 'resource_type': ['aws_alb'], 'tags.Terraform': True, 'tags.Environment': 'sophi-staging' } self.assertTrue( attribute_has_nested_attributes(attribute_key='tags', attributes=attributes)) self.assertFalse( attribute_has_nested_attributes(attribute_key='name', attributes=attributes)) self.assertFalse( attribute_has_nested_attributes(attribute_key='tags.Environment', attributes=attributes))
def _build_edges(self): logging.info('Creating edges') self.get_module_vertices_mapping() aliases = self._get_aliases() for origin_node_index, vertex in enumerate(self.vertices): for attribute_key in vertex.attributes: if attribute_key in reserved_attribute_names or attribute_has_nested_attributes( attribute_key, vertex.attributes): continue referenced_vertices = get_referenced_vertices_in_value( value=vertex.attributes[attribute_key], aliases=aliases, resources_types=self.get_resources_types_in_graph()) for vertex_reference in referenced_vertices: # for certain blocks such as data and resource, the block name is composed from several parts. # the purpose of the loop is to avoid not finding the node if the name has several parts sub_values = [ remove_index_pattern_from_str(sub_value) for sub_value in vertex_reference.sub_parts ] for i in range(len(sub_values)): reference_name = join_trimmed_strings( char_to_join=".", str_lst=sub_values, num_to_trim=i) if vertex.module_dependency: dest_node_index = self._find_vertex_index_relative_to_path( vertex_reference.block_type, reference_name, vertex.path, vertex.module_dependency) if dest_node_index == -1: dest_node_index = self._find_vertex_index_relative_to_path( vertex_reference.block_type, reference_name, vertex.path, vertex.path) else: dest_node_index = self._find_vertex_index_relative_to_path( vertex_reference.block_type, reference_name, vertex.path, vertex.module_dependency) if dest_node_index > -1 and origin_node_index > -1: if vertex_reference.block_type == BlockType.MODULE: try: self._connect_module( sub_values, attribute_key, self.vertices[dest_node_index], origin_node_index) except Exception as e: logging.warning( f'Module {self.vertices[dest_node_index]} does not have source attribute, skipping' ) logging.warning(e, stack_info=True) else: self._create_edge(origin_node_index, dest_node_index, attribute_key) break if vertex.block_type == BlockType.MODULE: target_path = vertex.path if vertex.module_dependency != '': target_path = unify_dependency_path( [vertex.module_dependency, vertex.path]) target_variables = list( filter( lambda v: self.vertices[v].module_dependency == target_path, self.vertices_by_block_type.get( BlockType.VARIABLE, {}))) for attribute, value in vertex.attributes.items(): if attribute in MODULE_RESERVED_ATTRIBUTES: continue target_variable = None for v in target_variables: if self.vertices[v].name == attribute: target_variable = v break if target_variable is not None: self._create_edge(target_variable, origin_node_index, 'default') elif vertex.block_type == BlockType.TF_VARIABLE: if vertex.module_dependency != '': target_path = unify_dependency_path( [vertex.module_dependency, vertex.path]) # Assuming the tfvars file is in the same directory as the variables file (best practice) target_variables = list( filter( lambda v: os.path.dirname(self.vertices[v].path) == os. path.dirname(vertex.path), self.vertices_block_name_map.get( BlockType.VARIABLE, {}).get(vertex.name, []))) if len(target_variables) == 1: self._create_edge(target_variables[0], origin_node_index, 'default')