123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- 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 "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)
|