When a command hook exits with a soft failure, ping the log and finish states for any configured monitoring hooks (#842).

This commit is contained in:
Dan Helfman 2024-03-18 23:15:28 -07:00
parent 893fbcf9ff
commit ecf5a7e294
3 changed files with 15 additions and 15 deletions

2
NEWS
View file

@ -1,4 +1,6 @@
1.8.10.dev0 1.8.10.dev0
* #842: When a command hook exits with a soft failure, ping the log and finish states for any
configured monitoring hooks.
* Fix handling of the NO_COLOR environment variable to ignore an empty value. * Fix handling of the NO_COLOR environment variable to ignore an empty value.
* Add documentation about backing up containerized databases by configuring borgmatic to exec into * Add documentation about backing up containerized databases by configuring borgmatic to exec into
a container to run a dump command: a container to run a dump command:

View file

@ -169,7 +169,7 @@ def run_configuration(config_filename, config, config_paths, arguments):
continue continue
if command.considered_soft_failure(config_filename, error): if command.considered_soft_failure(config_filename, error):
return break
yield from log_error_records( yield from log_error_records(
f'{repository.get("label", repository["path"])}: Error running actions for repository', f'{repository.get("label", repository["path"])}: Error running actions for repository',
@ -191,9 +191,7 @@ def run_configuration(config_filename, config, config_paths, arguments):
global_arguments.dry_run, global_arguments.dry_run,
) )
except (OSError, CalledProcessError) as error: except (OSError, CalledProcessError) as error:
if command.considered_soft_failure(config_filename, error): if not command.considered_soft_failure(config_filename, error):
return
encountered_error = error encountered_error = error
yield from log_error_records(f'{repository["path"]}: Error pinging monitor', error) yield from log_error_records(f'{repository["path"]}: Error pinging monitor', error)

View file

@ -61,7 +61,7 @@ def test_run_configuration_with_invalid_borg_version_errors():
flexmock(module.command).should_receive('execute_hook').never() flexmock(module.command).should_receive('execute_hook').never()
flexmock(module.dispatch).should_receive('call_hooks').never() flexmock(module.dispatch).should_receive('call_hooks').never()
flexmock(module).should_receive('run_actions').never() flexmock(module).should_receive('run_actions').never()
config = {'repositories': ['foo']} config = {'repositories': [{'path': 'foo'}]}
arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'prune': flexmock()} arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'prune': flexmock()}
list(module.run_configuration('test.yaml', config, ['/tmp/test.yaml'], arguments)) list(module.run_configuration('test.yaml', config, ['/tmp/test.yaml'], arguments))
@ -77,7 +77,7 @@ def test_run_configuration_logs_monitor_start_error():
expected_results = [flexmock()] expected_results = [flexmock()]
flexmock(module).should_receive('log_error_records').and_return(expected_results) flexmock(module).should_receive('log_error_records').and_return(expected_results)
flexmock(module).should_receive('run_actions').never() flexmock(module).should_receive('run_actions').never()
config = {'repositories': ['foo']} config = {'repositories': [{'path': 'foo'}]}
arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()} arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
results = list(module.run_configuration('test.yaml', config, ['/tmp/test.yaml'], arguments)) results = list(module.run_configuration('test.yaml', config, ['/tmp/test.yaml'], arguments))
@ -93,7 +93,7 @@ def test_run_configuration_bails_for_monitor_start_soft_failure():
flexmock(module.dispatch).should_receive('call_hooks').and_raise(error) flexmock(module.dispatch).should_receive('call_hooks').and_raise(error)
flexmock(module).should_receive('log_error_records').never() flexmock(module).should_receive('log_error_records').never()
flexmock(module).should_receive('run_actions').never() flexmock(module).should_receive('run_actions').never()
config = {'repositories': ['foo']} config = {'repositories': [{'path': 'foo'}]}
arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()} arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
results = list(module.run_configuration('test.yaml', config, ['/tmp/test.yaml'], arguments)) results = list(module.run_configuration('test.yaml', config, ['/tmp/test.yaml'], arguments))
@ -118,11 +118,11 @@ def test_run_configuration_logs_actions_error():
assert results == expected_results assert results == expected_results
def test_run_configuration_bails_for_actions_soft_failure(): def test_run_configuration_skips_remaining_actions_for_actions_soft_failure_but_still_pings_monitor():
flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO) flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
flexmock(module).should_receive('get_skip_actions').and_return([]) flexmock(module).should_receive('get_skip_actions').and_return([])
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock()) flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
flexmock(module.dispatch).should_receive('call_hooks') flexmock(module.dispatch).should_receive('call_hooks').times(5)
error = subprocess.CalledProcessError(borgmatic.hooks.command.SOFT_FAIL_EXIT_CODE, 'try again') error = subprocess.CalledProcessError(borgmatic.hooks.command.SOFT_FAIL_EXIT_CODE, 'try again')
flexmock(module).should_receive('run_actions').and_raise(error) flexmock(module).should_receive('run_actions').and_raise(error)
flexmock(module).should_receive('log_error_records').never() flexmock(module).should_receive('log_error_records').never()
@ -153,14 +153,14 @@ def test_run_configuration_logs_monitor_log_error():
assert results == expected_results assert results == expected_results
def test_run_configuration_bails_for_monitor_log_soft_failure(): def test_run_configuration_still_pings_monitor_for_monitor_log_soft_failure():
flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO) flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
flexmock(module).should_receive('get_skip_actions').and_return([]) flexmock(module).should_receive('get_skip_actions').and_return([])
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock()) flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
error = subprocess.CalledProcessError(borgmatic.hooks.command.SOFT_FAIL_EXIT_CODE, 'try again') error = subprocess.CalledProcessError(borgmatic.hooks.command.SOFT_FAIL_EXIT_CODE, 'try again')
flexmock(module.dispatch).should_receive('call_hooks').and_return(None).and_return( flexmock(module.dispatch).should_receive('call_hooks').and_return(None).and_return(
None None
).and_raise(error) ).and_raise(error).and_return(None).and_return(None).times(5)
flexmock(module).should_receive('log_error_records').never() flexmock(module).should_receive('log_error_records').never()
flexmock(module).should_receive('run_actions').and_return([]) flexmock(module).should_receive('run_actions').and_return([])
flexmock(module.command).should_receive('considered_soft_failure').and_return(True) flexmock(module.command).should_receive('considered_soft_failure').and_return(True)
@ -259,7 +259,7 @@ def test_run_configuration_bails_for_on_error_hook_soft_failure():
def test_run_configuration_retries_soft_error(): def test_run_configuration_retries_soft_error():
# Run action first fails, second passes # Run action first fails, second passes.
flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO) flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
flexmock(module).should_receive('get_skip_actions').and_return([]) flexmock(module).should_receive('get_skip_actions').and_return([])
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock()) flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
@ -273,7 +273,7 @@ def test_run_configuration_retries_soft_error():
def test_run_configuration_retries_hard_error(): def test_run_configuration_retries_hard_error():
# Run action fails twice # Run action fails twice.
flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO) flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
flexmock(module).should_receive('get_skip_actions').and_return([]) flexmock(module).should_receive('get_skip_actions').and_return([])
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock()) flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())