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 1.7.9.dev0
* #295: Add a SQLite database dump/restore hook. * #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. success or failure.
1.7.8 1.7.8

View file

@ -46,11 +46,12 @@ def parse_subparser_arguments(unparsed_arguments, subparsers):
if 'borg' in unparsed_arguments: if 'borg' in unparsed_arguments:
subparsers = {'borg': subparsers['borg']} subparsers = {'borg': subparsers['borg']}
for subparser_name, subparser in subparsers.items(): for argument in remaining_arguments:
if subparser_name not in remaining_arguments: canonical_name = alias_to_subparser_name.get(argument, argument)
continue 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 # 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 # remaining arguments. This prevents, for instance, "check --only extract" from triggering

View file

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

View file

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

View file

@ -1,3 +1,5 @@
import collections
from flexmock import flexmock from flexmock import flexmock
from borgmatic.commands import arguments as module from borgmatic.commands import arguments as module
@ -70,6 +72,26 @@ def test_parse_subparser_arguments_consumes_multiple_subparser_arguments():
assert remaining_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(): def test_parse_subparser_arguments_applies_default_subparsers():
prune_namespace = flexmock() prune_namespace = flexmock()
compact_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(): def test_load_configurations_collects_parsed_configurations_and_logs():
configuration = flexmock() configuration = flexmock()
other_configuration = flexmock() other_configuration = flexmock()