Run any command-line actions in the order specified instead of using a fixed ordering (#304).

This commit is contained in:
Dan Helfman 2023-03-08 13:19:41 -08:00
parent d88bcc8be9
commit 9db31bd1e9
6 changed files with 222 additions and 158 deletions

3
NEWS
View file

@ -1,6 +1,7 @@
1.7.9.dev0
* #295: Add a SQLite database dump/restore hook.
* #628: Add Healthchecks "log" state to send borgmatic logs to Healthchecks without signalling
* #304: Run any command-line actions in the order specified instead of using a fixed ordering.
* #628: Add a Healthchecks "log" state to send borgmatic logs to Healthchecks without signalling
success or failure.
1.7.8

View file

@ -46,11 +46,12 @@ def parse_subparser_arguments(unparsed_arguments, subparsers):
if 'borg' in unparsed_arguments:
subparsers = {'borg': subparsers['borg']}
for subparser_name, subparser in subparsers.items():
if subparser_name not in remaining_arguments:
continue
for argument in remaining_arguments:
canonical_name = alias_to_subparser_name.get(argument, argument)
subparser = subparsers.get(canonical_name)
canonical_name = alias_to_subparser_name.get(subparser_name, subparser_name)
if not subparser:
continue
# If a parsed value happens to be the same as the name of a subparser, remove it from the
# remaining arguments. This prevents, for instance, "check --only extract" from triggering

View file

@ -281,155 +281,162 @@ def run_actions(
**hook_context,
)
if 'rcreate' in arguments:
borgmatic.actions.rcreate.run_rcreate(
repository,
storage,
local_borg_version,
arguments['rcreate'],
global_arguments,
local_path,
remote_path,
)
if 'transfer' in arguments:
borgmatic.actions.transfer.run_transfer(
repository,
storage,
local_borg_version,
arguments['transfer'],
global_arguments,
local_path,
remote_path,
)
if 'prune' in arguments:
borgmatic.actions.prune.run_prune(
config_filename,
repository,
storage,
retention,
hooks,
hook_context,
local_borg_version,
arguments['prune'],
global_arguments,
dry_run_label,
local_path,
remote_path,
)
if 'compact' in arguments:
borgmatic.actions.compact.run_compact(
config_filename,
repository,
storage,
retention,
hooks,
hook_context,
local_borg_version,
arguments['compact'],
global_arguments,
dry_run_label,
local_path,
remote_path,
)
if 'create' in arguments:
yield from borgmatic.actions.create.run_create(
config_filename,
repository,
location,
storage,
hooks,
hook_context,
local_borg_version,
arguments['create'],
global_arguments,
dry_run_label,
local_path,
remote_path,
)
if 'check' in arguments and checks.repository_enabled_for_checks(repository, consistency):
borgmatic.actions.check.run_check(
config_filename,
repository,
location,
storage,
consistency,
hooks,
hook_context,
local_borg_version,
arguments['check'],
global_arguments,
local_path,
remote_path,
)
if 'extract' in arguments:
borgmatic.actions.extract.run_extract(
config_filename,
repository,
location,
storage,
hooks,
hook_context,
local_borg_version,
arguments['extract'],
global_arguments,
local_path,
remote_path,
)
if 'export-tar' in arguments:
borgmatic.actions.export_tar.run_export_tar(
repository,
storage,
local_borg_version,
arguments['export-tar'],
global_arguments,
local_path,
remote_path,
)
if 'mount' in arguments:
borgmatic.actions.mount.run_mount(
repository, storage, local_borg_version, arguments['mount'], local_path, remote_path,
)
if 'restore' in arguments:
borgmatic.actions.restore.run_restore(
repository,
location,
storage,
hooks,
local_borg_version,
arguments['restore'],
global_arguments,
local_path,
remote_path,
)
if 'rlist' in arguments:
yield from borgmatic.actions.rlist.run_rlist(
repository, storage, local_borg_version, arguments['rlist'], local_path, remote_path,
)
if 'list' in arguments:
yield from borgmatic.actions.list.run_list(
repository, storage, local_borg_version, arguments['list'], local_path, remote_path,
)
if 'rinfo' in arguments:
yield from borgmatic.actions.rinfo.run_rinfo(
repository, storage, local_borg_version, arguments['rinfo'], local_path, remote_path,
)
if 'info' in arguments:
yield from borgmatic.actions.info.run_info(
repository, storage, local_borg_version, arguments['info'], local_path, remote_path,
)
if 'break-lock' in arguments:
borgmatic.actions.break_lock.run_break_lock(
repository,
storage,
local_borg_version,
arguments['break-lock'],
local_path,
remote_path,
)
if 'borg' in arguments:
borgmatic.actions.borg.run_borg(
repository, storage, local_borg_version, arguments['borg'], local_path, remote_path,
)
for (action_name, action_arguments) in arguments.items():
if action_name == 'rcreate':
borgmatic.actions.rcreate.run_rcreate(
repository,
storage,
local_borg_version,
action_arguments,
global_arguments,
local_path,
remote_path,
)
elif action_name == 'transfer':
borgmatic.actions.transfer.run_transfer(
repository,
storage,
local_borg_version,
action_arguments,
global_arguments,
local_path,
remote_path,
)
elif action_name == 'prune':
borgmatic.actions.prune.run_prune(
config_filename,
repository,
storage,
retention,
hooks,
hook_context,
local_borg_version,
action_arguments,
global_arguments,
dry_run_label,
local_path,
remote_path,
)
elif action_name == 'compact':
borgmatic.actions.compact.run_compact(
config_filename,
repository,
storage,
retention,
hooks,
hook_context,
local_borg_version,
action_arguments,
global_arguments,
dry_run_label,
local_path,
remote_path,
)
elif action_name == 'create':
yield from borgmatic.actions.create.run_create(
config_filename,
repository,
location,
storage,
hooks,
hook_context,
local_borg_version,
action_arguments,
global_arguments,
dry_run_label,
local_path,
remote_path,
)
elif action_name == 'check':
if checks.repository_enabled_for_checks(repository, consistency):
borgmatic.actions.check.run_check(
config_filename,
repository,
location,
storage,
consistency,
hooks,
hook_context,
local_borg_version,
action_arguments,
global_arguments,
local_path,
remote_path,
)
elif action_name == 'extract':
borgmatic.actions.extract.run_extract(
config_filename,
repository,
location,
storage,
hooks,
hook_context,
local_borg_version,
action_arguments,
global_arguments,
local_path,
remote_path,
)
elif action_name == 'export-tar':
borgmatic.actions.export_tar.run_export_tar(
repository,
storage,
local_borg_version,
action_arguments,
global_arguments,
local_path,
remote_path,
)
elif action_name == 'mount':
borgmatic.actions.mount.run_mount(
repository,
storage,
local_borg_version,
arguments['mount'],
local_path,
remote_path,
)
elif action_name == 'restore':
borgmatic.actions.restore.run_restore(
repository,
location,
storage,
hooks,
local_borg_version,
action_arguments,
global_arguments,
local_path,
remote_path,
)
elif action_name == 'rlist':
yield from borgmatic.actions.rlist.run_rlist(
repository, storage, local_borg_version, action_arguments, local_path, remote_path,
)
elif action_name == 'list':
yield from borgmatic.actions.list.run_list(
repository, storage, local_borg_version, action_arguments, local_path, remote_path,
)
elif action_name == 'rinfo':
yield from borgmatic.actions.rinfo.run_rinfo(
repository, storage, local_borg_version, action_arguments, local_path, remote_path,
)
elif action_name == 'info':
yield from borgmatic.actions.info.run_info(
repository, storage, local_borg_version, action_arguments, local_path, remote_path,
)
elif action_name == 'break-lock':
borgmatic.actions.break_lock.run_break_lock(
repository,
storage,
local_borg_version,
arguments['break-lock'],
local_path,
remote_path,
)
elif action_name == 'borg':
borgmatic.actions.borg.run_borg(
repository, storage, local_borg_version, action_arguments, local_path, remote_path,
)
command.execute_hook(
hooks.get('after_actions'),

View file

@ -36,10 +36,16 @@ skipping certain actions while running others. For instance, this skips
borgmatic create check
```
Or, you can make backups with `create` on a frequent schedule (e.g. with
`borgmatic create` called from one cron job), while only running expensive
consistency checks with `check` on a much less frequent basis (e.g. with
`borgmatic check` called from a separate cron job).
<span class="minilink minilink-addedin">New in version 1.7.9</span> borgmatic
now respects your specified command-line action order, running actions in the
order you specify. In previous versions, borgmatic ran your specified actions
in a fixed ordering regardless of the order they appeared on the command-line.
But instead of running actions together, another option is to run backups with
`create` on a frequent schedule (e.g. with `borgmatic create` called from one
cron job), while only running expensive consistency checks with `check` on a
much less frequent basis (e.g. with `borgmatic check` called from a separate
cron job).
### Consistency check configuration

View file

@ -1,3 +1,5 @@
import collections
from flexmock import flexmock
from borgmatic.commands import arguments as module
@ -70,6 +72,26 @@ def test_parse_subparser_arguments_consumes_multiple_subparser_arguments():
assert remaining_arguments == []
def test_parse_subparser_arguments_respects_command_line_action_ordering():
other_namespace = flexmock()
action_namespace = flexmock(foo=True)
subparsers = {
'action': flexmock(
parse_known_args=lambda arguments: (action_namespace, ['action', '--foo', 'true'])
),
'other': flexmock(parse_known_args=lambda arguments: (other_namespace, ['other'])),
}
arguments, remaining_arguments = module.parse_subparser_arguments(
('other', '--foo', 'true', 'action'), subparsers
)
assert arguments == collections.OrderedDict(
[('other', other_namespace), ('action', action_namespace)]
)
assert remaining_arguments == []
def test_parse_subparser_arguments_applies_default_subparsers():
prune_namespace = flexmock()
compact_namespace = flexmock()

View file

@ -778,6 +778,33 @@ def test_run_actions_runs_borg():
)
def test_run_actions_runs_multiple_actions_in_argument_order():
flexmock(module).should_receive('add_custom_log_levels')
flexmock(module.command).should_receive('execute_hook')
flexmock(borgmatic.actions.borg).should_receive('run_borg').once().ordered()
flexmock(borgmatic.actions.restore).should_receive('run_restore').once().ordered()
tuple(
module.run_actions(
arguments={
'global': flexmock(dry_run=False),
'borg': flexmock(),
'restore': flexmock(),
},
config_filename=flexmock(),
location={'repositories': []},
storage=flexmock(),
retention=flexmock(),
consistency=flexmock(),
hooks={},
local_path=flexmock(),
remote_path=flexmock(),
local_borg_version=flexmock(),
repository_path='repo',
)
)
def test_load_configurations_collects_parsed_configurations_and_logs():
configuration = flexmock()
other_configuration = flexmock()