def validate_inputs_types(inputs, inputs_schema): for input_key, _input in inputs_schema.iteritems(): input_type = _input.get('type') if input_type is None: # no type defined - no validation continue input_val = inputs[input_key] if functions.parse(input_val) != input_val: # intrinsic function - not validated at the moment continue if input_type == 'integer': if isinstance(input_val, (int, long)) and not \ isinstance(input_val, bool): continue elif input_type == 'float': if isinstance(input_val, (int, float, long)) and not \ isinstance(input_val, bool): continue elif input_type == 'boolean': if isinstance(input_val, bool): continue elif input_type == 'string': continue else: raise DSLParsingLogicException( 80, "Unexpected type defined in inputs schema " "for input '{0}' - unknown type is {1}".format( input_key, input_type)) raise DSLParsingLogicException( 50, "Input type validation failed: Input '{0}' type " "is '{1}', yet it was assigned with the value '{2}'".format( input_key, input_type, input_val))
def merge_schema_and_instance_properties( instance_properties, schema_properties, undefined_property_error_message, missing_property_error_message, node_name): flattened_schema_props = flatten_schema(schema_properties) # validate instance properties don't # contain properties that are not defined # in the schema. for key in instance_properties.iterkeys(): if key not in flattened_schema_props: ex = DSLParsingLogicException( 106, undefined_property_error_message.format(node_name, key)) ex.property = key raise ex merged_properties = dict(flattened_schema_props.items() + instance_properties.items()) for key, value in merged_properties.iteritems(): if value is None: ex = DSLParsingLogicException( 107, missing_property_error_message.format(node_name, key)) ex.property = key raise ex _validate_properties_types(merged_properties, schema_properties) return merged_properties
def extract_complete_type_recursive(dsl_type, dsl_type_name, dsl_container, merging_func, is_relationships, visited_type_names=None): """ This method is applicable to both types and relationships. it's concerned with extracting the super types recursively, where the merging_func parameter is used to merge them with the current type :param dsl_type: :param dsl_type_name: :param dsl_container: :param merging_func: :param is_relationships: :param visited_type_names: :return: """ if not visited_type_names: visited_type_names = [] if dsl_type_name in visited_type_names: visited_type_names.append(dsl_type_name) ex = DSLParsingLogicException( 100, 'Failed parsing {0} {1}, Circular dependency detected: {2}' .format('relationship' if is_relationships else 'type', dsl_type_name, ' --> '.join(visited_type_names))) ex.circular_dependency = visited_type_names raise ex visited_type_names.append(dsl_type_name) current_level_type = copy.deepcopy(dsl_type) # halt condition if 'derived_from' not in current_level_type: return current_level_type super_type_name = current_level_type['derived_from'] if super_type_name not in dsl_container: raise DSLParsingLogicException( 14, 'Missing definition for {0} {1} which is declared as derived ' 'by {0} {2}' .format('relationship' if is_relationships else 'type', super_type_name, dsl_type_name)) super_type = dsl_container[super_type_name] complete_super_type = extract_complete_type_recursive( dsl_type=super_type, dsl_type_name=super_type_name, dsl_container=dsl_container, merging_func=merging_func, visited_type_names=visited_type_names, is_relationships=is_relationships) return merging_func(complete_super_type, current_level_type)
def _merge_flattened_schema_and_instance_properties( instance_properties, schema_properties, flattened_schema_properties, data_types, undefined_property_error_message, missing_property_error_message, node_name, path, raise_on_missing_property): path = path or [] # validate instance properties don't # contain properties that are not defined # in the schema. for key in instance_properties: if key not in schema_properties: ex = DSLParsingLogicException( 106, undefined_property_error_message.format( node_name, _property_description(path, key))) ex.property = key raise ex merged_properties = flattened_schema_properties.copy() merged_properties.update(instance_properties) result = {} for key, property_schema in schema_properties.items(): if key not in merged_properties: required = property_schema.get('required', True) if required and raise_on_missing_property: ex = DSLParsingLogicException( 107, missing_property_error_message.format( node_name, _property_description(path, key))) ex.property = key raise ex else: continue prop_path = copy.copy(path) prop_path.append(key) result[key] = parse_value( value=merged_properties.get(key), derived_value=flattened_schema_properties.get(key), type_name=property_schema.get('type'), data_types=data_types, undefined_property_error_message=undefined_property_error_message, missing_property_error_message=missing_property_error_message, node_name=node_name, path=prop_path, raise_on_missing_property=raise_on_missing_property) return result
def parse_value(value, type_name, data_types, undefined_property_error_message, missing_property_error_message, node_name, path, derived_value=None, raise_on_missing_property=True): if type_name is None: return value if functions.parse(value) != value: # intrinsic function - not validated at the moment return value if type_name == 'integer': if isinstance(value, (int, long)) and not isinstance(value, bool): return value elif type_name == 'float': if isinstance(value, (int, float, long)) and not isinstance(value, bool): return value elif type_name == 'boolean': if isinstance(value, bool): return value elif type_name == 'string': return value elif type_name in data_types: if isinstance(value, dict): data_schema = data_types[type_name]['properties'] flattened_data_schema = flatten_schema(data_schema) if isinstance(derived_value, dict): flattened_data_schema.update(derived_value) undef_msg = undefined_property_error_message return _merge_flattened_schema_and_instance_properties( instance_properties=value, schema_properties=data_schema, flattened_schema_properties=flattened_data_schema, data_types=data_types, undefined_property_error_message=undef_msg, missing_property_error_message=missing_property_error_message, node_name=node_name, path=path, raise_on_missing_property=raise_on_missing_property) else: raise RuntimeError( "Unexpected type defined in property schema for property '{0}'" " - unknown type is '{1}'".format(_property_description(path), type_name)) raise DSLParsingLogicException( exceptions.ERROR_VALUE_DOES_NOT_MATCH_TYPE, "Property type validation failed in '{0}': property " "'{1}' type is '{2}', yet it was assigned with the " "value '{3}'".format(node_name, _property_description(path), type_name, value))
def resolve(self, import_url): failed_urls = {} # trying to find a matching rule that can resolve this url for rule in self.rules: key = list(rule.keys())[0] value = list(rule.values())[0] prefix = key prefix_len = len(key) if prefix == import_url[:prefix_len]: # found a matching rule url_to_resolve = value + import_url[prefix_len:] # trying to resolve the resolved_url if url_to_resolve not in failed_urls.keys(): # there is no point to try to resolve the same url twice try: return read_import(url_to_resolve) except DSLParsingLogicException as ex: # failed to resolve current rule, # continue to the next one failed_urls[url_to_resolve] = str(ex) # failed to resolve the url using the rules # trying to open the original url try: return read_import(import_url) except DSLParsingLogicException as ex: if not self.rules: raise if not failed_urls: # no matching rules msg = 'None of the resolver rules {0} was applicable, ' \ 'failed to resolve the original import url: {1} '\ .format(self.rules, ex) else: # all urls failed to be resolved msg = 'Failed to resolve the following urls: {0}. ' \ 'In addition, failed to resolve the original ' \ 'import url - {1}'.format(failed_urls, ex) ex = DSLParsingLogicException(13, msg) ex.failed_import = import_url raise ex
def parse_dsl_version(dsl_version): if not dsl_version: raise DSLParsingLogicException( 71, '{0} is missing or empty'.format(VERSION)) if not isinstance(dsl_version, basestring): raise DSLParsingLogicException( 72, 'Invalid {0}: {1} is not a string'.format(VERSION, dsl_version)) # handle the 'dsl_version_' prefix if dsl_version.startswith(DSL_VERSION_PREFIX): short_dsl_version = dsl_version[len(DSL_VERSION_PREFIX):] else: raise DSLParsingLogicException( 73, "Invalid {0}: '{1}', expected a " "value following this format: '{2}'".format( VERSION, dsl_version, DSL_VERSION_1_0)) if not short_dsl_version.__contains__("_"): raise DSLParsingLogicException( 73, "Invalid {0}: '{1}', expected a " "value following this format: '{2}'".format( VERSION, dsl_version, DSL_VERSION_1_0)) version_parts = short_dsl_version.split('_') version_details = collections.namedtuple('version_details', ['major', 'minor', 'micro']) major = version_parts[0] minor = version_parts[1] micro = None if len(version_parts) > 2: micro = version_parts[2] if not major.isdigit(): raise DSLParsingLogicException( 74, "Invalid {0}: '{1}', major version " "is '{2}' while expected to be a number".format( VERSION, dsl_version, major)) if not minor.isdigit(): raise DSLParsingLogicException( 75, "Invalid {0}: '{1}', minor version " "is '{2}' while expected to be a number".format( VERSION, dsl_version, minor)) if micro and not micro.isdigit(): raise DSLParsingLogicException( 76, "Invalid {0}: '{1}', micro version " "is '{2}' while expected to be a number".format( VERSION, dsl_version, micro)) return version_details(int(major), int(minor), int(micro) if micro else None)
def resolve(self, import_url): failed_urls = {} # trying to find a matching rule that can resolve this url for rule in self.rules: # the validate method checks that the dict has exactly 1 element prefix, value = list(rule.items())[0] prefix_len = len(prefix) if prefix == import_url[:prefix_len]: # found a matching rule url_to_resolve = value + import_url[prefix_len:] # trying to resolve the resolved_url if url_to_resolve not in failed_urls: # there is no point to try to resolve the same url twice try: return read_import(url_to_resolve) except DSLParsingLogicException as ex: # failed to resolve current rule, # continue to the next one failed_urls[url_to_resolve] = str(ex) # failed to resolve the url using the rules # trying to open the original url try: return read_import(import_url) except DSLParsingLogicException as ex: if not self.rules: raise if not failed_urls: # no matching rules msg = 'None of the resolver rules {0} was applicable, ' \ 'failed to resolve the original import url: {1} '\ .format(self.rules, ex) else: # all urls failed to be resolved msg = 'Failed to resolve the following urls: {0}. ' \ 'In addition, failed to resolve the original ' \ 'import url - {1}'.format(failed_urls, ex) ex = DSLParsingLogicException(13, msg) ex.failed_import = import_url raise ex
def validate_missing_inputs(inputs, schema_inputs): """Check that all inputs defined in schema_inputs exist in inputs""" missing_inputs = set(schema_inputs) - set(inputs) if missing_inputs: if len(missing_inputs) == 1: message = "Input '{0}' is missing a value".format( missing_inputs.pop()) else: formatted_inputs = ', '.join("'{0}'".format(input_name) for input_name in missing_inputs) message = "Inputs {0} are missing a value".format(formatted_inputs) raise DSLParsingLogicException(ERROR_MISSING_PROPERTY, message)
def _merge_flattened_schema_and_instance_properties( instance_properties, schema_properties, flattened_schema_properties, data_types, undefined_property_error_message, missing_property_error_message, node_name, path, raise_on_missing_property): path = path or [] # validate instance properties don't # contain properties that are not defined # in the schema. for key in instance_properties.iterkeys(): if key not in schema_properties: ex = DSLParsingLogicException( 106, undefined_property_error_message.format( node_name, _property_description(path, key))) ex.property = key raise ex merged_properties = dict(flattened_schema_properties.items() + instance_properties.items()) result = {} for key, property_schema in schema_properties.iteritems(): if key not in merged_properties: required = property_schema.get('required', True) if required and raise_on_missing_property: ex = DSLParsingLogicException( 107, missing_property_error_message.format( node_name, _property_description(path, key))) ex.property = key raise ex else: continue prop_path = copy.copy(path) prop_path.append(key) result[key] = parse_value( value=merged_properties.get(key), derived_value=flattened_schema_properties.get(key), type_name=property_schema.get('type'), data_types=data_types, undefined_property_error_message=undefined_property_error_message, missing_property_error_message=missing_property_error_message, node_name=node_name, path=prop_path, raise_on_missing_property=raise_on_missing_property) return result
def validate_dsl_version(dsl_version): if dsl_version not in SUPPORTED_VERSIONS: raise DSLParsingLogicException( 29, 'Unexpected tosca_definitions_version {0}; Currently ' 'supported versions are: {1}'.format(dsl_version, SUPPORTED_VERSIONS))
def validate_missing_inputs(inputs): for key, value in inputs.iteritems(): if value is None: raise DSLParsingLogicException( 107, "Input '{0}' is missing a value".format(key))
class DefaultImportResolver(AbstractImportResolver): """ This class is a default implementation of an import resolver. This resolver uses the rules to replace URL's prefix with another prefix and tries to resolve the new URL (after the prefix has been replaced). If there aren't any rules, none of the rules matches or none of the prefix replacements works, the resolver will try to use the original URL. Each rule in the ``rules`` list is expected to be a dictionary with one (key, value) pair which represents a prefix and its replacement which can be used to resolve the import url. The resolver will go over the rules and for each matching rule (its key is a prefix of the url) it will replace the prefix with the value and will try to resolve the new url. For example: The rules list: [ {'http://prefix1': 'http://prefix1_replacement'}, {'http://prefix2': 'http://prefix2_replacement1'}, {'http://prefix2': 'http://prefix2_replacement2'} ] contains three rules that can be used for resolve URLs that starts with 'http://prefix1' and 'http://prefix2'. If the url is 'http://prefix2.suffix2.org' than the resolve method will find a match in both the second and the third rules. It will first try to apply the second rule by replacing the url's prefix with the second rule value ('http://prefix2_replacement1') and will try to resolve the new url: 'http://prefix2_replacement1.suffix2.org'. In case this url cannot be resolved, it will try to apply the third rule by replacing the url's prefix with the third rule value ('http://prefix2_replacement2') and will try to resolve the url: 'http://prefix2_replacement2.suffix2.org'. If this url, also, cannot be resolved, it will try to resolve the original url, i.e. http://prefix2.suffix2.org' In case that all the resolve attempts will fail, a DSLParsingLogicException will be raise. """ def __init__(self, rules=None): # set the rules self.rules = rules if self.rules is None: self.rules = DEFAULT_RULES self._validate_rules() def resolve(self, import_url): failed_urls = {} # trying to find a matching rule that can resolve this url for rule in self.rules: key = rule.keys()[0] value = rule.values()[0] prefix = key prefix_len = len(key) if prefix == import_url[:prefix_len]: # found a matching rule url_to_resolve = value + import_url[prefix_len:] # trying to resolve the resolved_url if url_to_resolve not in failed_urls.keys(): # there is no point to try to resolve the same url twice try: return read_import(url_to_resolve) except DSLParsingLogicException, ex: # failed to resolve current rule, # continue to the next one failed_urls[url_to_resolve] = str(ex) # failed to resolve the url using the rules # trying to open the original url try: return read_import(import_url) except DSLParsingLogicException, ex: if not self.rules: raise if not failed_urls: # no matching rules msg = 'None of the resolver rules {0} was applicable, ' \ 'failed to resolve the original import url: {1} '\ .format(self.rules, ex) else: # all urls failed to be resolved msg = 'Failed to resolve the following urls: {0}. ' \ 'In addition, failed to resolve the original ' \ 'import url - {1}'.format(failed_urls, ex) ex = DSLParsingLogicException(13, msg) ex.failed_import = import_url raise ex