import contextlib import os import yaml import sys from glob import glob @contextlib.contextmanager def pushd(newDir): previousDir = os.getcwd() os.chdir(newDir) try: yield finally: os.chdir(previousDir) class DependencyException(Exception): pass class BackupException(Exception): pass def deptree(sources, deps=None): if deps is None: deps = [] deferred = [] for name in sources.keys(): source = sources[name] if "depends" in source and not source["depends"]: deferred.append(name) else: deps.append(name) while deferred: name = deferred.pop() depends = sources[name]["depends"] # todo - detect dependency loop if name in depends: raise DependencyException('Source {} depends upon itself'.format(name)) elif set(depends).issubset(set(deps)): deps.append(name) elif not set(depends).issubset(set(deps)): missing = ', '.join(set(depends).difference(set(deps))) raise DependencyException( 'Source {0} has missing dependencies: {1}'.format(name, missing)) else: deferred.append(name) return deps def status(name, value=None): if not hasattr(status, "_handle"): status._handle = {} if value is None: return status._handle[name] if name in status._handle else None status._handle[name] = value def backup(name): source = main.sources[name] failed = [x for x in source["depends"] if not status(x)] if failed: raise BackupException( "Unable to backup {0} due to ncomplete backups: {1}".format( name, failed)) # TODO - handle explicit failures with false # status(name, False) status(name, True) def main(args): with pushd('etc/backup.d'): with open("backup.yml") as f: main.config = yaml.load(f) sources = {} for source in main.config['sources']: source = os.path.realpath(source) for path in glob('{}/*.yml'.format(source)): path = os.path.realpath(path) with pushd(os.path.dirname(path)), open(path) as f: data = yaml.load(f) if "active" in data and data["active"]: if "depends" not in data: data["depends"] = [] for i in range(0, len(data["depends"])): data["depends"][i] = os.path.realpath( '{}.yml'.format(data["depends"][i])) sources[path] = data main.sources = sources main.deptree = deptree(sources) errors = [] for name in main.deptree: try: backup(name) except BackupException as ex: print(ex) errors.append(ex) if errors: raise BackupException("At least one backup failed") if __name__ == '__main__': try: main(sys.argv[1:]) except DependencyException as ex: print(ex) sys.exit(1) except BackupException as ex: print(ex) sys.exit(1) except Exception: from traceback import format_exc msg = "Error encountered:\n" + format_exc().strip() print(msg) sys.exit(1)