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..
|
|
|
|
"""
|
2014-04-08 14:33:40 +00:00
|
|
|
from bootstrapvz.common.tools import load_json
|
|
|
|
from bootstrapvz.common.tools import load_yaml
|
2013-06-09 20:29:54 +02:00
|
|
|
import logging
|
|
|
|
log = logging.getLogger(__name__)
|
2013-06-09 15:50:00 +02:00
|
|
|
|
|
|
|
|
2014-01-05 14:03:04 +01:00
|
|
|
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.
|
|
|
|
"""
|
2014-01-05 14:03:04 +01:00
|
|
|
def __init__(self, path):
|
2014-03-23 16:04:03 +01:00
|
|
|
"""Initializer: Given a path we load, validate and parse the manifest.
|
|
|
|
|
2014-05-04 19:31:53 +02:00
|
|
|
:param str path: The path to the manifest
|
2014-03-23 16:04:03 +01:00
|
|
|
"""
|
2014-01-05 14:03:04 +01:00
|
|
|
self.path = path
|
|
|
|
self.load()
|
|
|
|
self.validate()
|
|
|
|
self.parse()
|
2013-06-09 15:50:00 +02:00
|
|
|
|
2014-01-05 14:03:04 +01:00
|
|
|
def load(self):
|
2014-03-23 16:04:03 +01:00
|
|
|
"""Loads the manifest.
|
|
|
|
This function not only reads the manifest but also loads the specified provider and plugins.
|
|
|
|
Once they are loaded, the initialize() function is called on each of them (if it exists).
|
|
|
|
The provider must have an initialize function.
|
|
|
|
"""
|
|
|
|
# Load the manifest JSON using the loader in common.tools
|
|
|
|
# It strips comments (which are invalid in strict json) before loading the data.
|
2014-04-08 05:33:34 +00:00
|
|
|
if self.path.endswith('.json'):
|
|
|
|
self.data = load_json(self.path)
|
|
|
|
elif self.path.endswith('.yml') or self.path.endswith('.yaml'):
|
|
|
|
self.data = load_yaml(self.path)
|
|
|
|
|
2014-03-23 16:04:03 +01:00
|
|
|
# Get the provider name from the manifest and load the corresponding module
|
2014-07-05 18:14:29 +02:00
|
|
|
provider_modname = 'bootstrapvz.providers.' + self.data['provider']['name']
|
2014-05-03 22:24:13 +02:00
|
|
|
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
|
2014-04-08 21:25:39 +02:00
|
|
|
import importlib
|
|
|
|
self.modules = {'provider': importlib.import_module(provider_modname),
|
2014-01-05 14:03:04 +01:00
|
|
|
'plugins': [],
|
|
|
|
}
|
2014-03-23 16:04:03 +01:00
|
|
|
# Run through all the plugins mentioned in the manifest and load them
|
2014-01-05 14:03:04 +01:00
|
|
|
if 'plugins' in self.data:
|
|
|
|
for plugin_name, plugin_data in self.data['plugins'].iteritems():
|
2014-05-03 22:24:13 +02:00
|
|
|
modname = 'bootstrapvz.plugins.' + plugin_name
|
|
|
|
log.debug('Loading plugin ' + modname)
|
2014-04-08 21:25:39 +02:00
|
|
|
plugin = importlib.import_module(modname)
|
2014-01-05 14:03:04 +01:00
|
|
|
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
|
2014-01-05 14:03:04 +01:00
|
|
|
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
|
2014-01-05 14:03:04 +01:00
|
|
|
init = getattr(module, 'initialize', None)
|
|
|
|
if callable(init):
|
|
|
|
init()
|
2013-05-02 19:13:35 +02:00
|
|
|
|
2014-01-05 14:03:04 +01:00
|
|
|
def validate(self):
|
2014-03-23 16:04:03 +01:00
|
|
|
"""Validates the manifest using the base, provider and plugin validation functions.
|
|
|
|
Plugins are not required to have a validate_manifest function
|
|
|
|
"""
|
2014-01-05 14:03:04 +01:00
|
|
|
from . import validate_manifest
|
2014-03-23 16:04:03 +01:00
|
|
|
# Validate the manifest with the base validation function in __init__
|
2014-01-05 14:03:04 +01:00
|
|
|
validate_manifest(self.data, self.schema_validator, self.validation_error)
|
2014-05-03 13:37:05 +02:00
|
|
|
|
2014-03-23 16:04:03 +01:00
|
|
|
# Run the provider validation
|
2014-01-05 14:03:04 +01:00
|
|
|
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
|
2014-01-05 14:03:04 +01:00
|
|
|
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
|
|
|
|
2014-01-05 14:03:04 +01: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.
|
|
|
|
"""
|
2014-01-05 14:03:04 +01:00
|
|
|
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 {}
|
2014-01-05 14:03:04 +01:00
|
|
|
self.plugins = self.data['plugins'] if 'plugins' in self.data else {}
|
2013-06-26 20:14:37 +02:00
|
|
|
|
2014-01-05 14:03:04 +01: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.
|
|
|
|
|
2014-05-04 19:31:53 +02:00
|
|
|
: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
|
2014-02-23 20:14:23 +01:00
|
|
|
schema = load_json(schema_path)
|
2013-06-23 23:37:21 +02:00
|
|
|
try:
|
|
|
|
jsonschema.validate(data, schema)
|
|
|
|
except jsonschema.ValidationError as e:
|
2014-01-05 14:03:04 +01:00
|
|
|
self.validation_error(e.message, e.path)
|
2013-05-02 19:13:35 +02:00
|
|
|
|
2014-01-05 14:03:04 +01:00
|
|
|
def validation_error(self, message, json_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.
|
|
|
|
|
2014-05-04 19:31:53 +02:00
|
|
|
:param str message: Message to user about the error
|
|
|
|
:param list json_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
|
|
|
"""
|
2014-03-23 23:53:20 +01:00
|
|
|
from bootstrapvz.common.exceptions import ManifestError
|
2014-01-05 15:29:30 +01:00
|
|
|
raise ManifestError(message, self.path, json_path)
|