Wed, 07 Feb 2018 20:39:01 +0000
Time snapshot fixes.
Python's default arguments are purely idiotic (aka. pythonic): generated
only once. This makes sense in a purely functional language, which Python
lightyears away from, but severely limits their usefulness in an imperative
language. Decorators also seem clumsy for this, as one would have to tell
the number of positional arguments for things to work nice, being able to
pass the snapshot both positionally and as keyword. No luck.
So have to do things the old-fashioned hard way.
#!/usr/local/bin/python3 # # Borgend by Tuomo Valkonen, 2018 # # This file implements basic configuration processing # import yaml import io import os import string import logging import platform from functools import reduce from . import branding from . import locations logger=logging.getLogger(__name__) # # Defaults # defaults={ # borg # Default: backup every 6 hours (21600 seconds) 'backup_interval': 21600, # Default: retry every 15 minutes if unable to connect / unfinished backup 'retry_interval': 900, # Default: do not prune (0) 'prune_interval': 0, # Extract passphrases at startup or on demand? 'extract_passphrases_at_startup': True, # Do not insert a quit menu entry (useful for installing on computers of # inexperienced users) 'no_quit_menu_entry': False, # Borg settings 'borg': { 'executable': 'borg', 'common_parameters': [], 'create_parameters': [], 'prune_parameters': [], } } # # Type checking etc. # def error(x): raise AssertionError(x) def check_field(cfg, field, descr, loc, default, check): if field in cfg: tmp=cfg[field] if not check(tmp): error("%s is of invalid type for %s" % (field, loc)) return tmp else: if default is not None: return default else: error("%s is not configured for %s" % (field, loc)) def check_bool(cfg, field, descr, loc, default=None): return check_field(cfg, field, descr, loc, default, lambda x: isinstance(x, bool)) def check_string(cfg, field, descr, loc, default=None): return check_field(cfg, field, descr, loc, default, lambda x: isinstance(x, str)) def check_dict(cfg, field, descr, loc, default=None): return check_field(cfg, field, descr, loc, default, lambda x: isinstance(x, dict)) def check_list(cfg, field, descr, loc, default=None): return check_field(cfg, field, descr, loc, default, lambda x: isinstance(x, list)) def is_list_of(x, chk): if x is None: return True elif isinstance(x, list): return reduce(lambda y, z: y and chk(z), x, True) else: return False def check_list_of_dicts(cfg, field, descr, loc, default=None): return check_field(cfg, field, descr, loc, default, lambda x: is_list_of(x, lambda z: isinstance(z, dict))) def check_list_of_strings(cfg, field, descr, loc, default=None): return check_field(cfg, field, descr, loc, default, lambda x: is_list_of(x, lambda z: isinstance(z, str))) def check_nonempty_list_of_strings(cfg, field, descr, loc): return check_list_of_strings(cfg, field, descr, loc) and cfg[field] def check_nonneg_int(cfg, field, descr, loc, default=None): return check_field(cfg, field, descr, loc, default, lambda x: isinstance(x, int) and x>=0) # # Borg command line parameter configuration helper routines and classes # class BorgParameters: def __init__(self, common, create, prune): self.common=common or [] self.create=create or [] self.prune=prune or [] def from_config(cfg, loc): common=check_list_of_dicts(cfg, 'common_parameters', 'Borg parameters', loc, default=[]) create=check_list_of_dicts(cfg, 'create_parameters', 'Create parameters', loc, default=[]) prune=check_list_of_dicts(cfg, 'prune_parameters', 'Prune parameters', loc, default=[]) return BorgParameters(common, create, prune) def __add__(self, other): common=self.common+other.common create=self.create+other.create prune=self.prune+other.prune return BorgParameters(common, create, prune) # # Load config on module load # def expand_env(cfg, env): if isinstance(cfg, dict): out={key: expand_env(val, env) for key, val in cfg.items()} elif isinstance(cfg, list): out=[expand_env(val, env) for val in cfg] elif isinstance(cfg, str): out=string.Template(cfg).substitute(os.environ) else: out=cfg return out if not (os.path.exists(locations.cfgfile) and os.path.isfile(locations.cfgfile)): raise SystemExit("Configuration file required: %s" % locations.cfgfile) logger.info("Reading configuration %s missing" % locations.cfgfile) with io.open(locations.cfgfile, 'r') as file: settings=expand_env(yaml.load(file), os.environ); # # Verify basic settings # def check_and_set(cfg, field, loc, defa, fn): cfg[field]=fn(cfg, field, field, loc, defa[field]) check_and_set(settings, 'backup_interval', 'top-level', defaults, check_nonneg_int) check_and_set(settings, 'retry_interval', 'top-level', defaults, check_nonneg_int) check_and_set(settings, 'extract_passphrases_at_startup', 'top-level', defaults, check_nonneg_int) check_and_set(settings, 'no_quit_menu_entry', 'top-level', defaults, check_bool) check_and_set(settings, 'borg', 'top-level', defaults, check_dict) # Check parameters within 'borg' if True: check_and_set(settings['borg'], 'executable', 'borg', defaults['borg'], check_string) borg_parameters=BorgParameters.from_config(settings['borg'], "top-level")