New Saltstack Salt formula
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

225 lines
6.4KB

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