def main(): try: # read config file read_config() # read command line cmdline = docopt( __doc__.format(commands=Command.summarize()), version='avendesora {} ({})'.format(__version__, __released__), options_first=True, ) # start logging logfile = BufferedFile(get_setting('log_file'), True) Inform(logfile=logfile, hanging_indent=False, stream_policy='header', notify_if_no_tty=True) shlib.set_prefs(use_inform=True, log_cmd=True) # run the requested command Command.execute(cmdline['<command>'], cmdline['<args>']) done() except KeyboardInterrupt: output('\nTerminated by user.') terminate() except (PasswordError, Error) as e: e.terminate() except OSError as e: fatal(os_error(e)) done()
def main(): with Inform(error_status=2, flush=True, version=version) as inform: # read command line cmdline = docopt(expanded_synopsis, options_first=True, version=version) config = cmdline["--config"] command = cmdline["<command>"] args = cmdline["<args>"] if cmdline["--mute"]: inform.mute = True if cmdline["--quiet"]: inform.quiet = True emborg_opts = cull( [ "verbose" if cmdline["--verbose"] else "", "narrate" if cmdline["--narrate"] else "", "dry-run" if cmdline["--dry-run"] else "", "no-log" if cmdline["--no-log"] else "", ] ) if cmdline["--narrate"]: inform.narrate = True try: # find the command cmd, cmd_name = Command.find(command) # execute the command initialization exit_status = cmd.execute_early(cmd_name, args, None, emborg_opts) if exit_status is not None: terminate(exit_status) worst_exit_status = 0 try: while True: with Settings(config, cmd, emborg_opts) as settings: try: exit_status = cmd.execute( cmd_name, args, settings, emborg_opts ) except Error as e: settings.fail(e, cmd=' '.join(sys.argv)) e.terminate() if exit_status and exit_status > worst_exit_status: worst_exit_status = exit_status except NoMoreConfigs: pass # execute the command termination exit_status = cmd.execute_late(cmd_name, args, None, emborg_opts) if exit_status and exit_status > worst_exit_status: worst_exit_status = exit_status except KeyboardInterrupt: display("Terminated by user.") except Error as e: e.terminate() except OSError as e: fatal(os_error(e)) terminate(worst_exit_status)
def main(): with Inform() as inform: # read command line cmdline = docopt( __doc__.format(commands=Command.summarize()), options_first=True ) config = cmdline['--config'] command = cmdline['<command>'] args = cmdline['<args>'] options = cull([ 'verbose' if cmdline['--verbose'] else '', 'narrate' if cmdline['--narrate'] else '', 'trial-run' if cmdline['--trial-run'] else '', ]) if cmdline['--narrate']: inform.narrate = True try: cmd, name = Command.find(command) with Settings(config, cmd.REQUIRES_EXCLUSIVITY) as settings: cmd.execute(name, args, settings, options) except KeyboardInterrupt: display('Terminated by user.') except Error as err: err.terminate() except OSError as err: fatal(os_error(err)) terminate()
def test_anglicize(capsys): Inform(colorscheme=None, prog_name=False) ppp() out, err = capsys.readouterr() assert out == dedent(''' DEBUG: test_debug.py, 17, tests.test_debug.test_anglicize() ''').lstrip()
def main(): with Inform(notify_if_no_tty=True, version=version) as inform: try: # assure config and log directories exist to_path(CONFIG_DIR).mkdir(parents=True, exist_ok=True) to_path(DATA_DIR).mkdir(parents=True, exist_ok=True) inform.set_logfile(to_path(DATA_DIR, LOG_FILE)) # read command line cmdline = docopt(synopsis, options_first=True, version=version) command = cmdline["<command>"] args = cmdline["<args>"] if cmdline["--quiet"]: inform.quiet = True # find and run command settings = Settings(cmdline) cmd, cmd_name = Command.find(command) cmd.execute(cmd_name, args, settings, cmdline) except KeyboardInterrupt: display("Terminated by user.") except Error as e: e.terminate() except OSError as e: fatal(os_error(e)) done()
def test_bartender(capsys): Inform(colorscheme=None, prog_name=False) b = 'b' ret = aaa(b) out, err = capsys.readouterr() assert out == dedent(''' DEBUG: test_debug.py, 155, tests.test_debug.test_bartender(): 'b' ''').lstrip() assert ret == 'b'
def test_grouch(capsys): Inform(colorscheme=None, prog_name=False) a = 0 b = 'b' ppp('hey now!', a, b) out, err = capsys.readouterr() assert out == dedent(''' DEBUG: test_debug.py, 27, tests.test_debug.test_grouch(): hey now! 0 b ''').lstrip()
def test_prude(capsys): Inform(colorscheme=None, prog_name=False) Info(email='*****@*****.**') out, err = capsys.readouterr() assert out == dedent(''' DEBUG: test_debug.py, 76, tests.test_debug.Info.__init__(): email = '*****@*****.**' self = Info object containing {'email': '*****@*****.**'} ''').lstrip()
def main(): with Inform(error_status=2, flush=True, version=version) as inform: # read command line cmdline = docopt(expanded_synopsis, options_first=True, version=version) config = cmdline['--config'] command = cmdline['<command>'] args = cmdline['<args>'] if cmdline['--mute']: inform.mute = True if cmdline['--quiet']: inform.quiet = True options = cull([ 'verbose' if cmdline['--verbose'] else '', 'narrate' if cmdline['--narrate'] else '', 'trial-run' if cmdline['--trial-run'] else '', 'no-log' if cmdline['--no-log'] else '', ]) if cmdline['--narrate']: inform.narrate = True try: # find the command cmd, cmd_name = Command.find(command) # execute the command initialization exit_status = cmd.execute_early(cmd_name, args, None, options) if exit_status is not None: terminate(exit_status) worst_exit_status = 0 try: while True: with Settings(config, cmd, options) as settings: try: exit_status = cmd.execute(cmd_name, args, settings, options) except Error as e: settings.fail(e) e.terminate() if exit_status and exit_status > worst_exit_status: worst_exit_status = exit_status except NoMoreConfigs: pass # execute the command termination exit_status = cmd.execute_late(cmd_name, args, None, options) if exit_status and exit_status > worst_exit_status: worst_exit_status = exit_status except KeyboardInterrupt: display('Terminated by user.') except Error as e: e.terminate() except OSError as e: fatal(os_error(e)) terminate(worst_exit_status)
def test_prostrate(capsys): Inform(colorscheme=None, prog_name=False) sss() out, err = capsys.readouterr() out = out.strip().split('\n') assert out[ 0] == 'DEBUG: test_debug.py, 129, tests.test_debug.test_prostrate():' assert out[-2][ -57:] == "inform/tests/test_debug.py', line 124, in test_prostrate," assert out[-1] == ' sss()'
def messenger(*args, **kwargs): stdout = StringIO() stderr = StringIO() logfile = StringIO() with Inform( *args, stdout=stdout, stderr=stderr, prog_name=False, logfile=logfile, **kwargs ) as msg: yield msg, stdout, stderr, logfile stdout.close() stderr.close() logfile.close()
def test_run_ground(): Inform(prog_name=False, logfile=False) set_prefs(use_inform=True) cmd = './test_prog 1' try: p = Run(cmd, 'sOEW') assert False, 'expected exception' except Error as e: assert str(e) == 'this is stderr.' assert e.cmd == './test_prog 1' assert e.stdout == 'this is stdout.' assert e.stderr == 'this is stderr.' assert e.status == 1 assert e.msg == 'this is stderr.' set_prefs(use_inform=False)
def test_rubber(capsys): Inform(colorscheme=None, prog_name=False) a = aaa('a') out, err = capsys.readouterr() assert out == dedent(''' DEBUG: test_debug.py, 138, tests.test_debug.test_rubber(): 'a' ''').lstrip() assert a == 'a' b = aaa(b='b') out, err = capsys.readouterr() assert out == dedent(''' DEBUG: test_debug.py, 145, tests.test_debug.test_rubber(): b: 'b' ''').lstrip() assert b == 'b'
def test_run_ground(): with cd(wd): Inform(prog_name=False, logfile=False) set_prefs(use_inform=True) cmd = "./test_prog 1" try: Run(cmd, "sOEW") assert False, "expected exception" except Error as e: assert str(e) == "this is stderr." assert e.cmd == "./test_prog 1" assert e.stdout == "this is stdout." assert e.stderr == "this is stderr." assert e.status == 1 assert e.msg == "this is stderr." set_prefs(use_inform=False)
def test_shear(capsys): Inform(colorscheme=None, prog_name=False) if sys.version_info >= (3, 6): a = 0 b = 'b' c = [a, b] d = {a, b} e = {a: b} vvv(a, b, c, d, e) out, err = capsys.readouterr() assert out == dedent(''' DEBUG: test_debug.py, 116, tests.test_debug.test_shear(): a = 0 b = 'b' c = [0, 'b'] d = {0, 'b'} e = {0: 'b'} ''').lstrip() def test_prostrate(capsys): Inform(colorscheme=None, prog_name=False) sss() out, err = capsys.readouterr() out = out.strip().split('\n') assert out[ 0] == 'DEBUG: test_debug.py, 129, tests.test_debug.test_prostrate():' assert out[-2][ -57:] == "inform/tests/test_debug.py', line 124, in test_prostrate," assert out[-1] == ' sss()' def test_rubber(capsys): Inform(colorscheme=None, prog_name=False) a = aaa('a') out, err = capsys.readouterr() assert out == dedent(''' DEBUG: test_debug.py, 138, tests.test_debug.test_rubber(): 'a' ''').lstrip() assert a == 'a' b = aaa(b='b') out, err = capsys.readouterr() assert out == dedent(''' DEBUG: test_debug.py, 145, tests.test_debug.test_rubber(): b: 'b' ''').lstrip() assert b == 'b'
def test_composite(capsys): with Inform(prog_name=False): pw = PasswordGenerator() account = pw.get_account('mybank') accounts = account.get_composite('accounts') assert accounts == dict(checking='12345678', savings='23456789', creditcard='34567890') questions = account.get_composite('questions') assert questions == [ 'scallywag bedbug groupie', 'assay centrist fanatic', 'shunt remnant burrow' ] pin = account.get_composite('pin') assert pin == '9982' name = account.get_composite('NAME') assert name == 'mybank' nada = account.get_composite('nada') assert nada == None
def test_update(capsys): Inform(colorscheme=None, prog_name=False) if sys.version_info >= (3, 6): a = 0 b = 'b' c = [a, b] d = {a, b} e = {a: b} vvv() out, err = capsys.readouterr() out = '\n'.join(l for l in out.split('\n') if 'capsys' not in l) assert out == dedent(''' DEBUG: test_debug.py, 96, tests.test_debug.test_update(): a = 0 b = 'b' c = [0, 'b'] d = {0, 'b'} e = {0: 'b'} ''').lstrip()
def test_daiquiri(capsys): Inform(colorscheme=None, prog_name=False) if sys.version_info >= (3, 6): a = 0 b = 'b' c = [a, b] d = {a, b} e = {a: b} ddd(s='hey now!', a=a, b=b, c=c, d=d, e=e) out, err = capsys.readouterr() assert out == dedent(''' DEBUG: test_debug.py, 61, tests.test_debug.test_daiquiri(): a = 0 b = 'b' c = [0, 'b'] d = {0, 'b'} e = {0: 'b'} s = 'hey now!' ''').lstrip()
def test_salver(capsys): Inform(colorscheme=None, prog_name=False) if sys.version_info >= (3, 6): a = 0 b = 'b' c = [a, b] d = {a, b} e = {a: b} ddd('hey now!', a, b, c, d, e) out, err = capsys.readouterr() assert out == dedent(''' DEBUG: test_debug.py, 41, tests.test_debug.test_salver(): 'hey now!' 0 'b' [0, 'b'] {0, 'b'} {0: 'b'} ''').lstrip()
def test_archive(capsys): from avendesora import Hidden, OTP, Question, RecognizeTitle, Script from inform import render with Inform(prog_name=False): pw = PasswordGenerator() account = pw.get_account('mybank') archive = account.archive() expected = dict( accounts=dict(checking=Hidden('MTIzNDU2Nzg='), creditcard=Hidden('MzQ1Njc4OTA='), savings=Hidden('MjM0NTY3ODk=')), aliases='mb'.split(), birthdate=Hidden('MTk4MS0xMC0wMQ==', is_secret=False), checking=Script('{accounts.checking}'), comment=dedent(''' This is a multiline comment. It spans more than one line. '''), customer_service='1-866-229-6633', discovery=RecognizeTitle('easy peasy', script='lemon squeezy'), email='*****@*****.**', passcode=Hidden('b1UkJHcwVU1YZTc0'), pin=Hidden('OTk4Mg=='), questions=[ Question('What city were you born in?', answer=Hidden('c2NhbGx5d2FnIGJlZGJ1ZyBncm91cGll')), Question('What street did you grow up on?', answer=Hidden('YXNzYXkgY2VudHJpc3QgZmFuYXRpYw==')), Question('What was your childhood nickname?', answer=Hidden('c2h1bnQgcmVtbmFudCBidXJyb3c=')) ], urls='https://mb.com', username='******', verbal=Hidden('Zml6emxlIGxlb3BhcmQ=')) #with open('expected', 'w') as f: # f.write(render(expected, sort=True)) #with open('result', 'w') as f: # f.write(render(archive, sort=True)) assert render(archive, sort=True) == render(expected, sort=True)
def test_summary(capsys): try: with Inform(prog_name=False): pw = PasswordGenerator() account = pw.get_account('mybank') stdout, stderr = capsys.readouterr() assert stdout == '' assert stderr == '' account.write_summary(sort=True) stdout, stderr = capsys.readouterr() assert stderr == '' assert stdout == dedent(''' names: mybank, mb accounts: checking: reveal with: avendesora value mybank accounts.checking savings: reveal with: avendesora value mybank accounts.savings creditcard: reveal with: avendesora value mybank accounts.creditcard birthdate: 1981-10-01 checking: {accounts.checking} comment: This is a multiline comment. It spans more than one line. customer service: 1-866-229-6633 email: [email protected] passcode: reveal with: avendesora value mybank passcode pin: reveal with: avendesora value mybank pin questions: 0: What city were you born in?, reveal with: avendesora value mybank questions.0 1: What street did you grow up on?, reveal with: avendesora value mybank questions.1 2: What was your childhood nickname?, reveal with: avendesora value mybank questions.2 urls: https://mb.com username: pizzaman verbal: reveal with: avendesora value mybank verbal ''').lstrip() except PasswordError as err: assert str(err) == None
def main(): with Inform(error_status=2, flush=True, version=version) as inform: # read command line cmdline = docopt(expanded_synopsis, options_first=True, version=version) config = cmdline['--config'] command = cmdline['<command>'] args = cmdline['<args>'] if cmdline['--mute']: inform.mute = True options = cull([ 'verbose' if cmdline['--verbose'] else '', 'narrate' if cmdline['--narrate'] else '', 'trial-run' if cmdline['--trial-run'] else '', 'no-log' if cmdline['--no-log'] else '', ]) if cmdline['--narrate']: inform.narrate = True try: cmd, cmd_name = Command.find(command) with Settings(config, cmd.REQUIRES_EXCLUSIVITY, options) as settings: try: exit_status = cmd.execute(cmd_name, args, settings, options) except Error as e: settings.fail(e) e.terminate(True) except KeyboardInterrupt: display('Terminated by user.') exit_status = 0 except Error as e: e.terminate() except OSError as e: fatal(os_error(e)) terminate(exit_status)
def main(): version = f'{__version__} ({__released__})' cmdline = docopt(__doc__, version=version) quiet = cmdline['--quiet'] problem = False with Inform(flush=True, quiet=quiet, version=version) as inform: # read the settings file settings_file = PythonFile(CONFIG_DIR, OVERDUE_FILE) settings_filename = settings_file.path settings = settings_file.run() # gather needed settings default_maintainer = settings.get('default_maintainer') default_max_age = settings.get('default_max_age', 28) dumper = settings.get('dumper', f'{getusername()}@{gethostname()}') repositories = settings.get('repositories') root = settings.get('root') # process repositories table backups = [] if is_str(repositories): for line in repositories.split('\n'): line = line.split('#')[0].strip() # discard comments if not line: continue backups.append([c.strip() for c in line.split('|')]) else: for each in repositories: backups.append([ each.get('host'), each.get('path'), each.get('maintainer'), each.get('max_age') ]) def send_mail(recipient, subject, message): if cmdline['--mail']: display(f'Reporting to {recipient}.\n') mail_cmd = ['mailx', '-r', dumper, '-s', subject, recipient] Run(mail_cmd, stdin=message, modes='soeW0') # check age of repositories now = arrow.now() display(f'current time = {now}') for host, path, maintainer, max_age in backups: maintainer = default_maintainer if not maintainer else maintainer max_age = int(max_age) if max_age else default_max_age try: path = to_path(root, path) if not path.is_dir(): raise Error('does not exist or is not a directory.', culprit=path) paths = list(path.glob('index.*')) if not paths: raise Error('no sentinel file found.', culprit=path) if len(paths) > 1: raise Error('too many sentinel files.', *paths, sep='\n ') path = paths[0] mtime = arrow.get(path.stat().st_mtime) delta = now - mtime age = 24 * delta.days + delta.seconds / 3600 report = age > max_age display( dedent(f""" HOST: {host} sentinel file: {path!s} last modified: {mtime} since last change: {age:0.1f} hours maximum age: {max_age} hours overdue: {report} """)) if report: problem = True subject = f"backup of {host} is overdue" msg = overdue_message.format(host=host, path=path, age=age) send_mail(maintainer, subject, msg) except OSError as e: problem = True msg = os_error(e) error(msg) if maintaner: send_mail(maintainer, f'{get_prog_name()} error', error_message.format(msg)) except Error as e: problem = True e.report() if maintaner: send_mail(maintainer, f'{get_prog_name()} error', error_message.format(str(e))) terminate(problem)
#!/usr/bin/env python3 """ Wetlab protocols that follow the Unix philosophy. """ __version__ = '0.27.0' from .protocol import * from .reaction import * from .quantity import * from .format import * from .library import * from .printer import * from .config import * from .errors import * from pathlib import Path from inform import Inform Inform(stream_policy='header') class Builtins: protocol_dir = Path(__file__).parent / 'builtins' priority = 0
def main(): version = f"{__version__} ({__released__})" cmdline = docopt(__doc__, version=version) quiet = cmdline["--quiet"] problem = False use_color = Color.isTTY() and not cmdline["--no-color"] passes = Color("green", enable=use_color) fails = Color("red", enable=use_color) if cmdline["--verbose"]: overdue_message = verbose_overdue_message else: overdue_message = terse_overdue_message # prepare to create logfile log = to_path(DATA_DIR, OVERDUE_LOG_FILE) if OVERDUE_LOG_FILE else False if log: data_dir = to_path(DATA_DIR) if not data_dir.exists(): try: # data dir does not exist, create it data_dir.mkdir(mode=0o700, parents=True, exist_ok=True) except OSError as e: warn(os_error(e)) log = False with Inform(flush=True, quiet=quiet, logfile=log, version=version): # read the settings file try: settings_file = PythonFile(CONFIG_DIR, OVERDUE_FILE) settings = settings_file.run() except Error as e: e.terminate() # gather needed settings default_maintainer = settings.get("default_maintainer") default_max_age = settings.get("default_max_age", 28) dumper = settings.get("dumper", f"{username}@{hostname}") repositories = settings.get("repositories") root = settings.get("root") # process repositories table backups = [] if is_str(repositories): for line in repositories.split("\n"): line = line.split("#")[0].strip() # discard comments if not line: continue backups.append([c.strip() for c in line.split("|")]) else: for each in repositories: backups.append([ each.get("host"), each.get("path"), each.get("maintainer"), each.get("max_age"), ]) def send_mail(recipient, subject, message): if cmdline["--mail"]: if cmdline['--verbose']: display(f"Reporting to {recipient}.\n") mail_cmd = ["mailx", "-r", dumper, "-s", subject, recipient] Run(mail_cmd, stdin=message, modes="soeW0") # check age of repositories for host, path, maintainer, max_age in backups: maintainer = default_maintainer if not maintainer else maintainer max_age = float(max_age) if max_age else default_max_age try: path = to_path(root, path) if path.is_dir(): paths = list(path.glob("index.*")) if not paths: raise Error("no sentinel file found.", culprit=path) if len(paths) > 1: raise Error("too many sentinel files.", *paths, sep="\n ") path = paths[0] mtime = arrow.get(path.stat().st_mtime) delta = now - mtime age = 24 * delta.days + delta.seconds / 3600 report = age > max_age overdue = ' -- overdue' if report else '' color = fails if report else passes if report or not cmdline["--no-passes"]: display(color(fmt(overdue_message))) if report: problem = True subject = f"backup of {host} is overdue" msg = fmt(mail_overdue_message) send_mail(maintainer, subject, msg) except OSError as e: problem = True msg = os_error(e) error(msg) if maintainer: send_mail( maintainer, f"{get_prog_name()} error", error_message.format(msg), ) except Error as e: problem = True e.report() if maintainer: send_mail( maintainer, f"{get_prog_name()} error", error_message.format(str(e)), ) terminate(problem)
def main(): with Inform( error_status=2, flush=True, logfile=LoggingCache(), prog_name='emborg', version=version, ) as inform: # read command line cmdline = docopt(expanded_synopsis, options_first=True, version=version) config = cmdline["--config"] command = cmdline["<command>"] args = cmdline["<args>"] if cmdline["--mute"]: inform.mute = True if cmdline["--quiet"]: inform.quiet = True if cmdline["--relocated"]: os.environ['BORG_RELOCATED_REPO_ACCESS_IS_OK'] = 'YES' emborg_opts = cull([ "verbose" if cmdline["--verbose"] else "", "narrate" if cmdline["--narrate"] else "", "dry-run" if cmdline["--dry-run"] else "", "no-log" if cmdline["--no-log"] else "", ]) if cmdline["--narrate"]: inform.narrate = True Hooks.provision_hooks() worst_exit_status = 0 try: # find the command cmd, cmd_name = Command.find(command) # execute the command initialization exit_status = cmd.execute_early(cmd_name, args, None, emborg_opts) if exit_status is not None: terminate(exit_status) queue = ConfigQueue(cmd) while queue: with Settings(config, emborg_opts, queue) as settings: try: exit_status = cmd.execute(cmd_name, args, settings, emborg_opts) except Error as e: exit_status = 2 settings.fail(e, cmd=' '.join(sys.argv)) e.terminate() if exit_status and exit_status > worst_exit_status: worst_exit_status = exit_status # execute the command termination exit_status = cmd.execute_late(cmd_name, args, None, emborg_opts) if exit_status and exit_status > worst_exit_status: worst_exit_status = exit_status except KeyboardInterrupt: display("Terminated by user.") except Error as e: e.report() exit_status = 2 except OSError as e: exit_status = 2 error(os_error(e)) if exit_status and exit_status > worst_exit_status: worst_exit_status = exit_status terminate(worst_exit_status)
def main(): version = f'{__version__} ({__released__})' cmdline = docopt(__doc__, version=version) quiet = cmdline['--quiet'] problem = False use_color = Color.isTTY() and not cmdline['--no-color'] passes = Color('green', enable=use_color) fails = Color('red', enable=use_color) # prepare to create logfile log = to_path(DATA_DIR, OVERDUE_LOG_FILE) if OVERDUE_LOG_FILE else False if log: data_dir = to_path(DATA_DIR) if not data_dir.exists(): try: # data dir does not exist, create it data_dir.mkdir(mode=0o700, parents=True, exist_ok=True) except OSError as e: warn(os_error(e)) log = False with Inform(flush=True, quiet=quiet, logfile=log, version=version): # read the settings file try: settings_file = PythonFile(CONFIG_DIR, OVERDUE_FILE) settings = settings_file.run() except Error as e: e.terminate() # gather needed settings default_maintainer = settings.get('default_maintainer') default_max_age = settings.get('default_max_age', 28) dumper = settings.get('dumper', f'{username}@{hostname}') repositories = settings.get('repositories') root = settings.get('root') # process repositories table backups = [] if is_str(repositories): for line in repositories.split('\n'): line = line.split('#')[0].strip() # discard comments if not line: continue backups.append([c.strip() for c in line.split('|')]) else: for each in repositories: backups.append([ each.get('host'), each.get('path'), each.get('maintainer'), each.get('max_age') ]) def send_mail(recipient, subject, message): if cmdline['--mail']: display(f'Reporting to {recipient}.\n') mail_cmd = ['mailx', '-r', dumper, '-s', subject, recipient] Run(mail_cmd, stdin=message, modes='soeW0') # check age of repositories for host, path, maintainer, max_age in backups: maintainer = default_maintainer if not maintainer else maintainer max_age = float(max_age) if max_age else default_max_age try: path = to_path(root, path) if path.is_dir(): paths = list(path.glob('index.*')) if not paths: raise Error('no sentinel file found.', culprit=path) if len(paths) > 1: raise Error('too many sentinel files.', *paths, sep='\n ') path = paths[0] mtime = arrow.get(path.stat().st_mtime) delta = now - mtime age = 24 * delta.days + delta.seconds / 3600 report = age > max_age color = fails if report else passes if report or not cmdline['--no-passes']: display( color( dedent(f""" HOST: {host} sentinel file: {path!s} last modified: {mtime} since last change: {age:0.1f} hours maximum age: {max_age} hours overdue: {report} """).lstrip())) if report: problem = True subject = f"backup of {host} is overdue" msg = overdue_message.format(host=host, path=path, age=age) send_mail(maintainer, subject, msg) except OSError as e: problem = True msg = os_error(e) error(msg) if maintainer: send_mail(maintainer, f'{get_prog_name()} error', error_message.format(msg)) except Error as e: problem = True e.report() if maintainer: send_mail(maintainer, f'{get_prog_name()} error', error_message.format(str(e))) terminate(problem)