blob: cb45153ea579a302356e731ed4563c26fbd6cadc [file] [log] [blame]
Ales Komarek6041b1b2017-11-22 15:26:27 +01001
2from __future__ import absolute_import
3
4import glob
5import json
6import logging
7import os.path
8import yaml
9
10# Import third party libs
11try:
12 from jsonschema import validate as _validate
13 from jsonschema.validators import validator_for as _validator_for
14 from jsonschema.exceptions import SchemaError, ValidationError
15 HAS_JSONSCHEMA = True
16except ImportError:
17 HAS_JSONSCHEMA = False
18
19__virtualname__ = 'modelschema'
20
21LOG = logging.getLogger(__name__)
22
23
24def __virtual__():
25 """
26 Only load if jsonschema library exist.
27 """
28 if not HAS_JSONSCHEMA:
29 return (
30 False,
31 'Can not load module jsonschema: jsonschema library not found')
32 return __virtualname__
33
34
35def _get_base_dir():
36 return __salt__['config.get']('pilllar_schema_path',
37 '/usr/share/salt-formulas/env')
38
39
40def _dict_deep_merge(a, b, path=None):
41 """
42 Merges dict(b) into dict(a)
43 """
44 if path is None:
45 path = []
46 for key in b:
47 if key in a:
48 if isinstance(a[key], dict) and isinstance(b[key], dict):
49 _dict_deep_merge(a[key], b[key], path + [str(key)])
50 elif a[key] == b[key]:
51 pass # same leaf value
52 else:
53 raise Exception(
54 'Conflict at {}'.format('.'.join(path + [str(key)])))
55 else:
56 a[key] = b[key]
57 return a
58
59
60def schema_list():
61 """
62 Returns list of all defined schema files.
63
64 CLI Examples:
65
66 .. code-block:: bash
67
68 salt-call modelutils.schema_list
69
70
71 """
72 output = {}
azvyagintsev0d683712017-12-11 22:13:55 +020073 schemas = glob.glob('{}/*/schemas/*.yaml'.format(_get_base_dir()))
Ales Komarek6041b1b2017-11-22 15:26:27 +010074 for schema in schemas:
75 if os.path.exists(schema):
76 role_name = schema.split('/')[-1].replace('.yaml', '')
77 service_name = schema.split('/')[-3]
78 print role_name, service_name
79 name = '{}-{}'.format(service_name, role_name)
80 output[name] = {
81 'service': service_name,
82 'role': role_name,
83 'path': schema,
84 'valid': schema_validate(service_name, role_name)[name]
85 }
86 return output
87
88
89def schema_get(service, role):
90 """
91 Returns pillar schema for given service and role. If no service and role
92 is specified, method will return all known schemas.
93
94 CLI Examples:
95
96 .. code-block:: bash
97
98 salt-call modelutils.schema_get ntp server
99
100 """
101 schema_path = 'salt://{}/schemas/{}.yaml'.format(service, role)
102 schema = __salt__['cp.get_file_str'](schema_path)
103 if schema:
104 try:
105 data = yaml.safe_load(schema)
106 except yaml.YAMLError as exc:
107 raise Exception("Failed to parse schema:{}\n"
108 "{}".format(schema_path, exc))
109 else:
110 raise Exception("Schema not found:{}".format(schema_path))
111 return {'{}-{}'.format(service, role): data}
112
113
114def schema_validate(service, role):
115 """
116 Validates pillar schema itself of given service and role.
117
118 CLI Examples:
119
120 .. code-block:: bash
121
122 salt-call modelutils.schema_validate ntp server
123
124 """
125
126 schema = schema_get(service, role)['{}-{}'.format(service, role)]
127 cls = _validator_for(schema)
128 LOG.debug("Validating schema..")
129 try:
130 cls.check_schema(schema)
131 LOG.debug("Schema is valid")
132 data = 'Schema is valid'
133 except SchemaError as exc:
134 LOG.error("SchemaError:{}".format(exc))
135 data = repr(exc)
136 return {'{}-{}'.format(service, role): data}
137
138
139def model_validate(service=None, role=None):
140 """
141 Validates pillar metadata by schema for given service and role. If
142 no service and role is specified, method will validate all defined
143 services.
144
145 CLI Example:
146 .. code-block:: bash
147 salt-run modelschema.model_validate keystone server
148
149 """
150 schema = schema_get(service, role)['{}-{}'.format(service, role)]
151 model = __salt__['pillar.get']('{}:{}'.format(service, role))
152 try:
153 _validate(model, schema)
154 data = 'Model is valid'
155 except SchemaError as exc:
156 LOG.error("SchemaError:{}".format(exc))
157 data = repr(exc)
158 except ValidationError as exc:
159 LOG.error("ValidationError:{}\nInstance:{}\n"
160 "SchemaPath:{}".format(exc.message, exc.instance,
161 exc.schema_path))
162 raise Exception("ValidationError")
163 return {'{}-{}'.format(service, role): data}
164
165
166def data_validate(model, schema):
167 """
168 Validates model by given schema.
169
170 CLI Example:
171 .. code-block:: bash
172 salt-run modelschema.data_validate {'a': 'b'} {'a': 'b'}
173 """
174 try:
175 _validate(model, schema)
176 data = 'Model is valid'
177 except SchemaError as exc:
178 LOG.error("SchemaError:{}".format(exc))
179 data = str(exc)
180 except ValidationError as exc:
181 LOG.error("ValidationError:{}\nInstance:{}\n"
182 "SchemaPath:{}".format(exc.message, exc.instance,
183 exc.schema_path))
184 raise Exception("ValidationError")
185 return data
186
187
188def schema_from_tests(service):
189 """
190 Generate pillar schema skeleton for given service. Method iterates throught
191 test pillars and generates schema scaffold structure in JSON format that
192 can be passed to service like http://jsonschema.net/ to get the basic
193 schema for the individual roles of the service.
194
195 CLI Examples:
196
197 .. code-block:: bash
198
199 salt-call modelutils.schema_from_tests keystone
200 """
201 pillars = glob.glob(
202 '{}/{}/tests/pillar/*.sls'.format(_get_base_dir(), service))
203 raw_data = {}
204 for pillar in pillars:
205 if os.path.exists(pillar):
206 with open(pillar, 'r') as stream:
207 try:
208 data = yaml.load(stream)
209 except yaml.YAMLError as exc:
210 data = {}
211 LOG.error('{}: {}'.format(pillar, repr(exc)))
212 try:
213 _dict_deep_merge(raw_data, data)
214 except Exception as exc:
215 LOG.error('{}: {}'.format(pillar, repr(exc)))
216 if service not in raw_data.keys():
217 raise Exception(
218 "Could not find applicable data "
219 "for:{}\n at:{}".format(service, _get_base_dir()))
220 data = raw_data[service]
221 output = {}
222 for role_name, role in data.items():
223 output[role_name] = json.dumps(role)
224 return output