blob: 5e319aecb30e6060c227b96f08a081b765558e2a [file] [log] [blame]
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