bootstrap-vz/bootstrapvz/base/manifest.py

121 lines
4.9 KiB
Python
Raw Normal View History

2014-03-23 16:04:03 +01:00
"""The Manifest module contains the manifest that providers and plugins use
to determine which tasks should be added to the tasklist, what arguments various
invocations should have etc..
"""
from bootstrapvz.common.tools import load_data
2013-06-09 20:29:54 +02:00
import logging
log = logging.getLogger(__name__)
class Manifest(object):
2014-03-23 16:04:03 +01:00
"""This class holds all the information that providers and plugins need
to perform the bootstrapping process. All actions that are taken originate from
here. The manifest shall not be modified after it has been loaded.
Currently, immutability is not enforced and it would require a fair amount of code
to enforce it, instead we just rely on tasks behaving properly.
"""
def __init__(self, path):
2014-03-23 16:04:03 +01:00
"""Initializer: Given a path we load, validate and parse the manifest.
:param str path: The path to the manifest
2014-03-23 16:04:03 +01:00
"""
self.path = path
self.load()
self.validate()
self.parse()
def load(self):
"""Loads the manifest and performs a basic validation.
This function reads the manifest, loads the specified provider and plugins, and performs
some basic validation of the manifest itself to ensure that the properties
required for initalization are accessible
(otherwise the user would be presented with some cryptic error messages).
Once the provider and plugins are loaded,
the initialize() function is called on each of them (if it exists).
2014-03-23 16:04:03 +01:00
The provider must have an initialize function.
"""
self.data = load_data(self.path)
from . import validate_manifest
# Validate the manifest with the base validation function in __init__
validate_manifest(self.data, self.schema_validator, self.validation_error)
2014-03-23 16:04:03 +01:00
# Get the provider name from the manifest and load the corresponding module
provider_modname = 'bootstrapvz.providers.' + self.data['provider']['name']
log.debug('Loading provider ' + provider_modname)
2014-03-23 16:04:03 +01:00
# Create a modules dict that contains the loaded provider and plugins
import importlib
self.modules = {'provider': importlib.import_module(provider_modname),
'plugins': [],
}
2014-03-23 16:04:03 +01:00
# Run through all the plugins mentioned in the manifest and load them
if 'plugins' in self.data:
for plugin_name, plugin_data in self.data['plugins'].iteritems():
modname = 'bootstrapvz.plugins.' + plugin_name
log.debug('Loading plugin ' + modname)
plugin = importlib.import_module(modname)
self.modules['plugins'].append(plugin)
2013-05-02 19:13:35 +02:00
2014-03-23 16:04:03 +01:00
# Run the initialize function on the provider and plugins
self.modules['provider'].initialize()
for module in self.modules['plugins']:
2014-03-23 16:04:03 +01:00
# Plugins are not required to have an initialize function
init = getattr(module, 'initialize', None)
if callable(init):
init()
2013-05-02 19:13:35 +02:00
def validate(self):
"""Validates the manifest using the provider and plugin validation functions.
2014-03-23 16:04:03 +01:00
Plugins are not required to have a validate_manifest function
"""
2014-03-23 16:04:03 +01:00
# Run the provider validation
self.modules['provider'].validate_manifest(self.data, self.schema_validator, self.validation_error)
2014-03-23 16:04:03 +01:00
# Run the validation function for any plugin that has it
for plugin in self.modules['plugins']:
validate = getattr(plugin, 'validate_manifest', None)
if callable(validate):
validate(self.data, self.schema_validator, self.validation_error)
2013-06-26 20:14:37 +02:00
def parse(self):
2014-03-23 16:04:03 +01:00
"""Parses the manifest.
Well... "parsing" is a big word.
The function really just sets up some convenient attributes so that tasks
don't have to access information with info.manifest.data['section']
but can do it with info.manifest.section.
"""
self.provider = self.data['provider']
self.bootstrapper = self.data['bootstrapper']
self.image = self.data['image']
self.volume = self.data['volume']
self.system = self.data['system']
2014-03-23 16:04:03 +01:00
# The packages and plugins section is not required
2014-02-23 20:53:58 +01:00
self.packages = self.data['packages'] if 'packages' in self.data else {}
self.plugins = self.data['plugins'] if 'plugins' in self.data else {}
2013-06-26 20:14:37 +02:00
def schema_validator(self, data, schema_path):
2014-03-23 16:04:03 +01:00
"""This convenience function is passed around to all the validation functions
so that they may run a json-schema validation by giving it the data and a path to the schema.
:param dict data: Data to validate (normally the manifest data)
:param str schema_path: Path to the json-schema to use for validation
2014-03-23 16:04:03 +01:00
"""
2013-06-23 23:37:21 +02:00
import jsonschema
schema = load_data(schema_path)
2013-06-23 23:37:21 +02:00
try:
jsonschema.validate(data, schema)
except jsonschema.ValidationError as e:
self.validation_error(e.message, e.path)
2013-05-02 19:13:35 +02:00
def validation_error(self, message, data_path=None):
2014-03-23 16:04:03 +01:00
"""This function is passed to all validation functions so that they may
raise a validation error because a custom validation of the manifest failed.
:param str message: Message to user about the error
:param list data_path: A path to the location in the manifest where the error occurred
:raises ManifestError: With absolute certainty
2014-03-23 16:04:03 +01:00
"""
from bootstrapvz.common.exceptions import ManifestError
raise ManifestError(message, self.path, data_path)