|
|
@ -0,0 +1,84 @@ |
|
|
|
import logging |
|
|
|
import sys |
|
|
|
import warnings |
|
|
|
|
|
|
|
import pkg_resources |
|
|
|
import pykwalify.core |
|
|
|
import pykwalify.errors |
|
|
|
import ruamel.yaml.error |
|
|
|
|
|
|
|
|
|
|
|
def schema_filename(): |
|
|
|
''' |
|
|
|
Path to the installed YAML configuration schema file, used to validate and parse the |
|
|
|
configuration. |
|
|
|
''' |
|
|
|
return pkg_resources.resource_filename('borgmatic', 'config/schema.yaml') |
|
|
|
|
|
|
|
|
|
|
|
class Validation_error(ValueError): |
|
|
|
''' |
|
|
|
A collection of error message strings generated when attempting to validate a particular |
|
|
|
configurartion file. |
|
|
|
''' |
|
|
|
def __init__(self, config_filename, error_messages): |
|
|
|
self.config_filename = config_filename |
|
|
|
self.error_messages = error_messages |
|
|
|
|
|
|
|
|
|
|
|
def parse_configuration(config_filename, schema_filename): |
|
|
|
''' |
|
|
|
Given the path to a config filename in YAML format and the path to a schema filename in |
|
|
|
pykwalify YAML schema format, return the parsed configuration as a data structure of nested |
|
|
|
dicts and lists corresponding to the schema. Example return value: |
|
|
|
|
|
|
|
{'location': {'source_directories': ['/home', '/etc'], 'repository': 'hostname.borg'}, |
|
|
|
'retention': {'keep_daily': 7}, 'consistency': {'checks': ['repository', 'archives']}} |
|
|
|
|
|
|
|
Raise FileNotFoundError if the file does not exist, PermissionError if the user does not |
|
|
|
have permissions to read the file, or Validation_error if the config does not match the schema. |
|
|
|
''' |
|
|
|
warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning) |
|
|
|
logging.getLogger('pykwalify').setLevel(logging.CRITICAL) |
|
|
|
|
|
|
|
try: |
|
|
|
validator = pykwalify.core.Core(source_file=config_filename, schema_files=[schema_filename]) |
|
|
|
except pykwalify.errors.CoreError as error: |
|
|
|
if 'do not exists on disk' in str(error): |
|
|
|
raise FileNotFoundError("No such file or directory: '{}'".format(config_filename)) |
|
|
|
if 'Unable to load any data' in str(error): |
|
|
|
# If the YAML file has a syntax error, pykwalify's exception is particularly unhelpful. |
|
|
|
# So reach back to the originating exception from ruamel.yaml for something more useful. |
|
|
|
raise Validation_error(config_filename, (error.__context__,)) |
|
|
|
raise |
|
|
|
|
|
|
|
parsed_result = validator.validate(raise_exception=False) |
|
|
|
|
|
|
|
if validator.validation_errors: |
|
|
|
raise Validation_error(config_filename, validator.validation_errors) |
|
|
|
|
|
|
|
return parsed_result |
|
|
|
|
|
|
|
|
|
|
|
def display_validation_error(validation_error): |
|
|
|
''' |
|
|
|
Given a Validation_error, display its error messages to stderr. |
|
|
|
''' |
|
|
|
print( |
|
|
|
'An error occurred while parsing a configuration file at {}:'.format( |
|
|
|
validation_error.config_filename |
|
|
|
), |
|
|
|
file=sys.stderr, |
|
|
|
) |
|
|
|
|
|
|
|
for error in validation_error.error_messages: |
|
|
|
print(error, file=sys.stderr) |
|
|
|
|
|
|
|
|
|
|
|
# FOR TESTING |
|
|
|
if __name__ == '__main__': |
|
|
|
try: |
|
|
|
configuration = parse_configuration('sample/config.yaml', schema_filename()) |
|
|
|
print(configuration) |
|
|
|
except Validation_error as error: |
|
|
|
display_validation_error(error) |