diff options
Diffstat (limited to 'morphlib/morphloader.py')
-rw-r--r-- | morphlib/morphloader.py | 296 |
1 files changed, 24 insertions, 272 deletions
diff --git a/morphlib/morphloader.py b/morphlib/morphloader.py index c0344123..e114b7dd 100644 --- a/morphlib/morphloader.py +++ b/morphlib/morphloader.py @@ -14,9 +14,7 @@ # # =*= License: GPL-2 =*= - import collections -import warnings import yaml import morphlib @@ -58,27 +56,6 @@ class MissingFieldError(MorphologyValidationError): 'Missing field %s from morphology %s' % (field, morphology_name)) -class InvalidFieldError(MorphologyValidationError): - - def __init__(self, field, morphology_name): - self.field = field - self.morphology_name = morphology_name - self.msg = ( - 'Field %s not allowed in morphology %s' % (field, morphology_name)) - - -class InvalidTypeError(MorphologyValidationError): - - def __init__(self, field, expected, actual, morphology_name): - self.field = field - self.expected = expected - self.actual = actual - self.morphology_name = morphology_name - self.msg = ( - 'Field %s expected type %s, got %s in morphology %s' % - (field, expected, actual, morphology_name)) - - class UnknownArchitectureError(MorphologyValidationError): def __init__(self, arch, morph_filename): @@ -101,14 +78,6 @@ class NoStratumBuildDependenciesError(MorphologyValidationError): (stratum_name, morph_filename)) -class EmptyStratumError(MorphologyValidationError): - - def __init__(self, stratum_name, morph_filename): - self.msg = ( - 'Stratum %s has no chunks in %s' % - (stratum_name, morph_filename)) - - class DuplicateChunkError(MorphologyValidationError): def __init__(self, stratum_name, chunk_name): @@ -119,27 +88,6 @@ class DuplicateChunkError(MorphologyValidationError): 'in stratum %(stratum_name)s' % locals()) -class EmptyRefError(MorphologyValidationError): - - def __init__(self, ref_location, morph_filename): - self.ref_location = ref_location - self.morph_filename = morph_filename - MorphologyValidationError.__init__( - self, 'Empty ref found for %(ref_location)s '\ - 'in %(morph_filename)s' % locals()) - - -class ChunkSpecRefNotStringError(MorphologyValidationError): - - def __init__(self, ref_value, chunk_name, stratum_name): - self.ref_value = ref_value - self.chunk_name = chunk_name - self.stratum_name = stratum_name - MorphologyValidationError.__init__( - self, 'Ref %(ref_value)s for %(chunk_name)s '\ - 'in stratum %(stratum_name)s is not a string' % locals()) - - class ChunkSpecConflictingFieldsError(MorphologyValidationError): def __init__(self, fields, chunk_name, stratum_name): @@ -164,17 +112,6 @@ class ChunkSpecNoBuildInstructionsError(MorphologyValidationError): 'and setting "morph: " in the stratum.' % locals()) -class SystemStrataNotListError(MorphologyValidationError): - - def __init__(self, system_name, strata_type): - self.system_name = system_name - self.strata_type = strata_type - typename = strata_type.__name__ - MorphologyValidationError.__init__( - self, 'System %(system_name)s has the wrong type for its strata: '\ - '%(typename)s, expected list' % locals()) - - class DuplicateStratumError(MorphologyValidationError): def __init__(self, system_name, stratum_name): @@ -185,23 +122,6 @@ class DuplicateStratumError(MorphologyValidationError): 'in system %(system_name)s' % locals()) -class SystemStratumSpecsNotMappingError(MorphologyValidationError): - - def __init__(self, system_name, strata): - self.system_name = system_name - self.strata = strata - MorphologyValidationError.__init__( - self, 'System %(system_name)s has stratum specs '\ - 'that are not mappings.' % locals()) - - -class EmptySystemError(MorphologyValidationError): - - def __init__(self, system_name): - MorphologyValidationError.__init__( - self, 'System %(system_name)s has no strata.' % locals()) - - class DependsOnSelfError(MorphologyValidationError): def __init__(self, name, filename): @@ -210,16 +130,6 @@ class DependsOnSelfError(MorphologyValidationError): MorphologyValidationError.__init__(self, msg) -class MultipleValidationErrors(MorphologyValidationError): - - def __init__(self, name, errors): - self.name = name - self.errors = errors - self.msg = 'Multiple errors when validating %(name)s:' - for error in errors: - self.msg += ('\n' + str(error)) - - class DuplicateDeploymentNameError(MorphologyValidationError): def __init__(self, cluster_filename, duplicates): @@ -320,24 +230,6 @@ class MorphologyLoader(object): '''Load morphologies from disk, or save them back to disk.''' - _required_fields = { - 'chunk': [ - 'name', - ], - 'stratum': [ - 'name', - ], - 'system': [ - 'name', - 'arch', - 'strata', - ], - 'cluster': [ - 'name', - 'systems', - ], - } - _static_defaults = { 'chunk': { 'description': '', @@ -380,9 +272,9 @@ class MorphologyLoader(object): }, } - def __init__(self, - predefined_build_systems={}): + def __init__(self, predefined_build_systems={}, schemas={}): self._predefined_build_systems = predefined_build_systems.copy() + self._schemas = schemas if 'manual' not in self._predefined_build_systems: self._predefined_build_systems['manual'] = \ @@ -441,78 +333,60 @@ class MorphologyLoader(object): '''Validate a morphology.''' # Validate that the kind field is there. - self._require_field('kind', morph) + if 'kind' not in morph: + raise MissingFieldError('kind', morph.filename) # The rest of the validation is dependent on the kind. kind = morph['kind'] if kind not in ('system', 'stratum', 'chunk', 'cluster'): raise UnknownKindError(morph['kind'], morph.filename) - required = ['kind'] + self._required_fields[kind] - allowed = self._static_defaults[kind].keys() - self._require_fields(required, morph) - self._deny_unknown_fields(required + allowed, morph) + error = morphlib.util.validate_json( + dict(morph), self._schemas[kind], morph.filename) + if error: + raise MorphologyValidationError(error) getattr(self, '_validate_%s' % kind)(morph) + @classmethod + def get_subsystem_names(cls, system): # pragma: no cover + for subsystem in system.get('subsystems', []): + for name in subsystem['deploy'].iterkeys(): + yield name + for name in cls.get_subsystem_names(subsystem): + yield name + def _validate_cluster(self, morph): + # Deployment names must be unique within a cluster deployments = collections.Counter() for system in morph['systems']: deployments.update(system['deploy'].iterkeys()) if 'subsystems' in system: - deployments.update(self._get_subsystem_names(system)) + deployments.update(self.get_subsystem_names(system)) duplicates = set(deployment for deployment, count in deployments.iteritems() if count > 1) if duplicates: raise DuplicateDeploymentNameError(morph.filename, duplicates) - def _get_subsystem_names(self, system): # pragma: no cover - for subsystem in system.get('subsystems', []): - for name in subsystem['deploy'].iterkeys(): - yield name - for name in self._get_subsystem_names(subsystem): - yield name - def _validate_system(self, morph): - # A system must contain at least one stratum - strata = morph['strata'] - if (not isinstance(strata, collections.Iterable) - or isinstance(strata, collections.Mapping)): - - raise SystemStrataNotListError(morph['name'], - type(strata)) - - if not strata: - raise EmptySystemError(morph['name']) - - if not all(isinstance(o, collections.Mapping) for o in strata): - raise SystemStratumSpecsNotMappingError(morph['name'], strata) + # Architecture name must be known. + if morph['arch'] not in morphlib.valid_archs: + raise UnknownArchitectureError(morph['arch'], morph.filename) # All stratum names should be unique within a system. names = set() + strata = morph['strata'] for spec in strata: name = spec['morph'] if name in names: raise DuplicateStratumError(morph['name'], name) names.add(name) - # Architecture name must be known. - if morph['arch'] not in morphlib.valid_archs: - raise UnknownArchitectureError(morph['arch'], morph.filename) - def _validate_stratum(self, morph): - # Require at least one chunk. - if len(morph.get('chunks', [])) == 0: - raise EmptyStratumError(morph['name'], morph.filename) - # Require build-dependencies for the stratum itself, unless # it has chunks built in bootstrap mode. if 'build-depends' in morph: - if not isinstance(morph['build-depends'], list): - raise InvalidTypeError( - 'build-depends', list, type(morph['build-depends']), - morph['name']) for dep in morph['build-depends']: if dep['morph'] == morph.filename: raise DependsOnSelfError(morph['name'], morph.filename) @@ -535,23 +409,6 @@ class MorphologyLoader(object): # Check each reference to a chunk. for spec in morph['chunks']: chunk_name = spec['name'] - - # All chunk refs must be strings. - if 'ref' in spec: - ref = spec['ref'] - if ref == None: - raise EmptyRefError(spec['name'], morph.filename) - elif not isinstance(ref, basestring): - raise ChunkSpecRefNotStringError( - ref, spec['name'], morph.filename) - - # The build-depends field must be a list. - if 'build-depends' in spec: - if not isinstance(spec['build-depends'], list): - raise InvalidTypeError( - '%s.build-depends' % chunk_name, list, - type(spec['build-depends']), morph['name']) - # Either 'morph' or 'build-system' must be specified. if 'morph' in spec and 'build-system' in spec: raise ChunkSpecConflictingFieldsError( @@ -560,113 +417,8 @@ class MorphologyLoader(object): raise ChunkSpecNoBuildInstructionsError( chunk_name, morph.filename) - @classmethod - def _validate_chunk(cls, morphology): - errors = [] - - if 'products' in morphology: - cls._validate_products(morphology['name'], - morphology['products'], errors) - - for key in MorphologyDumper.keyorder: - if key.endswith('-commands') and key in morphology: - cls._validate_commands(morphology['name'], key, - morphology[key], errors) - - if len(errors) == 1: - raise errors[0] - elif errors: - raise MultipleValidationErrors(morphology['name'], errors) - - @classmethod - def _validate_commands(cls, morphology_name, key, commands, errors): - if commands is None: - return - - for cmd_index, cmd in enumerate(commands): # pragma: no cover - if not isinstance(cmd, basestring): - e = InvalidTypeError('%s[%d]' % (key, cmd_index), - str, type(cmd), morphology_name) - errors.append(e) - - @classmethod - def _validate_products(cls, morphology_name, products, errors): - '''Validate the products field is of the correct type.''' - if (not isinstance(products, collections.Iterable) - or isinstance(products, collections.Mapping)): - raise InvalidTypeError('products', list, - type(products), morphology_name) - - for spec_index, spec in enumerate(products): - - if not isinstance(spec, collections.Mapping): - e = InvalidTypeError('products[%d]' % spec_index, - dict, type(spec), morphology_name) - errors.append(e) - continue - - cls._validate_products_spec_fields_exist(morphology_name, - spec_index, spec, errors) - - if 'include' in spec: - cls._validate_products_specs_include( - morphology_name, spec_index, spec['include'], errors) - - product_spec_required_fields = ('artifact', 'include') - @classmethod - def _validate_products_spec_fields_exist( - cls, morphology_name, spec_index, spec, errors): - - given_fields = sorted(spec.iterkeys()) - missing = (field for field in cls.product_spec_required_fields - if field not in given_fields) - for field in missing: - e = MissingFieldError('products[%d].%s' % (spec_index, field), - morphology_name) - errors.append(e) - unexpected = (field for field in given_fields - if field not in cls.product_spec_required_fields) - for field in unexpected: - e = InvalidFieldError('products[%d].%s' % (spec_index, field), - morphology_name) - errors.append(e) - - @classmethod - def _validate_products_specs_include(cls, morphology_name, spec_index, - include_patterns, errors): - '''Validate that products' include field is a list of strings.''' - # Allow include to be most iterables, but not a mapping - # or a string, since iter of a mapping is just the keys, - # and the iter of a string is a 1 character length string, - # which would also validate as an iterable of strings. - if (not isinstance(include_patterns, collections.Iterable) - or isinstance(include_patterns, collections.Mapping) - or isinstance(include_patterns, basestring)): - - e = InvalidTypeError('products[%d].include' % spec_index, list, - type(include_patterns), morphology_name) - errors.append(e) - else: - for pattern_index, pattern in enumerate(include_patterns): - pattern_path = ('products[%d].include[%d]' % - (spec_index, pattern_index)) - if not isinstance(pattern, basestring): - e = InvalidTypeError(pattern_path, str, - type(pattern), morphology_name) - errors.append(e) - - def _require_field(self, field, morphology): - if field not in morphology: - raise MissingFieldError(field, morphology.filename) - - def _require_fields(self, fields, morphology): - for field in fields: - self._require_field(field, morphology) - - def _deny_unknown_fields(self, allowed, morphology): - for field in morphology: - if field not in allowed: - raise InvalidFieldError(field, morphology.filename) + def _validate_chunk(self, morph): + pass def set_defaults(self, morphology): '''Set all missing fields in the morpholoy to their defaults. |