validate.py 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import logging
  2. import sys
  3. import warnings
  4. import pkg_resources
  5. import pykwalify.core
  6. import pykwalify.errors
  7. from ruamel import yaml
  8. def schema_filename():
  9. '''
  10. Path to the installed YAML configuration schema file, used to validate and parse the
  11. configuration.
  12. '''
  13. return pkg_resources.resource_filename('borgmatic', 'config/schema.yaml')
  14. class Validation_error(ValueError):
  15. '''
  16. A collection of error message strings generated when attempting to validate a particular
  17. configurartion file.
  18. '''
  19. def __init__(self, config_filename, error_messages):
  20. self.config_filename = config_filename
  21. self.error_messages = error_messages
  22. def parse_configuration(config_filename, schema_filename):
  23. '''
  24. Given the path to a config filename in YAML format and the path to a schema filename in
  25. pykwalify YAML schema format, return the parsed configuration as a data structure of nested
  26. dicts and lists corresponding to the schema. Example return value:
  27. {'location': {'source_directories': ['/home', '/etc'], 'repository': 'hostname.borg'},
  28. 'retention': {'keep_daily': 7}, 'consistency': {'checks': ['repository', 'archives']}}
  29. Raise FileNotFoundError if the file does not exist, PermissionError if the user does not
  30. have permissions to read the file, or Validation_error if the config does not match the schema.
  31. '''
  32. logging.getLogger('pykwalify').setLevel(logging.ERROR)
  33. try:
  34. config = yaml.round_trip_load(open(config_filename))
  35. schema = yaml.round_trip_load(open(schema_filename))
  36. except yaml.error.YAMLError as error:
  37. raise Validation_error(config_filename, (str(error),))
  38. # pykwalify gets angry if the example field is not a string. So rather than bend to its will,
  39. # simply remove all examples before passing the schema to pykwalify.
  40. for section_name, section_schema in schema['map'].items():
  41. for field_name, field_schema in section_schema['map'].items():
  42. field_schema.pop('example')
  43. validator = pykwalify.core.Core(source_data=config, schema_data=schema)
  44. parsed_result = validator.validate(raise_exception=False)
  45. if validator.validation_errors:
  46. raise Validation_error(config_filename, validator.validation_errors)
  47. return parsed_result
  48. def display_validation_error(validation_error):
  49. '''
  50. Given a Validation_error, display its error messages to stderr.
  51. '''
  52. print(
  53. 'An error occurred while parsing a configuration file at {}:'.format(
  54. validation_error.config_filename
  55. ),
  56. file=sys.stderr,
  57. )
  58. for error in validation_error.error_messages:
  59. print(error, file=sys.stderr)