|
-
- from __future__ import absolute_import
-
- import glob
- import json
- import logging
- import os.path
- import yaml
-
- # Import third party libs
- try:
- from jsonschema import validate as _validate
- from jsonschema.validators import validator_for as _validator_for
- from jsonschema.exceptions import SchemaError, ValidationError
- HAS_JSONSCHEMA = True
- except ImportError:
- HAS_JSONSCHEMA = False
-
- __virtualname__ = 'modelschema'
-
- LOG = logging.getLogger(__name__)
-
-
- def __virtual__():
- """
- Only load if jsonschema library exist.
- """
- if not HAS_JSONSCHEMA:
- return (
- False,
- 'Can not load module jsonschema: jsonschema library not found')
- return __virtualname__
-
-
- def _get_base_dir():
- return __salt__['config.get']('pilllar_schema_path',
- '/usr/share/salt-formulas/env')
-
-
- def _dict_deep_merge(a, b, path=None):
- """
- Merges dict(b) into dict(a)
- """
- if path is None:
- path = []
- for key in b:
- if key in a:
- if isinstance(a[key], dict) and isinstance(b[key], dict):
- _dict_deep_merge(a[key], b[key], path + [str(key)])
- elif a[key] == b[key]:
- pass # same leaf value
- else:
- raise Exception(
- 'Conflict at {}'.format('.'.join(path + [str(key)])))
- else:
- a[key] = b[key]
- return a
-
-
- def schema_list():
- """
- Returns list of all defined schema files.
-
- CLI Examples:
-
- .. code-block:: bash
-
- salt-call modelutils.schema_list
-
-
- """
- output = {}
- schemas = glob.glob('{}/*/schemas/*.yaml'.format(_get_base_dir()))
- for schema in schemas:
- if os.path.exists(schema):
- role_name = schema.split('/')[-1].replace('.yaml', '')
- service_name = schema.split('/')[-3]
- print role_name, service_name
- name = '{}-{}'.format(service_name, role_name)
- output[name] = {
- 'service': service_name,
- 'role': role_name,
- 'path': schema,
- 'valid': schema_validate(service_name, role_name)[name]
- }
- return output
-
-
- def schema_get(service, role):
- """
- Returns pillar schema for given service and role. If no service and role
- is specified, method will return all known schemas.
-
- CLI Examples:
-
- .. code-block:: bash
-
- salt-call modelutils.schema_get ntp server
-
- """
- schema_path = 'salt://{}/schemas/{}.yaml'.format(service, role)
- schema = __salt__['cp.get_file_str'](schema_path)
- if schema:
- try:
- data = yaml.safe_load(schema)
- except yaml.YAMLError as exc:
- raise Exception("Failed to parse schema:{}\n"
- "{}".format(schema_path, exc))
- else:
- raise Exception("Schema not found:{}".format(schema_path))
- return {'{}-{}'.format(service, role): data}
-
-
- def schema_validate(service, role):
- """
- Validates pillar schema itself of given service and role.
-
- CLI Examples:
-
- .. code-block:: bash
-
- salt-call modelutils.schema_validate ntp server
-
- """
-
- schema = schema_get(service, role)['{}-{}'.format(service, role)]
- cls = _validator_for(schema)
- LOG.debug("Validating schema..")
- try:
- cls.check_schema(schema)
- LOG.debug("Schema is valid")
- data = 'Schema is valid'
- except SchemaError as exc:
- LOG.error("SchemaError:{}".format(exc))
- raise Exception("SchemaError")
- return {'{}-{}'.format(service, role): data}
-
-
- def model_validate(service=None, role=None):
- """
- Validates pillar metadata by schema for given service and role. If
- no service and role is specified, method will validate all defined
- services.
-
- CLI Example:
- .. code-block:: bash
- salt-run modelschema.model_validate keystone server
-
- """
- schema = schema_get(service, role)['{}-{}'.format(service, role)]
- model = __salt__['pillar.get']('{}:{}'.format(service, role))
- try:
- _validate(model, schema)
- data = 'Model is valid'
- except SchemaError as exc:
- LOG.error("SchemaError:{}".format(exc))
- raise Exception("SchemaError")
- except ValidationError as exc:
- LOG.error("ValidationError:{}\nInstance:{}\n"
- "Schema title:{}\n"
- "SchemaPath:{}".format(exc.message,
- exc.instance,
- exc.schema.get(
- "title",
- "Schema title not set!"),
- exc.schema_path))
- raise Exception("ValidationError")
- return {'{}-{}'.format(service, role): data}
-
-
- def data_validate(model, schema):
- """
- Validates model by given schema.
-
- CLI Example:
- .. code-block:: bash
- salt-run modelschema.data_validate {'a': 'b'} {'a': 'b'}
- """
- try:
- _validate(model, schema)
- data = 'Model is valid'
- except SchemaError as exc:
- LOG.error("SchemaError:{}".format(exc))
- raise Exception("SchemaError")
- except ValidationError as exc:
- LOG.error("ValidationError:{}\nInstance:{}\n"
- "Schema title:{}\n"
- "SchemaPath:{}".format(exc.message,
- exc.instance,
- exc.schema.get(
- "title",
- "Schema title not set!"),
- exc.schema_path))
- raise Exception("ValidationError")
- return data
-
-
- def schema_from_tests(service):
- """
- Generate pillar schema skeleton for given service. Method iterates throught
- test pillars and generates schema scaffold structure in JSON format that
- can be passed to service like http://jsonschema.net/ to get the basic
- schema for the individual roles of the service.
-
- CLI Examples:
-
- .. code-block:: bash
-
- salt-call modelutils.schema_from_tests keystone
- """
- pillars = glob.glob(
- '{}/{}/tests/pillar/*.sls'.format(_get_base_dir(), service))
- raw_data = {}
- for pillar in pillars:
- if os.path.exists(pillar):
- with open(pillar, 'r') as stream:
- try:
- data = yaml.load(stream)
- except yaml.YAMLError as exc:
- data = {}
- LOG.error('{}: {}'.format(pillar, repr(exc)))
- try:
- _dict_deep_merge(raw_data, data)
- except Exception as exc:
- LOG.error('{}: {}'.format(pillar, repr(exc)))
- if service not in raw_data.keys():
- LOG.error("Could not find applicable data "
- "for:{}\n at:{}".format(service, _get_base_dir()))
- raise Exception("DataError")
-
- data = raw_data[service]
- output = {}
- for role_name, role in data.items():
- output[role_name] = json.dumps(role)
- return output
|