From e009bfeaa20d173ebe189cecb47c43819f747aa0 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Thu, 12 Dec 2019 22:54:45 -0800 Subject: [PATCH] Update Healthchecks/Cronitor/Cronhub monitoring integrations to fire for "check" and "prune" actions, not just "create" (#249). --- NEWS | 2 + borgmatic/commands/borgmatic.py | 65 ++++++++++++++------------- docs/how-to/monitor-your-backups.md | 31 +++++++------ tests/unit/commands/test_borgmatic.py | 35 ++++++++++++++- 4 files changed, 87 insertions(+), 46 deletions(-) diff --git a/NEWS b/NEWS index 49a167f9..61abaf45 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,7 @@ 1.4.20 * Fix repository probing during "borgmatic init" to respect verbosity flag and remote_path option. + * #249: Update Healthchecks/Cronitor/Cronhub monitoring integrations to fire for "check" and + "prune" actions, not just "create". 1.4.19 * #259: Optionally change the internal database dump path via "borgmatic_source_directory" option diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index 3b539764..f3da1618 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -53,8 +53,8 @@ def run_configuration(config_filename, config, arguments): encountered_error = None error_repository = '' - if 'create' in arguments: - try: + try: + if {'prune', 'create', 'check'}.intersection(arguments): dispatch.call_hooks( 'ping_monitor', hooks, @@ -63,6 +63,7 @@ def run_configuration(config_filename, config, arguments): monitor.State.START, global_arguments.dry_run, ) + if 'create' in arguments: command.execute_hook( hooks.get('before_backup'), hooks.get('umask'), @@ -78,11 +79,11 @@ def run_configuration(config_filename, config, arguments): location, global_arguments.dry_run, ) - except (OSError, CalledProcessError) as error: - encountered_error = error - yield from make_error_log_records( - '{}: Error running pre-backup hook'.format(config_filename), error - ) + except (OSError, CalledProcessError) as error: + encountered_error = error + yield from make_error_log_records( + '{}: Error running pre-backup hook'.format(config_filename), error + ) if not encountered_error: for repository_path in location['repositories']: @@ -105,31 +106,33 @@ def run_configuration(config_filename, config, arguments): '{}: Error running actions for repository'.format(repository_path), error ) - if 'create' in arguments and not encountered_error: + if not encountered_error: try: - dispatch.call_hooks( - 'remove_database_dumps', - hooks, - config_filename, - dump.DATABASE_HOOK_NAMES, - location, - global_arguments.dry_run, - ) - command.execute_hook( - hooks.get('after_backup'), - hooks.get('umask'), - config_filename, - 'post-backup', - global_arguments.dry_run, - ) - dispatch.call_hooks( - 'ping_monitor', - hooks, - config_filename, - monitor.MONITOR_HOOK_NAMES, - monitor.State.FINISH, - global_arguments.dry_run, - ) + if 'create' in arguments: + dispatch.call_hooks( + 'remove_database_dumps', + hooks, + config_filename, + dump.DATABASE_HOOK_NAMES, + location, + global_arguments.dry_run, + ) + command.execute_hook( + hooks.get('after_backup'), + hooks.get('umask'), + config_filename, + 'post-backup', + global_arguments.dry_run, + ) + if {'prune', 'create', 'check'}.intersection(arguments): + dispatch.call_hooks( + 'ping_monitor', + hooks, + config_filename, + monitor.MONITOR_HOOK_NAMES, + monitor.State.FINISH, + global_arguments.dry_run, + ) except (OSError, CalledProcessError) as error: encountered_error = error yield from make_error_log_records( diff --git a/docs/how-to/monitor-your-backups.md b/docs/how-to/monitor-your-backups.md index cddadb01..f2f105da 100644 --- a/docs/how-to/monitor-your-backups.md +++ b/docs/how-to/monitor-your-backups.md @@ -116,21 +116,22 @@ hooks: With this hook in place, borgmatic pings your Healthchecks project when a backup begins, ends, or errors. Specifically, before the `before_backup` -hooks run, borgmatic lets Healthchecks know that a backup has started. +hooks run, borgmatic lets Healthchecks know that it has started if any of +the `prune`, `create`, or `check` actions are run. -Then, if the backup completes successfully, borgmatic notifies Healthchecks of +Then, if the actions complete successfully, borgmatic notifies Healthchecks of the success after the `after_backup` hooks run, and includes borgmatic logs in the payload data sent to Healthchecks. This means that borgmatic logs show up in the Healthchecks UI, although be aware that Healthchecks currently has a 10-kilobyte limit for the logs in each ping. -If an error occurs during the backup, borgmatic notifies Healthchecks after +If an error occurs during any action, borgmatic notifies Healthchecks after the `on_error` hooks run, also tacking on logs including the error itself. But -the logs are only included for errors that occur within the borgmatic `create` -action (and not other actions). +the logs are only included for errors that occur when a `prune`, `create`, or +`check` action is run. Note that borgmatic sends logs to Healthchecks by applying the maximum of any -other borgmatic verbosity level (`--verbosity`, `--syslog-verbosity`, etc.), +other borgmatic verbosity levels (`--verbosity`, `--syslog-verbosity`, etc.), as there is not currently a dedicated Healthchecks verbosity setting. You can configure Healthchecks to notify you by a [variety of @@ -155,10 +156,11 @@ hooks: With this hook in place, borgmatic pings your Cronitor monitor when a backup begins, ends, or errors. Specifically, before the `before_backup` -hooks run, borgmatic lets Cronitor know that a backup has started. Then, -if the backup completes successfully, borgmatic notifies Cronitor of the -success after the `after_backup` hooks run. And if an error occurs during the -backup, borgmatic notifies Cronitor after the `on_error` hooks run. +hooks run, borgmatic lets Cronitor know that it has started if any of the +`prune`, `create`, or `check` actions are run. Then, if the actions complete +successfully, borgmatic notifies Cronitor of the success after the +`after_backup` hooks run. And if an error occurs during any action, borgmatic +notifies Cronitor after the `on_error` hooks run. You can configure Cronitor to notify you by a [variety of mechanisms](https://cronitor.io/docs/cron-job-notifications) when backups fail @@ -182,10 +184,11 @@ hooks: With this hook in place, borgmatic pings your Cronhub monitor when a backup begins, ends, or errors. Specifically, before the `before_backup` -hooks run, borgmatic lets Cronhub know that a backup has started. Then, -if the backup completes successfully, borgmatic notifies Cronhub of the -success after the `after_backup` hooks run. And if an error occurs during the -backup, borgmatic notifies Cronhub after the `on_error` hooks run. +hooks run, borgmatic lets Cronhub know that it has started if any of the +`prune`, `create`, or `check` actions are run. Then, if the actions complete +successfully, borgmatic notifies Cronhub of the success after the +`after_backup` hooks run. And if an error occurs during any action, borgmatic +notifies Cronhub after the `on_error` hooks run. Note that even though you configure borgmatic with the "start" variant of the ping URL, borgmatic substitutes the correct state into the URL when pinging diff --git a/tests/unit/commands/test_borgmatic.py b/tests/unit/commands/test_borgmatic.py index 1bdb3213..a2b98f37 100644 --- a/tests/unit/commands/test_borgmatic.py +++ b/tests/unit/commands/test_borgmatic.py @@ -20,7 +20,18 @@ def test_run_configuration_runs_actions_for_each_repository(): assert results == expected_results -def test_run_configuration_executes_hooks_for_create_action(): +def test_run_configuration_calls_hooks_for_prune_action(): + flexmock(module.borg_environment).should_receive('initialize') + flexmock(module.command).should_receive('execute_hook').never() + flexmock(module.dispatch).should_receive('call_hooks').at_least().twice() + flexmock(module).should_receive('run_actions').and_return([]) + config = {'location': {'repositories': ['foo']}} + arguments = {'global': flexmock(dry_run=False), 'prune': flexmock()} + + list(module.run_configuration('test.yaml', config, arguments)) + + +def test_run_configuration_executes_and_calls_hooks_for_create_action(): flexmock(module.borg_environment).should_receive('initialize') flexmock(module.command).should_receive('execute_hook').twice() flexmock(module.dispatch).should_receive('call_hooks').at_least().twice() @@ -31,6 +42,28 @@ def test_run_configuration_executes_hooks_for_create_action(): list(module.run_configuration('test.yaml', config, arguments)) +def test_run_configuration_calls_hooks_for_check_action(): + flexmock(module.borg_environment).should_receive('initialize') + flexmock(module.command).should_receive('execute_hook').never() + flexmock(module.dispatch).should_receive('call_hooks').at_least().twice() + flexmock(module).should_receive('run_actions').and_return([]) + config = {'location': {'repositories': ['foo']}} + arguments = {'global': flexmock(dry_run=False), 'check': flexmock()} + + list(module.run_configuration('test.yaml', config, arguments)) + + +def test_run_configuration_does_not_trigger_hooks_for_list_action(): + flexmock(module.borg_environment).should_receive('initialize') + flexmock(module.command).should_receive('execute_hook').never() + flexmock(module.dispatch).should_receive('call_hooks').never() + flexmock(module).should_receive('run_actions').and_return([]) + config = {'location': {'repositories': ['foo']}} + arguments = {'global': flexmock(dry_run=False), 'list': flexmock()} + + list(module.run_configuration('test.yaml', config, arguments)) + + def test_run_configuration_logs_actions_error(): flexmock(module.borg_environment).should_receive('initialize') flexmock(module.command).should_receive('execute_hook')