summaryrefslogtreecommitdiff
path: root/morphlib/morphloader.py
diff options
context:
space:
mode:
Diffstat (limited to 'morphlib/morphloader.py')
-rw-r--r--morphlib/morphloader.py296
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.