def _parse_configurations(self): ''' Assigns all configuration related data defined in the terraform plan. This is mostly used for resources referencing each other. :return: none ''' # Resources self.configuration['resources'] = {} for findings in seek_key_in_dict(self.raw.get('configuration', {}).get('root_module', {}), 'resources'): for resource in findings.get('resources', []): if self.is_type(resource, 'data'): self.data[resource['address']] = resource else: self.configuration['resources'][resource['address']] = resource # Variables self.configuration['variables'] = {} for findings in seek_key_in_dict(self.raw.get('configuration', {}).get('root_module', {}), 'variables'): self.configuration['variables'] = findings.get('variables') # Providers self.configuration['providers'] = {} for findings in seek_key_in_dict(self.raw.get('configuration', {}), 'provider_config'): self.configuration['providers'] = findings.get('provider_config', {}) # Outputs self.configuration['outputs'] = {} for findings in seek_key_in_dict(self.raw.get('configuration', {}), 'outputs'): for key, value in findings.get('outputs', {}).items(): tmp_output = dict(address=key, value={}) if 'expression' in value: if 'references' in value['expression']: tmp_output['value'] = value['expression']['references'] tmp_output['type'] = 'object' elif 'constant_value' in value['expression']: tmp_output['value'] = value['expression']['constant_value'] if 'sensitive' in value: tmp_output['sensitive'] = str(value['sensitive']).lower() else: tmp_output['sensitive'] = 'false' if 'type' in value: tmp_output['type'] = value['type'] elif 'type' not in tmp_output: if isinstance(tmp_output['value'], list): tmp_output['type'] = 'list' elif isinstance(tmp_output['value'], dict): tmp_output['type'] = 'map' elif isinstance(tmp_output['value'], str): tmp_output['type'] = 'string' elif isinstance(tmp_output['value'], int): tmp_output['type'] = 'integer' elif isinstance(tmp_output['value'], bool): tmp_output['type'] = 'boolean' self.configuration['outputs'][key] = tmp_output
def _parse_configurations(self): ''' Assigns all configuration related data defined in the terraform plan. This is mostly used for resources referencing each other. :return: none ''' # Resources self.configuration['resources'] = dict() for findings in seek_key_in_dict( self.raw.get('configuration', {}).get('root_module', {}), 'resources'): for resource in findings.get('resources', []): if resource['address'].startswith('data'): self.data[resource['address']] = resource else: self.configuration['resources'][ resource['address']] = resource # Variables self.configuration['variables'] = dict() for findings in seek_key_in_dict( self.raw.get('configuration', {}).get('root_module', {}), 'variables'): self.configuration['variables'] = findings.get('variables', {}) # Providers self.configuration['providers'] = dict() for findings in seek_key_in_dict(self.raw.get('configuration', {}), 'provider_config'): self.configuration['providers'] = findings.get( 'provider_config', {})
def _parse_resources(self): ''' Assigns all resources defined in the terraform plan :return: none ''' # Resources ( exists in Plan ) for findings in seek_key_in_dict(self.raw.get('planned_values', {}).get('root_module', {}), 'resources'): for resource in findings.get('resources', []): if self.is_type(resource, 'data'): self.data[resource['address']] = resource else: self.resources[resource['address']] = resource # Resources ( exists in State ) for findings in seek_key_in_dict(self.raw.get('values', {}).get('root_module', {}), 'resources'): for resource in findings.get('resources', []): if self.is_type(resource, 'data'): self.data[resource['address']] = resource else: self.resources[resource['address']] = resource # Resources ( exists in Prior State ) for findings in seek_key_in_dict(self.raw.get('prior_state', {}).get('values', {}).get('root_module', {}).get('resources', {}), 'resource'): for resource in findings.get('resources', []): if self.is_type(resource, 'data'): self.data[resource['address']] = resource else: self.resources[resource['address']] = resource # Child Modules Resources ( exists in State ) for findings in seek_key_in_dict(self.raw.get('values', {}).get('root_module', {}), 'child_modules'): for resource in findings.get('resources', []): if self.is_type(resource, 'data'): self.data[resource['address']] = resource else: self.resources[resource['address']] = resource # Resource Changes ( exists in Plan ) for finding in self.raw.get('resource_changes', {}): resource = deepcopy(finding) change = resource.get('change', {}) actions = change.get('actions', []) if actions != ['delete']: resource['values'] = dict_merge(change.get('after', {}), change.get('after_unknown', {})) if 'change' in resource: del resource['change'] if self.is_type(resource, 'data'): self.data[resource['address']] = resource else: self.resources[resource['address']] = resource
def _parse_resources(self): ''' Assigns all resources defined in the terraform plan :return: none ''' #TODO: Consider about using 'resource_changes' instead of 'resources' # Resources ( exists in Plan ) for findings in seek_key_in_dict( self.raw.get('planned_values', {}).get('root_module', {}), 'resources'): for resource in findings.get('resources', []): if resource['address'].startswith('data'): self.data[resource['address']] = resource else: self.resources[resource['address']] = resource # Resources ( exists in State ) for findings in seek_key_in_dict( self.raw.get('values', {}).get('root_module', {}), 'resources'): for resource in findings.get('resources', []): if resource['address'].startswith('data'): self.data[resource['address']] = resource else: self.resources[resource['address']] = resource # Resources ( exists in Prior State ) for findings in seek_key_in_dict( self.raw.get('prior_state', {}).get('values', {}).get('root_module', {}).get('resources', {}), 'resource'): for resource in findings.get('resources', []): if resource['address'].startswith('data'): self.data[resource['address']] = resource else: self.resources[resource['address']] = resource # Child Modules Resources ( exists in State ) for findings in seek_key_in_dict( self.raw.get('values', {}).get('root_module', {}), 'child_modules'): for resource in findings.get('resources', []): if resource['address'].startswith('data'): self.data[resource['address']] = resource else: self.resources[resource['address']] = resource
def encryption_is_enabled(_step_obj): for resource in _step_obj.context.stash: if type(resource) is dict: prop = encryption_property.get(resource['type'], None) if not prop: raise TerraformComplianceNotImplemented( 'Encryption property for {} ' 'is not implemented yet.'.format(resource['type'])) encryption_value = seek_key_in_dict( resource.get('values', {}), encryption_property[resource['type']]) if len(encryption_value): encryption_value = encryption_value[0] if type(encryption_value) is dict: encryption_value = encryption_value[encryption_property[ resource['type']]] if not encryption_value: raise Failure( 'Resource {} does not have encryption enabled ({}={}).'. format(resource['address'], prop, encryption_value)) return True
def i_action_them(_step_obj, action_type): if action_type == "count": # WARNING: Only case where we set stash as a dictionary, instead of a list. if isinstance(_step_obj.context.stash, list): # This means we are directly started counting without drilling down any property # Thus, our target for the count is stash itself. if _step_obj.context.property_name in types_list: _step_obj.context.stash = dict( values=len(_step_obj.context.stash)) else: if isinstance(_step_obj.context.stash[0], dict): if _step_obj.context.stash[0].get('values'): _step_obj.context.stash = seek_key_in_dict( _step_obj.context.stash, 'values') count = 0 for result in _step_obj.context.stash: count += len(result.get( 'values', {})) if result.get('values') else 1 _step_obj.context.stash = dict(values=count) else: _step_obj.context.stash = dict( values=len(_step_obj.context.stash)) else: raise TerraformComplianceNotImplemented( 'Invalid action_type in the scenario: {}'.format(action_type))
def test_seek_in_dict_for_root_and_non_root_values(self): dictionary = dict(a=dict(b=dict(something=dict(test=[]))), something=["b"]) search_key = "something" expected = [{'something': {'test': []}}, {'something': ['b']}] self.assertEqual(seek_key_in_dict(dictionary, search_key), expected)
def test_seek_in_dict_finding_multiple_keys_in_nested_dict(self): dictionary = dict(search_key=dict( something=dict(something_else=None, something=['something_else'])), something=[]) search_key = 'something' expected = [{ 'something': { 'something_else': None, 'something': ['something_else'] } }, { 'something': [] }] self.assertEqual(seek_key_in_dict(dictionary, search_key), expected)
def i_action_them(_step_obj, action_type): if action_type == "count": # WARNING: Only case where we set stash as a dictionary, instead of a list. if type(_step_obj.context.stash) is list: if type(_step_obj.context.stash[0]) is dict(): if _step_obj.context.stash.get('values'): _step_obj.context.stash = seek_key_in_dict(_step_obj.context.stash, 'values') count = 0 for result in _step_obj.context.stash: count += len(result.get('values', {})) if result.get('values') else 1 _step_obj.context.stash = {'values': count} else: _step_obj.context.stash = {'values': len(_step_obj.context.stash)} else: raise TerraformComplianceNotImplemented('Invalid action_type in the scenario: {}'.format(action_type))
def property_is_enabled(_step_obj, something): for resource in _step_obj.context.stash: if type(resource) is dict: if something in property_match_list: something = property_match_list[something].get(resource['type'], something) property_value = seek_key_in_dict(resource.get('values', {}), something) if len(property_value): property_value = property_value[0] if type(property_value) is dict: property_value = property_value.get(something, Null) if not property_value: raise Failure('Resource {} does not have {} property enabled ({}={}).'.format(resource.get('address', "resource"), something, something, property_value)) return True
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)
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 it_condition_contain_something(_step_obj, something): prop_list = [] if _step_obj.context.type in ('resource', 'data'): for resource in _step_obj.context.stash: if type(resource) is not dict: resource = { 'values': resource, 'address': resource, 'type': _step_obj.context.name } values = resource.get('values', resource.get('expressions', {})) found_value = Null found_key = Null if type(values) is dict: found_key = values.get(something, seek_key_in_dict(values, something)) if type(found_key) is not list: found_key = [{something: found_key}] if len(found_key): found_key = found_key[0] if len( found_key) == 1 else found_key if type(found_key) is dict: found_value = jsonify( found_key.get(something, found_key)) else: found_value = found_key elif type(values) is list: found_value = [] for value in values: if type(value) is dict: # First search in the keys found_key = seek_key_in_dict(value, something) # Then search in the values with 'key' if not found_key: found_key = seek_regex_key_in_dict_values( value, 'key', something) if found_key: found_key = found_key[0] found_value = value.get('value') break if found_key is not Null and len(found_key): found_key = found_key[0] if len( found_key) == 1 else found_key if type(found_key) is dict: found_value.append( jsonify(found_key.get(something, found_key))) if type(found_value) is dict and 'constant_value' in found_value: found_value = found_value['constant_value'] if found_value is not Null and found_value != [] and found_value != '' and found_value != {}: prop_list.append({ 'address': resource['address'], 'values': found_value, 'type': _step_obj.context.name }) elif 'must' in _step_obj.context_sensitive_sentence: raise Failure('{} ({}) does not have {} property.'.format( resource['address'], resource.get('type', ''), something)) if prop_list: _step_obj.context.stash = prop_list _step_obj.context.property_name = something return True skip_step(_step_obj, resource=_step_obj.context.name, message='Can not find any {} property for {} resource in ' 'terraform plan.'.format(something, _step_obj.context.name)) elif _step_obj.context.type == 'provider': values = seek_key_in_dict(_step_obj.context.stash, something) if values: _step_obj.context.stash = values _step_obj.context.property_name = something return True skip_step( _step_obj, resource=_step_obj.context.name, message='Skipping the step since {} type does not have {} property.'. format(_step_obj.context.type, something))
def it_must_contain_something(_step_obj, something, inherited_values=Null): prop_list = [] _step_obj.context.stash = inherited_values if inherited_values is not Null else _step_obj.context.stash if _step_obj.context.type in ('resource', 'data'): for resource in _step_obj.context.stash: if not isinstance(resource, dict) \ or 'values' not in resource \ or 'address' not in resource \ or 'type' not in resource: resource = { 'values': resource, 'address': resource, 'type': _step_obj.context.name } values = resource.get('values', resource.get('expressions', {})) if not values: values = seek_key_in_dict(resource, something) found_value = Null found_key = Null if isinstance(values, dict): found_key = values.get(something, seek_key_in_dict(values, something)) if not isinstance(found_key, list): found_key = [{something: found_key}] if len(found_key): found_key = found_key[0] if len( found_key ) == 1 and something in found_key[0] else found_key if isinstance(found_key, dict): found_value = jsonify( found_key.get(something, found_key)) found_value = found_value if found_value not in ( [], '') else found_key else: found_value = found_key elif isinstance(values, list): found_value = [] for value in values: if isinstance(value, dict): # First search in the keys found_key = seek_key_in_dict(value, something) # Then search in the values with 'key' if not found_key: found_key = seek_regex_key_in_dict_values( value, 'key', something) if found_key: found_key = found_key[0] found_value = value.get('value') break elif isinstance(value, list): found_key, found_value = it_must_contain_something( _step_obj, something, value) if found_key is not Null and len(found_key): found_key = found_key[0] if len( found_key) == 1 else found_key if isinstance(found_key, dict): found_value.append( jsonify(found_key.get(something, found_key))) if isinstance(found_value, dict) and 'constant_value' in found_value: found_value = found_value['constant_value'] if found_value is not Null and found_value != [] and found_value != '' and found_value != {}: prop_list.append({ 'address': resource['address'], 'values': found_value, 'type': _step_obj.context.name }) else: Error( _step_obj, '{} ({}) does not have {} property.'.format( resource['address'], resource.get('type', ''), something)) if prop_list: _step_obj.context.stash = prop_list _step_obj.context.property_name = something return something, prop_list elif _step_obj.context.type == 'provider': for provider_data in _step_obj.context.stash: values = seek_key_in_dict(provider_data, something) if values: _step_obj.context.stash = values _step_obj.context.property_name = something _step_obj.context.address = '{}.{}'.format( provider_data.get('name', _step_obj.context.addresses), provider_data.get('alias', "\b")) return True else: Error( _step_obj, '{} {} does not have {} property.'.format( _step_obj.context.addresses, _step_obj.context.type, something)) Error( _step_obj, '{} {} does not have {} property.'.format( _step_obj.context.addresses, _step_obj.context.type, something))
def it_condition_contain_something(_step_obj, something): prop_list = [] if _step_obj.context.type == 'resource': for resource in _step_obj.context.stash: if type(resource) is not dict: resource = { 'values': resource, 'address': resource, 'type': _step_obj.context.name } values = resource.get('values', {}) found_value = None found_key = None if type(values) is dict: found_key = seek_key_in_dict(values, something) if len(found_key): found_key = found_key[0] if type(found_key) is dict: found_value = jsonify(found_key[something]) elif type(values) is list: for value in values: if type(value) is dict: # First search in the keys found_key = seek_key_in_dict(value, something) # Then search in the values with 'key' if not found_key: found_key = seek_regex_key_in_dict_values( value, 'key', something) if found_key: found_key = found_key[0] found_value = value.get('value') break else: raise TerraformComplianceInternalFailure( 'Unexpected value type {}. {}'.format( type(value), value)) if found_key: prop_list.append({ 'address': resource['address'], 'values': found_value, 'type': _step_obj.context.name }) elif 'must' in _step_obj.context_sensitive_sentence: raise Failure('{} ({}) does not have {} property.'.format( resource['address'], resource.get('type', ''), something)) if prop_list: _step_obj.context.stash = prop_list _step_obj.context.property_name = something return True skip_step(_step_obj, resource=_step_obj.context.name, message='Can not find any {} property for {} resource in ' 'terraform plan.'.format(something, _step_obj.context.name)) elif _step_obj.context.type == 'provider': values = seek_key_in_dict(_step_obj.context.stash, something) if values: _step_obj.context.stash = values _step_obj.context.property_name = something return True skip_step( _step_obj, resource=_step_obj.context.name, message='Skipping the step since {} type does not have {} property.'. format(_step_obj.context.type, something))
def it_must_contain_something(_step_obj, something, inherited_values=Null): match = _step_obj.context.match seek_key_in_dict, seek_regex_key_in_dict_values = match.seek_key_in_dict, match.seek_regex_key_in_dict_values prop_list = [] _step_obj.context.stash = inherited_values if inherited_values is not Null else _step_obj.context.stash if _step_obj.context.type in ('resource', 'data'): for resource in _step_obj.context.stash: if not isinstance(resource, dict) \ or 'values' not in resource \ or 'address' not in resource \ or 'type' not in resource: resource = { 'values': resource, 'address': resource, 'type': _step_obj.context.name } # not going to use match.get here because the following line is an edge case values = resource.get('values', resource.get('expressions', {})) if not values: values = seek_key_in_dict(resource, something) found_value = Null found_key = Null if isinstance(values, dict): found_key = match.get(values, something, seek_key_in_dict(values, something)) if not isinstance(found_key, list): found_key = [{something: found_key}] if len(found_key): found_key = found_key[0] if len( found_key ) == 1 and something in found_key[0] else found_key if isinstance(found_key, dict): found_value = match.get(found_key, something, found_key) found_value = found_value if found_value not in ( [], '') else found_key else: found_value = found_key elif isinstance(values, list): found_value = [] for value in values: if isinstance(value, dict): # First search in the keys found_key = seek_key_in_dict(value, something) # Then search in the values with 'key' if not found_key: found_key = seek_regex_key_in_dict_values( value, 'key', something) if found_key: found_key = found_key[0] # not going to use match.get here because the following line is an edge case found_value.extend(value.get('value')) break elif isinstance(value, list): found_key, temp_found_value = it_must_contain_something( _step_obj, something, value) found_value.extend(temp_found_value) elif isinstance(value, (str, bool, int, float)): if match.equals(value, something): found_value.append(value) if found_key is not Null and len(found_key): found_key = found_key[0] if len( found_key) == 1 else found_key if isinstance(found_key, dict): found_value.append( found_key.get(something, found_key)) if isinstance(found_value, dict) and 'constant_value' in found_value: found_value = found_value['constant_value'] if found_value not in (Null, [], '', {}): prop_list.append({ 'address': resource['address'], 'values': found_value, 'type': _step_obj.context.name }) else: Error( _step_obj, '{} ({}) does not have {} property.'.format( resource['address'], resource.get('type', ''), something)) if prop_list: _step_obj.context.stash = prop_list _step_obj.context.property_name = something return something, prop_list elif _step_obj.context.type == 'provider': _step_obj.context.stash = [] for provider_data in _step_obj.context.stash: values = seek_key_in_dict(provider_data, something) if values: _step_obj.context.stash.append(values) _step_obj.context.property_name = something _step_obj.context.address = '{}.{}'.format( provider_data.get('name', _step_obj.context.addresses), provider_data.get('alias', "\b")) return True else: Error( _step_obj, '{} {} does not have {} property.'.format( _step_obj.context.addresses, _step_obj.context.type, something)) Error( _step_obj, '{} {} does not have {} property.'.format( _step_obj.context.addresses, _step_obj.context.type, something))
def test_seek_in_dict_finding_a_key_in_nested_dict(self): dictionary = dict(search_key=dict(something=dict(something_else=None))) search_key = 'something' expected = [{'something': {'something_else': None}}] self.assertEqual(seek_key_in_dict(dictionary, search_key), expected)
def test_seek_in_dict_finding_values_in_non_dicts_on_root(self): dictionary = 'something_else' search_key = 'something_else' expected = [] self.assertEqual(seek_key_in_dict(dictionary, search_key), expected)
def it_has_something(_step_obj, something, inherited_values=Null): prop_list = [] _step_obj.context.stash = inherited_values if inherited_values is not Null else _step_obj.context.stash if _step_obj.context.type in ('resource', 'data'): for resource in _step_obj.context.stash: if not isinstance(resource, dict) \ or 'values' not in resource \ or 'address' not in resource \ or 'type' not in resource: resource = {'values': resource, 'address': resource, 'type': _step_obj.context.name} values = resource.get('values', resource.get('expressions', {})) if not values: values = seek_key_in_dict(resource, something) found_value = Null found_key = Null if isinstance(values, dict): found_key = values.get(something, seek_key_in_dict(values, something)) if not isinstance(found_key, list): found_key = [{something: found_key}] if len(found_key): found_key = found_key[0] if len(found_key) == 1 and something in found_key[0] else found_key if isinstance(found_key, dict): found_value = jsonify(found_key.get(something, found_key)) else: found_value = found_key elif isinstance(values, list): found_value = [] for value in values: if isinstance(value, dict): # First search in the keys found_key = seek_key_in_dict(value, something) # Then search in the values with 'key' if not found_key: found_key = seek_regex_key_in_dict_values(value, 'key', something) if found_key: found_key = found_key[0] found_value = value.get('value') break elif isinstance(value, list): found_key, found_value = it_has_something(_step_obj, something, value) if found_key is not Null and len(found_key): found_key = found_key[0] if len(found_key) == 1 else found_key if isinstance(found_key, dict): found_value.append(jsonify(found_key.get(something, found_key))) if isinstance(found_value, dict) and 'constant_value' in found_value: found_value = found_value['constant_value'] if found_value not in (Null, [], '', {}): prop_list.append(resource) if prop_list: _step_obj.context.stash = prop_list _step_obj.context.property_name = something return something, prop_list if _step_obj.state != Step.State.FAILED: skip_step(_step_obj, resource=_step_obj.context.name, message='Can not find any {} property for {} resource in ' 'terraform plan.'.format(something, _step_obj.context.name)) elif _step_obj.context.type == 'provider': for provider_data in _step_obj.context.stash: values = seek_key_in_dict(provider_data, something) if values: _step_obj.context.stash = provider_data _step_obj.context.property_name = something _step_obj.context.address = '{}.{}'.format(provider_data.get('name', _step_obj.context.addresses), provider_data.get('alias', "\b")) return True if _step_obj.state != Step.State.FAILED: skip_step(_step_obj, resource=_step_obj.context.name, message='Skipping the step since {} type does not have {} property.'.format(_step_obj.context.type, something))
def test_seek_in_dict_finding_a_key_in_root(self): dictionary = dict(search_key=dict(something=[])) search_key = 'search_key' expected = [{'search_key': {'something': []}}] self.assertEqual(seek_key_in_dict(dictionary, search_key), expected)
def it_contains_something_old(_step_obj, something, inherited_values=Null): console_write("\t{} {}: {}".format(Defaults().warning_icon, Defaults().warning_colour('WARNING'), Defaults().info_colour('"When it contains {}" step functionality will be changed' ' on future versions and the functionality will be same ' 'as "When it has {}" step. Please use the ' 'latter.'.format(something, something)))) prop_list = [] _step_obj.context.stash = inherited_values if inherited_values is not Null else _step_obj.context.stash if _step_obj.context.type in ('resource', 'data'): for resource in _step_obj.context.stash: if not isinstance(resource, dict) \ or 'values' not in resource \ or 'address' not in resource \ or 'type' not in resource: resource = {'values': resource, 'address': resource, 'type': _step_obj.context.name} values = resource.get('values', resource.get('expressions', {})) if not values: values = seek_key_in_dict(resource, something) found_value = Null found_key = Null if isinstance(values, dict): found_key = values.get(something, seek_key_in_dict(values, something)) if not isinstance(found_key, list): found_key = [{something: found_key}] if len(found_key): found_key = found_key[0] if len(found_key) == 1 and something in found_key[0] else found_key if isinstance(found_key, dict): found_value = jsonify(found_key.get(something, found_key)) else: found_value = found_key elif isinstance(values, list): found_value = [] for value in values: if isinstance(value, dict): # First search in the keys found_key = seek_key_in_dict(value, something) # Then search in the values with 'key' if not found_key: found_key = seek_regex_key_in_dict_values(value, 'key', something) if found_key: found_key = found_key[0] found_value = value.get('value') break elif isinstance(value, list): found_key, found_value = it_contains_something_old(_step_obj, something, value) if found_key is not Null and len(found_key): found_key = found_key[0] if len(found_key) == 1 else found_key if isinstance(found_key, dict): found_value.append(jsonify(found_key.get(something, found_key))) if isinstance(found_value, dict) and 'constant_value' in found_value: found_value = found_value['constant_value'] if found_value is not Null and found_value != [] and found_value != '' and found_value != {}: prop_list.append({'address': resource['address'], 'values': found_value, 'type': _step_obj.context.name}) if prop_list: _step_obj.context.stash = prop_list _step_obj.context.property_name = something return something, prop_list if _step_obj.state != Step.State.FAILED: skip_step(_step_obj, resource=_step_obj.context.name, message='Can not find any {} property for {} resource in ' 'terraform plan.'.format(something, _step_obj.context.name)) elif _step_obj.context.type == 'provider': for provider_data in _step_obj.context.stash: values = seek_key_in_dict(provider_data, something) if values: _step_obj.context.stash = values _step_obj.context.property_name = something _step_obj.context.address = '{}.{}'.format(provider_data.get('name', _step_obj.context.addresses), provider_data.get('alias', "\b")) return True if _step_obj.state != Step.State.FAILED: skip_step(_step_obj, resource=_step_obj.context.name, message='Skipping the step since {} type does not have {} property.'.format(_step_obj.context.type, something))
def it_must_not_contain_something(_step_obj, something, inherited_values=Null): match = _step_obj.context.match seek_key_in_dict, seek_regex_key_in_dict_values = match.seek_key_in_dict, match.seek_regex_key_in_dict_values prop_list = [] _step_obj.context.stash = inherited_values if inherited_values is not Null else _step_obj.context.stash if _step_obj.context.type in ('resource', 'data'): for resource in _step_obj.context.stash: if not isinstance(resource, dict) \ or 'values' not in resource \ or 'address' not in resource \ or 'type' not in resource: resource = { 'values': resource, 'address': resource, 'type': _step_obj.context.name } values = resource.get('values', resource.get('expressions', {})) if not values: values = seek_key_in_dict(resource, something) found_value = Null found_key = Null if isinstance(values, dict): found_key = match.get(values, something, seek_key_in_dict(values, something)) if not isinstance(found_key, list): found_key = [{something: found_key}] if len(found_key): found_key = found_key[0] if len( found_key ) == 1 and something in found_key[0] else found_key if isinstance(found_key, dict): found_value = match.get(found_key, something, found_key) found_value = found_value if found_value not in ( [], '') else found_key else: found_value = found_key elif isinstance(values, list): found_value = [] for value in values: if isinstance(value, dict): # First search in the keys found_key = seek_key_in_dict(value, something) # Then search in the values with 'key' if not found_key: found_key = seek_regex_key_in_dict_values( value, 'key', something) if found_key: found_key = found_key[0] found_value.append(value.get('value')) break elif isinstance(value, list): found_key, temp_found_value = it_must_contain_something( _step_obj, something, value) found_value.extend(temp_found_value) elif isinstance(value, (str, bool, int, float)): if match.equals(value, something): found_value.append(value) if found_key is not Null and len(found_key): found_key = found_key[0] if len( found_key) == 1 else found_key if isinstance(found_key, dict): found_value.append( found_key.get(something, found_key)) if isinstance(found_value, dict) and 'constant_value' in found_value: found_value = found_value['constant_value'] # if found_value is not Null and found_value != [] and found_value != '' and found_value != {}: if found_value not in (Null, [], '', {}): Error( _step_obj, '{} property exists in {} ({}).'.format( something, resource['address'], resource.get('type', ''))) elif _step_obj.context.type == 'provider': for provider_data in _step_obj.context.stash: values = seek_key_in_dict(provider_data, something) if values: Error( _step_obj, '{} {} does not have {} property.'.format( _step_obj.context.addresses, _step_obj.context.type, something))
def it_does_not_have_something(_step_obj, something, inherited_values=Null): prop_list = [] _step_obj.context.stash = inherited_values if inherited_values is not Null else _step_obj.context.stash if _step_obj.context.type in ('resource', 'data'): for resource in _step_obj.context.stash: if not isinstance(resource, dict) \ or 'values' not in resource \ or 'address' not in resource \ or 'type' not in resource: resource = {'values': resource, 'address': resource, 'type': _step_obj.context.name} values = resource.get('values', resource.get('expressions', {})) if not values: values = seek_key_in_dict(resource, something) found_value = Null found_key = Null if isinstance(values, dict): found_key = values.get(something, seek_key_in_dict(values, something)) if not isinstance(found_key, list): found_key = [{something: found_key}] if len(found_key): found_key = found_key[0] if len(found_key) == 1 and something in found_key[0] else found_key if isinstance(found_key, dict): found_value = jsonify(found_key.get(something, found_key)) else: found_value = found_key elif isinstance(values, list): found_value = [] for value in values: if isinstance(value, dict): # First search in the keys found_key = seek_key_in_dict(value, something) # Then search in the values with 'key' if not found_key: found_key = seek_regex_key_in_dict_values(value, 'key', something) if found_key: found_key = found_key[0] found_value = value.get('value') break elif isinstance(value, list): found_key, found_value = it_has_something(_step_obj, something, value) if found_key is not Null and len(found_key): found_key = found_key[0] if len(found_key) == 1 else found_key if isinstance(found_key, dict): found_value.append(jsonify(found_key.get(something, found_key))) if isinstance(found_value, dict) and 'constant_value' in found_value: found_value = found_value['constant_value'] if found_value is not Null and found_value != [] and found_value != '' and found_value != {}: prop_list.append(resource['address']) prop_list = [resource for resource in _step_obj.context.stash if resource['address'] not in prop_list] _step_obj.context.property_name = something if prop_list: _step_obj.context.stash = prop_list return something, prop_list if _step_obj.state != Step.State.FAILED: skip_step(_step_obj, resource=_step_obj.context.name, message='All objects ({}) coming from previous step has {} ' 'property.'.format(_step_obj.context.name, something)) elif _step_obj.context.type == 'provider': stash = [] for provider_data in _step_obj.context.stash: values = seek_key_in_dict(provider_data, something) if values: return False else: stash.append(provider_data) if stash: _step_obj.context.stash = stash _step_obj.context.property_name = something return True return True
def it_must_contain_something(_step_obj, something, inherited_values=Null, child=False): match = _step_obj.context.match seek_key_in_dict, seek_regex_key_in_dict_values = match.seek_key_in_dict, match.seek_regex_key_in_dict_values prop_list = [] _step_obj.context.stash = inherited_values if inherited_values is not Null else _step_obj.context.stash if _step_obj.context.type in ('resource', 'data'): for resource in _step_obj.context.stash: if not isinstance(resource, dict) \ or 'values' not in resource \ or 'address' not in resource \ or 'type' not in resource: resource = { 'values': resource, 'address': resource, 'type': _step_obj.context.name } # not going to use match.get here because the following line is an edge case values = resource.get('values', resource.get('expressions', {})) if not values: values = seek_key_in_dict(resource, something) found_values = [] found_key = Null # this could also become a list resource_passed = False # set this to True if you get anything from the resource, don't set it to False if you get empty values as there could be other values as well if isinstance(values, dict): found_key = match.get(values, something, Null) if found_key is not Null: found_key = [{something: found_key}] else: found_key = seek_key_in_dict(values, something) for kv_pair in found_key: # kv_pair must be in {something: found_key} format. if not isinstance(kv_pair, dict): continue # should raise exception # ignore the values that correspond to Null # Following line could be problematic, how to determine if something is set to be empty or not set? Behavior is provider dependent. # For now, allow '' and don't allow [] as per user cases. if match.get(kv_pair, something) not in ([], ): found_values.append(match.get(kv_pair, something)) resource_passed = True elif isinstance(values, list): for value in values: if isinstance(value, dict): # First search in the keys found_key = seek_key_in_dict(value, something) # The following is an edge case that covers things like aws asg tags (https://www.terraform.io/docs/providers/aws/r/autoscaling_group.html) # Then search in the values with 'key' if not found_key: found_key = seek_regex_key_in_dict_values( value, 'key', something) if found_key: found_key = found_key[0] # not going to use match.get here because the following line is an edge case found_values.extend(value.get('value')) resource_passed = True continue elif isinstance(value, list): _, temp_found_values = it_must_contain_something( _step_obj, something, value, child=True) prop_list.extend(temp_found_values) found_values.append('added_to_proplist') resource_passed = True elif isinstance(value, (str, bool, int, float)): if match.equals(value, something): found_values.append(value) if found_key is not Null and len(found_key): for found_key_instance in found_key: if isinstance(found_key_instance, dict): if match.get( found_key_instance, something, Null) not in (Null, [], '', {}, 'added_to_proplist'): found_values.append( match.get(found_key_instance, something)) resource_passed = True for i, found_val in enumerate(found_values): if isinstance(found_val, dict) and 'constant_value' in found_val: found_values[i] = found_val['constant_value'] for found_val in found_values: if found_val not in ( Null, [], '', {}, 'added_to_proplist'): # slightly redundant now. prop_list.append({ 'address': resource['address'], 'values': found_val, 'type': _step_obj.context.name }) # do not check prop list here because every resource should contain it. if not resource_passed and not child: # if nothing was found in this resource, don't error if you're a child Error( _step_obj, '{} ({}) does not have {} property.'.format( resource['address'], resource.get('type', ''), something)) if prop_list: _step_obj.context.stash = prop_list _step_obj.context.property_name = something return something, prop_list elif _step_obj.context.type == 'provider': _step_obj.context.stash = [] for provider_data in _step_obj.context.stash: values = seek_key_in_dict(provider_data, something) if values: _step_obj.context.stash.append(values) _step_obj.context.property_name = something _step_obj.context.address = '{}.{}'.format( provider_data.get('name', _step_obj.context.addresses), provider_data.get('alias', "\b")) return True else: Error( _step_obj, '{} {} does not have {} property.'.format( _step_obj.context.addresses, _step_obj.context.type, something)) Error( _step_obj, '{} {} does not have {} property.'.format( _step_obj.context.addresses, _step_obj.context.type, something))
def it_must_not_contain_something(_step_obj, something, inherited_values=Null): match = _step_obj.context.match seek_key_in_dict, seek_regex_key_in_dict_values = match.seek_key_in_dict, match.seek_regex_key_in_dict_values prop_list = [] _step_obj.context.stash = inherited_values if inherited_values is not Null else _step_obj.context.stash if _step_obj.context.type in ('resource', 'data'): for resource in _step_obj.context.stash: if not isinstance(resource, dict) \ or 'values' not in resource \ or 'address' not in resource \ or 'type' not in resource: resource = { 'values': resource, 'address': resource, 'type': _step_obj.context.name } values = resource.get('values', resource.get('expressions', {})) if not values: values = seek_key_in_dict(resource, something) found_values = [] found_key = Null resource_passed = False # set this to True if you get anything from the resource, don't set it to False if you get empty values as there could be other values as well if isinstance(values, dict): found_key = match.get(values, something, Null) if found_key is not Null: found_key = [{something: found_key}] else: found_key = seek_key_in_dict(values, something) for kv_pair in found_key: # kv_pair must be in {something: found_key} format. if not isinstance(kv_pair, dict): continue # could raise an exception # ignore the values that correspond to Null # Following line could be problematic, how to determine if something is set to be empty or not set? Behavior is provider dependent. # For now, allow '' and don't allow [] as per user cases. if match.get(kv_pair, something) not in ([], ): found_values.append(match.get(kv_pair, something)) resource_passed = True elif isinstance(values, list): for value in values: if isinstance(value, dict): # First search in the keys found_key = seek_key_in_dict(value, something) # Then search in the values with 'key' if not found_key: found_key = seek_regex_key_in_dict_values( value, 'key', something) if found_key: found_key = found_key[0] found_values.extend(value.get('value')) resource_passed = True continue elif isinstance(value, list): _, temp_found_values = it_must_contain_something( _step_obj, something, value, child=True) prop_list.extend(temp_found_values) found_values.append('added_to_proplist') resource_passed = True elif isinstance(value, (str, bool, int, float)): if match.equals(value, something): found_values.append(value) if found_key is not Null and len(found_key): for found_key_instance in found_key: if isinstance(found_key_instance, dict): if match.get( found_key_instance, something, Null) not in (Null, [], '', {}, 'added_to_proplist'): found_values.append( match.get(found_key_instance, something)) resource_passed = True for i, found_val in enumerate(found_values): if isinstance(found_val, dict) and 'constant_value' in found_val: found_values[i] = found_val['constant_value'] if resource_passed: Error( _step_obj, '{} property exists in {} ({}).'.format( something, resource['address'], resource.get('type', ''))) elif _step_obj.context.type == 'provider': for provider_data in _step_obj.context.stash: values = seek_key_in_dict(provider_data, something) if values: Error( _step_obj, '{} {} does not have {} property.'.format( _step_obj.context.addresses, _step_obj.context.type, something))