def _mount_references(self): ''' Find the references that is defined in self.configuration :return: ''' self.resources_raw = deepcopy(self.resources) invalid_references = ('var') for resource in self.configuration['resources']: if 'expressions' in self.configuration['resources'][resource]: ref_list = list() for key, value in self.configuration['resources'][resource][ 'expressions'].items(): if 'references' in value: ref_list.extend([ self._find_resource_from_name(ref) for ref in value['references'] if not ref.startswith(invalid_references) ]) if ref_list: ref_type = self.configuration['resources'][resource][ 'expressions'].get('type', {}).get('constant_value', {}) if not ref_type and not resource.startswith(('data')): resource_type, resource_id = resource.split('.') ref_type = resource_type source_resources = self._find_resource_from_name( self.configuration['resources'][resource]['address']) self._mount_resources(source_resources, flatten_list(ref_list), ref_type)
def _mount_references(self): ''' Find the references that is defined in self.configuration :return: ''' self.resources_raw = deepcopy(self.resources) invalid_references = ('var') # This section will link resources found in configuration part of the plan output. # The reference should be on both ways (A->B, B->A) since terraform sometimes report these references # in opposite ways, depending on the provider structure. for resource in self.configuration['resources']: if 'expressions' in self.configuration['resources'][resource]: ref_list = [] for key, value in self.configuration['resources'][resource][ 'expressions'].items(): if 'references' in value: ref_list.extend([ self._find_resource_from_name(ref) for ref in value['references'] if not ref.startswith(invalid_references) ]) if ref_list: ref_type = self.configuration['resources'][resource][ 'expressions'].get('type', {}).get('constant_value', {}) if not ref_type and not resource.startswith(('data')): resource_type, resource_id = resource.split('.') ref_type = resource_type # Mounting A->B source_resources = self._find_resource_from_name( self.configuration['resources'][resource]['address']) self._mount_resources(source_resources, flatten_list(ref_list), ref_type) # Mounting B->A for source_resource in flatten_list(ref_list): if not source_resource.startswith( ('var', 'data', 'module', 'provider')): ref_type = source_resource.split('.', maxsplit=1)[0] self._mount_resources([source_resource], source_resources, ref_type)
def _mount_references(self): ''' Find the references that is defined in self.configuration :return: ''' self.resources_raw = deepcopy(self.resources) invalid_references = ('var.') # This section will link resources found in configuration part of the plan output. # The reference should be on both ways (A->B, B->A) since terraform sometimes report these references # in opposite ways, depending on the provider structure. for resource in self.configuration['resources']: if 'expressions' in self.configuration['resources'][resource]: ref_list = {} for key, value in self.configuration['resources'][resource]['expressions'].items(): if 'references' in value: for ref in value['references']: if not ref.startswith(invalid_references): if key not in ref_list: ref_list[key] = self._find_resource_from_name(ref) else: ref_list[key].extend(self._find_resource_from_name(ref)) if ref_list: ref_type = self.configuration['resources'][resource]['expressions'].get('type', {}).get('constant_value', {}) if not ref_type and not self.is_type(resource, 'data'): resource_type, resource_id = resource.split('.') ref_type = resource_type for k, v in ref_list.items(): v = flatten_list(v) # Mounting A->B source_resources = self._find_resource_from_name(self.configuration['resources'][resource]['address']) self._mount_resources(source=source_resources, target=ref_list, ref_type=ref_type) # Mounting B->A for parameter, target_resources in ref_list.items(): for target_resource in target_resources: if not self.is_type(resource, 'data') and not self.is_type(resource, 'var') and not self.is_type(resource, 'provider'): ref_type = target_resource.split('.', maxsplit=1)[0] self._mount_resources(source=[target_resource], target={parameter: source_resources}, ref_type=ref_type)
def _mount_references(self): ''' Find the references that is defined in self.configuration :return: ''' self.resources_raw = deepcopy(self.resources) invalid_references = ('var.', 'each.') # This section will link resources found in configuration part of the plan output. # The reference should be on both ways (A->B, B->A) since terraform sometimes report these references # in opposite ways, depending on the provider structure. for resource in self.configuration['resources']: if 'expressions' in self.configuration['resources'][resource]: ref_list = {} for key, value in self.configuration['resources'][resource][ 'expressions'].items(): references = seek_key_in_dict( value, 'references') if isinstance( value, (dict, list)) else [] valid_references = [] for ref in references: if isinstance(ref, dict) and ref.get('references'): valid_references = [ r for r in ref['references'] if not r.startswith(invalid_references) ] for ref in valid_references: # if ref is not in the correct format, handle it if len(ref.split('.')) < 3 and ref.startswith( 'module'): # Using for_each and modules together may introduce an issue where the plan.out.json won't include the necessary third part of the reference # It is partially resolved by mounting the reference to all instances belonging to the module if 'for_each_expression' in self.configuration[ 'resources'][resource]: # extract source resources assumed_source_resources = [ k for k in self.resources.keys() if k.startswith(resource) ] # extract for_each keys assumed_for_each_keys = [ k[len(resource):].split('.')[0] for k in assumed_source_resources ] # combine ref with for each keys assumed_refs = [ '{}{}'.format(ref, key) for key in assumed_for_each_keys ] # get all the resources that start with updated ref ambigious_references = [] for r in self.resources.keys(): for assumed_ref in assumed_refs: if r.startswith(assumed_ref): if key in ref_list: ref_list[key].append(r) else: ref_list[key] = [r] ambigious_references.append(r) # throw a warning defaults = Defaults() console_write('{} {}: {}'.format( defaults.warning_icon, defaults.warning_colour( 'WARNING (Mounting)'), defaults.info_colour( 'The reference "{}" in resource {} is ambigious.' ' It will be mounted to the following resources:' ).format(ref, resource))) for i, r in enumerate(ambigious_references, 1): console_write( defaults.info_colour('{}. {}'.format( i, r))) # if the reference can not be resolved, warn the user and continue. else: console_write('{} {}: {}'.format( Defaults().warning_icon, Defaults().warning_colour( 'WARNING (Mounting)'), Defaults().info_colour( 'The reference "{}" in resource {} is ambigious. It will not be mounted.' .format(ref, resource)))) continue elif key not in ref_list: ref_list[key] = self._find_resource_from_name(ref) else: ref_list[key].extend( self._find_resource_from_name(ref)) # This is where we synchronise constant_value in the configuration section with the resource # for filling up the missing elements that hasn't been defined in the resource due to provider # implementation. target_resource = [ t for t in [self.resources.get(resource, {}).get('address')] if t is not None ] if not target_resource: target_resource = [ k for k in self.resources.keys() if k.startswith(resource) ] for t_r in target_resource: if type(value) is type( self.resources[t_r]['values'].get(key) ) and self.resources[t_r]['values'].get(key) != value: if isinstance(value, (list, dict)): merge_dicts(self.resources[t_r]['values'][key], value) if ref_list: ref_type = self.configuration['resources'][resource][ 'expressions'].get('type', {}) if 'references' in ref_type: ref_type = resource.split('.')[0] if not ref_type and not self.is_type(resource, 'data'): ref_type = self.extract_resource_type_from_address( resource) for k, v in ref_list.items(): v = flatten_list(v) # Mounting A->B source_resources = self._find_resource_from_name( self.configuration['resources'][resource]['address']) self._mount_resources(source=source_resources, target=ref_list, ref_type=ref_type) # Mounting B->A for parameter, target_resources in ref_list.items(): for target_resource in target_resources: if not self.is_type( resource, 'data') and not self.is_type( resource, 'var') and not self.is_type( resource, 'provider'): ref_type = self.extract_resource_type_from_address( target_resource) self._mount_resources( source=[target_resource], target={parameter: source_resources}, ref_type=ref_type)
def test_flatten_single_dimensional_list(self): a = ['a', 'b', 'c'] b = ['a', 'b', 'c'] self.assertEqual(flatten_list(a), b)
def test_flatten_multi_dimensional_nested_list(self): a = ['a', 'b', ['c', ['d', 'e'], 'f'], 'g', 'h', 'i', ['j', 'k']] b = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'] self.assertEqual(flatten_list(a), b)
def test_flatten_multi_dimensional_list(self): a = ['a', 'b', ['c']] b = ['a', 'b', 'c'] self.assertEqual(flatten_list(a), b)
def _mount_references(self): ''' Find the references that is defined in self.configuration :return: ''' self.resources_raw = deepcopy(self.resources) invalid_references = ('var.', 'each.') # This section will link resources found in configuration part of the plan output. # The reference should be on both ways (A->B, B->A) since terraform sometimes report these references # in opposite ways, depending on the provider structure. for resource in self.configuration['resources']: if 'expressions' in self.configuration['resources'][resource]: ref_list = {} for key, value in self.configuration['resources'][resource][ 'expressions'].items(): references = seek_key_in_dict( value, 'references') if isinstance( value, (dict, list)) else [] valid_references = [] for ref in references: if isinstance(ref, dict) and ref.get('references'): valid_references = [ r for r in ref['references'] if not r.startswith(invalid_references) ] for ref in valid_references: if key not in ref_list: ref_list[key] = self._find_resource_from_name(ref) else: ref_list[key].extend( self._find_resource_from_name(ref)) # This is where we synchronise constant_value in the configuration section with the resource # for filling up the missing elements that hasn't been defined in the resource due to provider # implementation. target_resource = [ t for t in [self.resources.get(resource, {}).get('address')] if t is not None ] if not target_resource: target_resource = [ k for k in self.resources.keys() if k.startswith(resource) ] for t_r in target_resource: if type(value) is type( self.resources[t_r]['values'].get(key) ) and self.resources[t_r]['values'].get(key) != value: if isinstance(value, (list, dict)): merge_dicts(self.resources[t_r]['values'][key], value) if ref_list: ref_type = self.configuration['resources'][resource][ 'expressions'].get('type', {}) if 'references' in ref_type: ref_type = resource.split('.')[0] if not ref_type and not self.is_type(resource, 'data'): resource_type, resource_id = resource.split('.') ref_type = resource_type for k, v in ref_list.items(): v = flatten_list(v) # Mounting A->B source_resources = self._find_resource_from_name( self.configuration['resources'][resource]['address']) self._mount_resources(source=source_resources, target=ref_list, ref_type=ref_type) # Mounting B->A for parameter, target_resources in ref_list.items(): for target_resource in target_resources: if not self.is_type( resource, 'data') and not self.is_type( resource, 'var') and not self.is_type( resource, 'provider'): ref_type = target_resource.split('.', maxsplit=1)[0] self._mount_resources( source=[target_resource], target={parameter: source_resources}, ref_type=ref_type)