mirror of
https://github.com/borgmatic-collective/borgmatic.git
synced 2026-07-23 02:23:01 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13d49fda9b | ||
|
|
b01b8498aa | ||
|
|
a573e606a5 | ||
|
|
81db67c759 |
@@ -1,3 +1,10 @@
|
||||
2.0.3
|
||||
* #1065: Fix a regression in monitoring hooks in which an error pinged the finish state instead of
|
||||
the fail state.
|
||||
* #1066: Add a "states" option to command hooks, so you can optionally skip an "after" hook if
|
||||
borgmatic encounters an error.
|
||||
* #1071: Fix an error in the LVM hook when removing a snapshot directory.
|
||||
|
||||
2.0.2
|
||||
* #1035: Document potential performance issues and workarounds with the ZFS, Btrfs, and LVM hooks:
|
||||
https://torsion.org/borgmatic/docs/how-to/snapshot-your-filesystems/
|
||||
|
||||
@@ -279,8 +279,19 @@ def run_configuration(config_filename, config, config_paths, arguments):
|
||||
encountered_error = error
|
||||
error_repository = repository
|
||||
|
||||
# Re-raise any error, so that the Monitoring_hooks context manager wrapping this
|
||||
# code can see the error and act accordingly. Do this here rather than as soon as
|
||||
# the error is encountered so that an error with one repository doesn't prevent
|
||||
# other repositories from running.
|
||||
if encountered_error:
|
||||
raise encountered_error
|
||||
|
||||
except (OSError, CalledProcessError, ValueError) as error:
|
||||
yield from log_error_records('Error running configuration', error)
|
||||
# No need to repeat logging of the error if it was already logged above.
|
||||
if error_repository:
|
||||
yield from log_error_records('Error running configuration')
|
||||
else:
|
||||
yield from log_error_records('Error running configuration', error)
|
||||
|
||||
encountered_error = error
|
||||
|
||||
@@ -290,7 +301,10 @@ def run_configuration(config_filename, config, config_paths, arguments):
|
||||
try:
|
||||
command.execute_hooks(
|
||||
command.filter_hooks(
|
||||
config.get('commands'), after='error', action_names=arguments.keys()
|
||||
config.get('commands'),
|
||||
after='error',
|
||||
action_names=arguments.keys(),
|
||||
state_names=['fail'],
|
||||
),
|
||||
config.get('umask'),
|
||||
borgmatic.config.paths.get_working_directory(config),
|
||||
@@ -896,6 +910,7 @@ def collect_configuration_run_summary_logs(configs, config_paths, arguments, log
|
||||
|
||||
# Execute the actions corresponding to each configuration file.
|
||||
json_results = []
|
||||
encountered_error = False
|
||||
|
||||
for config_filename, config in configs.items():
|
||||
with Log_prefix(config_filename):
|
||||
@@ -906,6 +921,7 @@ def collect_configuration_run_summary_logs(configs, config_paths, arguments, log
|
||||
)
|
||||
|
||||
if error_logs:
|
||||
encountered_error = True
|
||||
yield from log_error_records('An error occurred')
|
||||
yield from error_logs
|
||||
else:
|
||||
@@ -928,6 +944,7 @@ def collect_configuration_run_summary_logs(configs, config_paths, arguments, log
|
||||
local_path=get_local_path(configs),
|
||||
)
|
||||
except (CalledProcessError, OSError) as error:
|
||||
encountered_error = True
|
||||
yield from log_error_records('Error unmounting mount point', error)
|
||||
|
||||
if json_results:
|
||||
@@ -937,7 +954,10 @@ def collect_configuration_run_summary_logs(configs, config_paths, arguments, log
|
||||
for config_filename, config in configs.items():
|
||||
command.execute_hooks(
|
||||
command.filter_hooks(
|
||||
config.get('commands'), after='everything', action_names=arguments.keys()
|
||||
config.get('commands'),
|
||||
after='everything',
|
||||
action_names=arguments.keys(),
|
||||
state_names=['fail' if encountered_error else 'finish'],
|
||||
),
|
||||
config.get('umask'),
|
||||
borgmatic.config.paths.get_working_directory(config),
|
||||
|
||||
@@ -1225,6 +1225,26 @@ properties:
|
||||
particular actions listed here. Defaults to
|
||||
running for all actions.
|
||||
example: [create, prune, compact, check]
|
||||
states:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- finish
|
||||
- fail
|
||||
description: |
|
||||
Only trigger the hook if borgmatic encounters one
|
||||
of the states (execution results) listed here,
|
||||
where:
|
||||
* "finish": No errors occurred.
|
||||
* "fail": An error occurred.
|
||||
This state is evaluated only for the scope of the
|
||||
configured "action", "repository", etc., rather
|
||||
than for the entire borgmatic run. Only available
|
||||
for "after" hooks. Defaults to running the hook
|
||||
for all states.
|
||||
example:
|
||||
- finish
|
||||
run:
|
||||
type: array
|
||||
items:
|
||||
|
||||
+13
-12
@@ -47,21 +47,25 @@ def make_environment(current_environment, sys_module=sys):
|
||||
return environment
|
||||
|
||||
|
||||
def filter_hooks(command_hooks, before=None, after=None, hook_name=None, action_names=None):
|
||||
def filter_hooks(command_hooks, before=None, after=None, action_names=None, state_names=None):
|
||||
'''
|
||||
Given a sequence of command hook dicts from configuration and one or more filters (before name,
|
||||
after name, calling hook name, or a sequence of action names), filter down the command hooks to
|
||||
just the ones that match the given filters.
|
||||
after name, a sequence of action names, and/or a sequence of execution result state names),
|
||||
filter down the command hooks to just the ones that match the given filters.
|
||||
'''
|
||||
return tuple(
|
||||
hook_config
|
||||
for hook_config in command_hooks or ()
|
||||
for config_action_names in (hook_config.get('when'),)
|
||||
for config_state_names in (hook_config.get('states'),)
|
||||
if before is None or hook_config.get('before') == before
|
||||
if after is None or hook_config.get('after') == after
|
||||
if action_names is None
|
||||
or config_action_names is None
|
||||
or set(config_action_names or ()).intersection(set(action_names))
|
||||
if state_names is None
|
||||
or config_state_names is None
|
||||
or set(config_state_names or ()).intersection(set(state_names))
|
||||
)
|
||||
|
||||
|
||||
@@ -143,7 +147,7 @@ class Before_after_hooks:
|
||||
before_after='do_stuff',
|
||||
umask=config.get('umask'),
|
||||
dry_run=dry_run,
|
||||
hook_name='myhook',
|
||||
action_names=['create'],
|
||||
):
|
||||
do()
|
||||
some()
|
||||
@@ -160,22 +164,20 @@ class Before_after_hooks:
|
||||
umask,
|
||||
working_directory,
|
||||
dry_run,
|
||||
hook_name=None,
|
||||
action_names=None,
|
||||
**context,
|
||||
):
|
||||
'''
|
||||
Given a sequence of command hook configuration dicts, the before/after name, a umask to run
|
||||
commands with, a working directory to run commands with, a dry run flag, the name of the
|
||||
calling hook, a sequence of action names, and any context for the executed commands, save
|
||||
those data points for use below.
|
||||
commands with, a working directory to run commands with, a dry run flag, a sequence of
|
||||
action names, and any context for the executed commands, save those data points for use
|
||||
below.
|
||||
'''
|
||||
self.command_hooks = command_hooks
|
||||
self.before_after = before_after
|
||||
self.umask = umask
|
||||
self.working_directory = working_directory
|
||||
self.dry_run = dry_run
|
||||
self.hook_name = hook_name
|
||||
self.action_names = action_names
|
||||
self.context = context
|
||||
|
||||
@@ -188,7 +190,6 @@ class Before_after_hooks:
|
||||
borgmatic.hooks.command.filter_hooks(
|
||||
self.command_hooks,
|
||||
before=self.before_after,
|
||||
hook_name=self.hook_name,
|
||||
action_names=self.action_names,
|
||||
),
|
||||
self.umask,
|
||||
@@ -202,7 +203,7 @@ class Before_after_hooks:
|
||||
|
||||
# Trigger the after hook manually, since raising here will prevent it from being run
|
||||
# otherwise.
|
||||
self.__exit__(None, None, None)
|
||||
self.__exit__(exception_type=type(error), exception=error, traceback=None)
|
||||
|
||||
raise ValueError(f'Error running before {self.before_after} hook: {error}')
|
||||
|
||||
@@ -215,8 +216,8 @@ class Before_after_hooks:
|
||||
borgmatic.hooks.command.filter_hooks(
|
||||
self.command_hooks,
|
||||
after=self.before_after,
|
||||
hook_name=self.hook_name,
|
||||
action_names=self.action_names,
|
||||
state_names=['fail' if exception_type else 'finish'],
|
||||
),
|
||||
self.umask,
|
||||
self.working_directory,
|
||||
|
||||
@@ -407,7 +407,7 @@ def remove_data_source_dumps(hook_config, config, borgmatic_runtime_directory, d
|
||||
continue
|
||||
|
||||
if not dry_run:
|
||||
shutil.rmtree(snapshots_directory)
|
||||
shutil.rmtree(snapshots_directory, ignore_errors=True)
|
||||
|
||||
# Delete snapshots.
|
||||
lvremove_command = hook_config.get('lvremove_command', 'lvremove')
|
||||
|
||||
@@ -55,6 +55,29 @@ commands:
|
||||
- "echo Backup: start"
|
||||
```
|
||||
|
||||
By default, an `after` command hook runs even if an error occurs in the
|
||||
corresponding `before` hook or between those two hooks. This allows you to
|
||||
perform cleanup steps that correspond to `before` preparation commands—even when
|
||||
something goes wrong. You may notice that this is a departure from the way that
|
||||
the deprecated `after_*` hooks worked in borgmatic prior to version 2.0.0.
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 2.0.3</span> You can
|
||||
customize this behavior with the `states` option. For instance, here's an
|
||||
example of an `after` hook that only triggers on success and not on error:
|
||||
|
||||
```yaml
|
||||
commands:
|
||||
- after: action
|
||||
when: [create]
|
||||
states: [finish]
|
||||
run:
|
||||
- echo "After successful create!"
|
||||
```
|
||||
|
||||
Additionally, when command hooks run, they respect the `working_directory`
|
||||
option if it is configured, meaning that the hook commands are run in that
|
||||
directory.
|
||||
|
||||
Each command in the `commands:` list has the following options:
|
||||
|
||||
* `before` or `after`: Name for the point in borgmatic's execution that the commands should be run before or after, one of:
|
||||
@@ -64,18 +87,11 @@ Each command in the `commands:` list has the following options:
|
||||
* `everything` runs before or after all configuration files. Errors here do not trigger `error` hooks or the `fail` state in monitoring hooks. This replaces the deprecated `before_everything` and `after_everything`.
|
||||
* `error` runs after an error occurs—and it's only available for `after`. This replaces the deprecated `on_error` hook.
|
||||
* `when`: Only trigger the hook when borgmatic is run with particular actions (`create`, `prune`, etc.) listed here. Defaults to running for all actions.
|
||||
* `states`: <span class="minilink minilink-addedin">New in version 2.0.3</span> Only trigger the hook if borgmatic encounters one of the states (execution results) listed here. This state is evaluated only for the scope of the configured `action`, `repository`, etc., rather than for the entire borgmatic run. Only available for `after` hooks. Defaults to running the hook for all states. One or more of:
|
||||
* `finish`: No errors occurred.
|
||||
* `fail`: An error occurred.
|
||||
* `run`: List of one or more shell commands or scripts to run when this command hook is triggered.
|
||||
|
||||
An `after` command hook runs even if an error occurs in the corresponding
|
||||
`before` hook or between those two hooks. This allows you to perform cleanup
|
||||
steps that correspond to `before` preparation commands—even when something goes
|
||||
wrong. This is a departure from the way that the deprecated `after_*` hooks
|
||||
worked in borgmatic prior to version 2.0.0.
|
||||
|
||||
Additionally, when command hooks run, they respect the `working_directory`
|
||||
option if it is configured, meaning that the hook commands are run in that
|
||||
directory.
|
||||
|
||||
|
||||
### Order of execution
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "borgmatic"
|
||||
version = "2.0.2"
|
||||
version = "2.0.3"
|
||||
authors = [
|
||||
{ name="Dan Helfman", email="witten@torsion.org" },
|
||||
]
|
||||
|
||||
@@ -12,3 +12,59 @@ def test_borgmatic_version_matches_news_version():
|
||||
news_version = open('NEWS').readline()
|
||||
|
||||
assert borgmatic_version == news_version
|
||||
|
||||
|
||||
def test_run_configuration_without_error_pings_monitoring_hooks_start_and_finish():
|
||||
config = {'repositories': [{'path': 'foo'}]}
|
||||
arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module).should_receive('run_actions').and_return([])
|
||||
flexmock(module.dispatch).should_receive('call_hooks')
|
||||
flexmock(module.dispatch).should_receive('call_hooks').with_args(
|
||||
'ping_monitor',
|
||||
config,
|
||||
module.dispatch.Hook_type.MONITORING,
|
||||
'test.yaml',
|
||||
module.monitor.State.START,
|
||||
object,
|
||||
object,
|
||||
).once()
|
||||
flexmock(module.dispatch).should_receive('call_hooks').with_args(
|
||||
'ping_monitor',
|
||||
config,
|
||||
module.dispatch.Hook_type.MONITORING,
|
||||
'test.yaml',
|
||||
module.monitor.State.FINISH,
|
||||
object,
|
||||
object,
|
||||
).once()
|
||||
|
||||
list(module.run_configuration('test.yaml', config, ['/tmp/test.yaml'], arguments))
|
||||
|
||||
|
||||
def test_run_configuration_with_action_error_pings_monioring_hooks_start_and_fail():
|
||||
config = {'repositories': [{'path': 'foo'}]}
|
||||
arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module).should_receive('run_actions').and_raise(OSError)
|
||||
flexmock(module.dispatch).should_receive('call_hooks')
|
||||
flexmock(module.dispatch).should_receive('call_hooks').with_args(
|
||||
'ping_monitor',
|
||||
config,
|
||||
module.dispatch.Hook_type.MONITORING,
|
||||
'test.yaml',
|
||||
module.monitor.State.START,
|
||||
object,
|
||||
object,
|
||||
).once()
|
||||
flexmock(module.dispatch).should_receive('call_hooks').with_args(
|
||||
'ping_monitor',
|
||||
config,
|
||||
module.dispatch.Hook_type.MONITORING,
|
||||
'test.yaml',
|
||||
module.monitor.State.FAIL,
|
||||
object,
|
||||
object,
|
||||
).once()
|
||||
|
||||
list(module.run_configuration('test.yaml', config, ['/tmp/test.yaml'], arguments))
|
||||
|
||||
@@ -465,8 +465,10 @@ def test_run_configuration_logs_actions_error():
|
||||
flexmock(module).should_receive('Monitoring_hooks').and_return(flexmock())
|
||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
expected_results = [flexmock()]
|
||||
flexmock(module).should_receive('log_error_records').and_return(expected_results)
|
||||
expected_results = [flexmock(), flexmock()]
|
||||
flexmock(module).should_receive('log_error_records').and_return(
|
||||
expected_results[:1]
|
||||
).and_return(expected_results[1:])
|
||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('run_actions').and_raise(OSError)
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
@@ -532,7 +534,7 @@ def test_run_configuration_logs_on_error_hook_error():
|
||||
expected_results = [flexmock(), flexmock()]
|
||||
flexmock(module).should_receive('log_error_records').and_return(
|
||||
expected_results[:1]
|
||||
).and_return(expected_results[1:])
|
||||
).and_return(expected_results[1:2]).and_return(expected_results[2:])
|
||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('run_actions').and_raise(OSError)
|
||||
config = {'repositories': [{'path': 'foo'}]}
|
||||
@@ -601,8 +603,10 @@ def test_run_configuration_bails_for_on_error_hook_soft_failure():
|
||||
error = subprocess.CalledProcessError(borgmatic.hooks.command.SOFT_FAIL_EXIT_CODE, 'try again')
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
flexmock(module.command).should_receive('execute_hooks').and_raise(error)
|
||||
expected_results = [flexmock()]
|
||||
flexmock(module).should_receive('log_error_records').and_return(expected_results)
|
||||
expected_results = [flexmock(), flexmock()]
|
||||
flexmock(module).should_receive('log_error_records').and_return(
|
||||
expected_results[:1]
|
||||
).and_return(expected_results[1:])
|
||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('run_actions').and_raise(OSError)
|
||||
config = {'repositories': [{'path': 'foo'}]}
|
||||
@@ -654,11 +658,14 @@ def test_run_configuration_retries_hard_error():
|
||||
levelno=logging.WARNING,
|
||||
log_command_error_output=True,
|
||||
).and_return([flexmock()])
|
||||
error_logs = [flexmock()]
|
||||
error_logs = [flexmock(), flexmock()]
|
||||
flexmock(module).should_receive('log_error_records').with_args(
|
||||
'Error running actions for repository',
|
||||
OSError,
|
||||
).and_return(error_logs)
|
||||
).and_return(error_logs[:1]).ordered()
|
||||
flexmock(module).should_receive('log_error_records').with_args(
|
||||
'Error running configuration',
|
||||
).and_return(error_logs[1:]).ordered()
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
flexmock(module.command).should_receive('execute_hooks')
|
||||
config = {'repositories': [{'path': 'foo'}], 'retries': 1}
|
||||
@@ -680,13 +687,16 @@ def test_run_configuration_retries_repositories_in_order():
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('run_actions').and_raise(OSError).times(2)
|
||||
expected_results = [flexmock(), flexmock()]
|
||||
expected_results = [flexmock(), flexmock(), flexmock()]
|
||||
flexmock(module).should_receive('log_error_records').with_args(
|
||||
'Error running actions for repository', OSError
|
||||
).and_return(expected_results[:1]).ordered()
|
||||
flexmock(module).should_receive('log_error_records').with_args(
|
||||
'Error running actions for repository', OSError
|
||||
).and_return(expected_results[1:]).ordered()
|
||||
).and_return(expected_results[1:2]).ordered()
|
||||
flexmock(module).should_receive('log_error_records').with_args(
|
||||
'Error running configuration',
|
||||
).and_return(expected_results[2:]).ordered()
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
flexmock(module.command).should_receive('execute_hooks')
|
||||
config = {'repositories': [{'path': 'foo'}, {'path': 'bar'}]}
|
||||
@@ -728,6 +738,10 @@ def test_run_configuration_retries_round_robin():
|
||||
flexmock(module).should_receive('log_error_records').with_args(
|
||||
'Error running actions for repository', OSError
|
||||
).and_return(bar_error_logs).ordered()
|
||||
config_error_logs = [flexmock()]
|
||||
flexmock(module).should_receive('log_error_records').with_args(
|
||||
'Error running configuration',
|
||||
).and_return(config_error_logs).ordered()
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
flexmock(module.command).should_receive('execute_hooks')
|
||||
config = {
|
||||
@@ -741,7 +755,7 @@ def test_run_configuration_retries_round_robin():
|
||||
|
||||
results = list(module.run_configuration('test.yaml', config, ['/tmp/test.yaml'], arguments))
|
||||
|
||||
assert results == foo_error_logs + bar_error_logs
|
||||
assert results == foo_error_logs + bar_error_logs + config_error_logs
|
||||
|
||||
|
||||
def test_run_configuration_with_one_retry():
|
||||
@@ -766,10 +780,13 @@ def test_run_configuration_with_one_retry():
|
||||
levelno=logging.WARNING,
|
||||
log_command_error_output=True,
|
||||
).and_return(flexmock()).ordered()
|
||||
error_logs = [flexmock()]
|
||||
error_logs = [flexmock(), flexmock()]
|
||||
flexmock(module).should_receive('log_error_records').with_args(
|
||||
'Error running actions for repository', OSError
|
||||
).and_return(error_logs).ordered()
|
||||
).and_return(error_logs[:1]).ordered()
|
||||
flexmock(module).should_receive('log_error_records').with_args(
|
||||
'Error running configuration',
|
||||
).and_return(error_logs[1:]).ordered()
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
flexmock(module.command).should_receive('execute_hooks')
|
||||
config = {
|
||||
@@ -818,10 +835,13 @@ def test_run_configuration_with_retry_wait_does_backoff_after_each_retry():
|
||||
).and_return([flexmock()]).ordered()
|
||||
|
||||
flexmock(time).should_receive('sleep').with_args(30).and_return().ordered()
|
||||
error_logs = [flexmock()]
|
||||
error_logs = [flexmock(), flexmock()]
|
||||
flexmock(module).should_receive('log_error_records').with_args(
|
||||
'Error running actions for repository', OSError
|
||||
).and_return(error_logs).ordered()
|
||||
).and_return(error_logs[:1]).ordered()
|
||||
flexmock(module).should_receive('log_error_records').with_args(
|
||||
'Error running configuration',
|
||||
).and_return(error_logs[1:]).ordered()
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
flexmock(module.command).should_receive('execute_hooks')
|
||||
config = {
|
||||
@@ -867,10 +887,13 @@ def test_run_configuration_with_multiple_repositories_retries_with_timeout():
|
||||
|
||||
# Sleep before retrying bar (and failing)
|
||||
flexmock(time).should_receive('sleep').with_args(10).and_return().ordered()
|
||||
error_logs = [flexmock()]
|
||||
error_logs = [flexmock(), flexmock()]
|
||||
flexmock(module).should_receive('log_error_records').with_args(
|
||||
'Error running actions for repository', OSError
|
||||
).and_return(error_logs).ordered()
|
||||
).and_return(error_logs[:1]).ordered()
|
||||
flexmock(module).should_receive('log_error_records').with_args(
|
||||
'Error running configuration',
|
||||
).and_return(error_logs[1:]).ordered()
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
flexmock(module.command).should_receive('execute_hooks')
|
||||
config = {
|
||||
@@ -1909,7 +1932,12 @@ def test_collect_highlander_action_summary_logs_error_on_run_validate_failure():
|
||||
|
||||
def test_collect_configuration_run_summary_logs_info_for_success():
|
||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, before='everything', action_names=object
|
||||
)
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, after='everything', action_names=object, state_names=['finish']
|
||||
)
|
||||
flexmock(module.command).should_receive('execute_hooks')
|
||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('run_configuration').and_return([])
|
||||
@@ -1929,7 +1957,12 @@ def test_collect_configuration_run_summary_logs_info_for_success():
|
||||
|
||||
def test_collect_configuration_run_summary_executes_hooks_for_create():
|
||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, before='everything', action_names=object
|
||||
)
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, after='everything', action_names=object, state_names=['finish']
|
||||
)
|
||||
flexmock(module.command).should_receive('execute_hooks')
|
||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('run_configuration').and_return([])
|
||||
@@ -1952,7 +1985,12 @@ def test_collect_configuration_run_summary_executes_hooks_for_create():
|
||||
|
||||
def test_collect_configuration_run_summary_logs_info_for_success_with_extract():
|
||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, before='everything', action_names=object
|
||||
)
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, after='everything', action_names=object, state_names=['finish']
|
||||
)
|
||||
flexmock(module.command).should_receive('execute_hooks')
|
||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('run_configuration').and_return([])
|
||||
@@ -1995,7 +2033,12 @@ def test_collect_configuration_run_summary_logs_extract_with_repository_error():
|
||||
|
||||
def test_collect_configuration_run_summary_logs_info_for_success_with_mount():
|
||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, before='everything', action_names=object
|
||||
)
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, after='everything', action_names=object, state_names=['finish']
|
||||
)
|
||||
flexmock(module.command).should_receive('execute_hooks')
|
||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('run_configuration').and_return([])
|
||||
@@ -2041,7 +2084,12 @@ def test_collect_configuration_run_summary_logs_mount_with_repository_error():
|
||||
|
||||
def test_collect_configuration_run_summary_logs_missing_configs_error():
|
||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, before='everything', action_names=object
|
||||
)
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, after='everything', action_names=object, state_names=['fail']
|
||||
)
|
||||
flexmock(module.command).should_receive('execute_hooks')
|
||||
arguments = {'global': flexmock(config_paths=[])}
|
||||
expected_logs = (flexmock(),)
|
||||
@@ -2056,9 +2104,14 @@ def test_collect_configuration_run_summary_logs_missing_configs_error():
|
||||
assert logs == expected_logs
|
||||
|
||||
|
||||
def test_collect_configuration_run_summary_logs_pre_hook_error():
|
||||
def test_collect_configuration_run_summary_logs_before_hook_error():
|
||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, before='everything', action_names=object
|
||||
)
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, after='everything', action_names=object, state_names=['fail']
|
||||
)
|
||||
flexmock(module.command).should_receive('execute_hooks').and_raise(ValueError)
|
||||
expected_logs = (flexmock(),)
|
||||
flexmock(module).should_receive('log_error_records').and_return(expected_logs)
|
||||
@@ -2079,9 +2132,14 @@ def test_collect_configuration_run_summary_logs_pre_hook_error():
|
||||
assert logs == expected_logs
|
||||
|
||||
|
||||
def test_collect_configuration_run_summary_logs_post_hook_error():
|
||||
def test_collect_configuration_run_summary_logs_after_hook_error():
|
||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, before='everything', action_names=object
|
||||
)
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, after='everything', action_names=object, state_names=['finish']
|
||||
)
|
||||
flexmock(module.command).should_receive('execute_hooks').and_return(None).and_raise(ValueError)
|
||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('run_configuration').and_return([])
|
||||
@@ -2129,7 +2187,12 @@ def test_collect_configuration_run_summary_logs_for_list_with_archive_and_reposi
|
||||
|
||||
def test_collect_configuration_run_summary_logs_info_for_success_with_list():
|
||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, before='everything', action_names=object
|
||||
)
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, after='everything', action_names=object, state_names=['finish']
|
||||
)
|
||||
flexmock(module.command).should_receive('execute_hooks')
|
||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('run_configuration').and_return([])
|
||||
@@ -2152,7 +2215,12 @@ def test_collect_configuration_run_summary_logs_info_for_success_with_list():
|
||||
|
||||
def test_collect_configuration_run_summary_logs_run_configuration_error_logs():
|
||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, before='everything', action_names=object
|
||||
)
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, after='everything', action_names=object, state_names=['fail']
|
||||
)
|
||||
flexmock(module.command).should_receive('execute_hooks')
|
||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('run_configuration').and_return(
|
||||
@@ -2175,7 +2243,12 @@ def test_collect_configuration_run_summary_logs_run_configuration_error_logs():
|
||||
|
||||
def test_collect_configuration_run_summary_logs_run_umount_error():
|
||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, before='everything', action_names=object
|
||||
)
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, after='everything', action_names=object, state_names=['fail']
|
||||
)
|
||||
flexmock(module.command).should_receive('execute_hooks')
|
||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('run_configuration').and_return([])
|
||||
@@ -2202,7 +2275,12 @@ def test_collect_configuration_run_summary_logs_run_umount_error():
|
||||
|
||||
def test_collect_configuration_run_summary_logs_outputs_merged_json_results():
|
||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||
flexmock(module.command).should_receive('filter_hooks')
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, before='everything', action_names=object
|
||||
)
|
||||
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||
object, after='everything', action_names=object, state_names=['finish']
|
||||
)
|
||||
flexmock(module.command).should_receive('execute_hooks')
|
||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('run_configuration').and_return(['foo', 'bar']).and_return(
|
||||
|
||||
@@ -167,6 +167,44 @@ def test_make_environment_with_pyinstaller_and_LD_LIBRARY_PATH_ORIG_copies_it_in
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
(
|
||||
{
|
||||
'before': 'action',
|
||||
'states': ['finish'], # Not actually valid; only valid for "after".
|
||||
'run': ['foo'],
|
||||
},
|
||||
{
|
||||
'after': 'action',
|
||||
'run': ['bar'],
|
||||
},
|
||||
{
|
||||
'after': 'action',
|
||||
'states': ['finish'],
|
||||
'run': ['baz'],
|
||||
},
|
||||
{
|
||||
'after': 'action',
|
||||
'states': ['fail'],
|
||||
'run': ['quux'],
|
||||
},
|
||||
),
|
||||
{
|
||||
'after': 'action',
|
||||
'state_names': ['finish'],
|
||||
},
|
||||
(
|
||||
{
|
||||
'after': 'action',
|
||||
'run': ['bar'],
|
||||
},
|
||||
{
|
||||
'after': 'action',
|
||||
'states': ['finish'],
|
||||
'run': ['baz'],
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_filter_hooks(hooks, filters, expected_hooks):
|
||||
@@ -336,14 +374,13 @@ def test_before_after_hooks_calls_command_hooks():
|
||||
flexmock(module).should_receive('filter_hooks').with_args(
|
||||
commands,
|
||||
before='action',
|
||||
hook_name='myhook',
|
||||
action_names=['create'],
|
||||
).and_return(flexmock()).once()
|
||||
flexmock(module).should_receive('filter_hooks').with_args(
|
||||
commands,
|
||||
after='action',
|
||||
hook_name='myhook',
|
||||
action_names=['create'],
|
||||
state_names=['finish'],
|
||||
).and_return(flexmock()).once()
|
||||
flexmock(module).should_receive('execute_hooks').twice()
|
||||
|
||||
@@ -353,7 +390,6 @@ def test_before_after_hooks_calls_command_hooks():
|
||||
umask=1234,
|
||||
working_directory='/working',
|
||||
dry_run=False,
|
||||
hook_name='myhook',
|
||||
action_names=['create'],
|
||||
context1='stuff',
|
||||
context2='such',
|
||||
@@ -369,14 +405,13 @@ def test_before_after_hooks_with_before_error_runs_after_hook_and_raises():
|
||||
flexmock(module).should_receive('filter_hooks').with_args(
|
||||
commands,
|
||||
before='action',
|
||||
hook_name='myhook',
|
||||
action_names=['create'],
|
||||
).and_return(flexmock()).once()
|
||||
flexmock(module).should_receive('filter_hooks').with_args(
|
||||
commands,
|
||||
after='action',
|
||||
hook_name='myhook',
|
||||
action_names=['create'],
|
||||
state_names=['fail'],
|
||||
).and_return(flexmock()).once()
|
||||
flexmock(module).should_receive('execute_hooks').and_raise(OSError).and_return(None)
|
||||
flexmock(module).should_receive('considered_soft_failure').and_return(False)
|
||||
@@ -388,7 +423,6 @@ def test_before_after_hooks_with_before_error_runs_after_hook_and_raises():
|
||||
umask=1234,
|
||||
working_directory='/working',
|
||||
dry_run=False,
|
||||
hook_name='myhook',
|
||||
action_names=['create'],
|
||||
context1='stuff',
|
||||
context2='such',
|
||||
@@ -404,14 +438,13 @@ def test_before_after_hooks_with_before_soft_failure_raises():
|
||||
flexmock(module).should_receive('filter_hooks').with_args(
|
||||
commands,
|
||||
before='action',
|
||||
hook_name='myhook',
|
||||
action_names=['create'],
|
||||
).and_return(flexmock()).once()
|
||||
flexmock(module).should_receive('filter_hooks').with_args(
|
||||
commands,
|
||||
after='action',
|
||||
hook_name='myhook',
|
||||
action_names=['create'],
|
||||
state_names=['finish'],
|
||||
).never()
|
||||
flexmock(module).should_receive('execute_hooks').and_raise(OSError)
|
||||
flexmock(module).should_receive('considered_soft_failure').and_return(True)
|
||||
@@ -423,7 +456,6 @@ def test_before_after_hooks_with_before_soft_failure_raises():
|
||||
umask=1234,
|
||||
working_directory='/working',
|
||||
dry_run=False,
|
||||
hook_name='myhook',
|
||||
action_names=['create'],
|
||||
context1='stuff',
|
||||
context2='such',
|
||||
@@ -431,6 +463,38 @@ def test_before_after_hooks_with_before_soft_failure_raises():
|
||||
pass
|
||||
|
||||
|
||||
def test_before_after_hooks_with_wrapped_code_error_runs_after_hook_and_raises():
|
||||
commands = [
|
||||
{'before': 'repository', 'run': ['foo', 'bar']},
|
||||
{'after': 'repository', 'run': ['baz']},
|
||||
]
|
||||
flexmock(module).should_receive('filter_hooks').with_args(
|
||||
commands,
|
||||
before='action',
|
||||
action_names=['create'],
|
||||
).and_return(flexmock()).once()
|
||||
flexmock(module).should_receive('filter_hooks').with_args(
|
||||
commands,
|
||||
after='action',
|
||||
action_names=['create'],
|
||||
state_names=['fail'],
|
||||
).and_return(flexmock()).once()
|
||||
flexmock(module).should_receive('execute_hooks').twice()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
with module.Before_after_hooks(
|
||||
command_hooks=commands,
|
||||
before_after='action',
|
||||
umask=1234,
|
||||
working_directory='/working',
|
||||
dry_run=False,
|
||||
action_names=['create'],
|
||||
context1='stuff',
|
||||
context2='such',
|
||||
):
|
||||
raise ValueError()
|
||||
|
||||
|
||||
def test_before_after_hooks_with_after_error_raises():
|
||||
commands = [
|
||||
{'before': 'repository', 'run': ['foo', 'bar']},
|
||||
@@ -439,14 +503,13 @@ def test_before_after_hooks_with_after_error_raises():
|
||||
flexmock(module).should_receive('filter_hooks').with_args(
|
||||
commands,
|
||||
before='action',
|
||||
hook_name='myhook',
|
||||
action_names=['create'],
|
||||
).and_return(flexmock()).once()
|
||||
flexmock(module).should_receive('filter_hooks').with_args(
|
||||
commands,
|
||||
after='action',
|
||||
hook_name='myhook',
|
||||
action_names=['create'],
|
||||
state_names=['finish'],
|
||||
).and_return(flexmock()).once()
|
||||
flexmock(module).should_receive('execute_hooks').and_return(None).and_raise(OSError)
|
||||
flexmock(module).should_receive('considered_soft_failure').and_return(False)
|
||||
@@ -458,7 +521,6 @@ def test_before_after_hooks_with_after_error_raises():
|
||||
umask=1234,
|
||||
working_directory='/working',
|
||||
dry_run=False,
|
||||
hook_name='myhook',
|
||||
action_names=['create'],
|
||||
context1='stuff',
|
||||
context2='such',
|
||||
@@ -474,14 +536,13 @@ def test_before_after_hooks_with_after_soft_failure_raises():
|
||||
flexmock(module).should_receive('filter_hooks').with_args(
|
||||
commands,
|
||||
before='action',
|
||||
hook_name='myhook',
|
||||
action_names=['create'],
|
||||
).and_return(flexmock()).once()
|
||||
flexmock(module).should_receive('filter_hooks').with_args(
|
||||
commands,
|
||||
after='action',
|
||||
hook_name='myhook',
|
||||
action_names=['create'],
|
||||
state_names=['finish'],
|
||||
).and_return(flexmock()).once()
|
||||
flexmock(module).should_receive('execute_hooks').and_return(None).and_raise(OSError)
|
||||
flexmock(module).should_receive('considered_soft_failure').and_return(True)
|
||||
@@ -493,7 +554,6 @@ def test_before_after_hooks_with_after_soft_failure_raises():
|
||||
umask=1234,
|
||||
working_directory='/working',
|
||||
dry_run=False,
|
||||
hook_name='myhook',
|
||||
action_names=['create'],
|
||||
context1='stuff',
|
||||
context2='such',
|
||||
|
||||
Reference in New Issue
Block a user