Run any command-line actions in the order specified instead of using a fixed ordering (#304).
This commit is contained in:
parent
d88bcc8be9
commit
9db31bd1e9
6 changed files with 222 additions and 158 deletions
3
NEWS
3
NEWS
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue