Add a "skip_actions" option to skip running particular actions (#701).
This commit is contained in:
parent
c3efe1b90e
commit
ef448e2dd1
10 changed files with 190 additions and 37 deletions
5
NEWS
5
NEWS
|
@ -1,4 +1,9 @@
|
|||
1.8.5.dev0
|
||||
* #701: Add a "skip_actions" option to skip running particular actions, handy for append-only or
|
||||
checkless configurations. See the documentation for more information:
|
||||
https://torsion.org/borgmatic/docs/how-to/set-up-backups/#skipping-actions
|
||||
* #701: Deprecate the "disabled" value for the "checks" option in favor of the new "skip_actions"
|
||||
option.
|
||||
* #779: Add a "--match-archives" flag to the "check" action for selecting the archives to check,
|
||||
overriding the existing "archive_name_format" and "match_archives" options in configuration.
|
||||
* #779: Only parse "--override" values as complex data types when they're for options of those
|
||||
|
|
|
@ -39,7 +39,11 @@ def parse_checks(config, only_checks=None):
|
|||
check_config['name'] for check_config in (config.get('checks', None) or DEFAULT_CHECKS)
|
||||
)
|
||||
checks = tuple(check.lower() for check in checks)
|
||||
|
||||
if 'disabled' in checks:
|
||||
logger.warning(
|
||||
'The "disabled" value for the "checks" option is deprecated and will be removed from a future release; use "skip_actions" instead'
|
||||
)
|
||||
if len(checks) > 1:
|
||||
logger.warning(
|
||||
'Multiple checks are configured, but one of them is "disabled"; not running any checks'
|
||||
|
@ -119,6 +123,9 @@ def filter_checks_on_frequency(
|
|||
|
||||
Raise ValueError if a frequency cannot be parsed.
|
||||
'''
|
||||
if not checks:
|
||||
return checks
|
||||
|
||||
filtered_checks = list(checks)
|
||||
|
||||
if force:
|
||||
|
|
|
@ -70,6 +70,12 @@ def run_configuration(config_filename, config, arguments):
|
|||
using_primary_action = {'create', 'prune', 'compact', 'check'}.intersection(arguments)
|
||||
monitoring_log_level = verbosity_to_log_level(global_arguments.monitoring_verbosity)
|
||||
monitoring_hooks_are_activated = using_primary_action and monitoring_log_level != DISABLED
|
||||
skip_actions = config.get('skip_actions')
|
||||
|
||||
if skip_actions:
|
||||
logger.debug(
|
||||
f"{config_filename}: Skipping {'/'.join(skip_actions)} action{'s' if len(skip_actions) > 1 else ''} due to configured skip_actions"
|
||||
)
|
||||
|
||||
try:
|
||||
local_borg_version = borg_version.local_borg_version(config, local_path)
|
||||
|
@ -274,6 +280,7 @@ def run_actions(
|
|||
'repositories': ','.join([repo['path'] for repo in config['repositories']]),
|
||||
'log_file': global_arguments.log_file if global_arguments.log_file else '',
|
||||
}
|
||||
skip_actions = set(config.get('skip_actions', {}))
|
||||
|
||||
command.execute_hook(
|
||||
config.get('before_actions'),
|
||||
|
@ -285,7 +292,7 @@ def run_actions(
|
|||
)
|
||||
|
||||
for action_name, action_arguments in arguments.items():
|
||||
if action_name == 'rcreate':
|
||||
if action_name == 'rcreate' and action_name not in skip_actions:
|
||||
borgmatic.actions.rcreate.run_rcreate(
|
||||
repository,
|
||||
config,
|
||||
|
@ -295,7 +302,7 @@ def run_actions(
|
|||
local_path,
|
||||
remote_path,
|
||||
)
|
||||
elif action_name == 'transfer':
|
||||
elif action_name == 'transfer' and action_name not in skip_actions:
|
||||
borgmatic.actions.transfer.run_transfer(
|
||||
repository,
|
||||
config,
|
||||
|
@ -305,7 +312,7 @@ def run_actions(
|
|||
local_path,
|
||||
remote_path,
|
||||
)
|
||||
elif action_name == 'create':
|
||||
elif action_name == 'create' and action_name not in skip_actions:
|
||||
yield from borgmatic.actions.create.run_create(
|
||||
config_filename,
|
||||
repository,
|
||||
|
@ -318,7 +325,7 @@ def run_actions(
|
|||
local_path,
|
||||
remote_path,
|
||||
)
|
||||
elif action_name == 'prune':
|
||||
elif action_name == 'prune' and action_name not in skip_actions:
|
||||
borgmatic.actions.prune.run_prune(
|
||||
config_filename,
|
||||
repository,
|
||||
|
@ -331,7 +338,7 @@ def run_actions(
|
|||
local_path,
|
||||
remote_path,
|
||||
)
|
||||
elif action_name == 'compact':
|
||||
elif action_name == 'compact' and action_name not in skip_actions:
|
||||
borgmatic.actions.compact.run_compact(
|
||||
config_filename,
|
||||
repository,
|
||||
|
@ -344,7 +351,7 @@ def run_actions(
|
|||
local_path,
|
||||
remote_path,
|
||||
)
|
||||
elif action_name == 'check':
|
||||
elif action_name == 'check' and action_name not in skip_actions:
|
||||
if checks.repository_enabled_for_checks(repository, config):
|
||||
borgmatic.actions.check.run_check(
|
||||
config_filename,
|
||||
|
@ -357,7 +364,7 @@ def run_actions(
|
|||
local_path,
|
||||
remote_path,
|
||||
)
|
||||
elif action_name == 'extract':
|
||||
elif action_name == 'extract' and action_name not in skip_actions:
|
||||
borgmatic.actions.extract.run_extract(
|
||||
config_filename,
|
||||
repository,
|
||||
|
@ -369,7 +376,7 @@ def run_actions(
|
|||
local_path,
|
||||
remote_path,
|
||||
)
|
||||
elif action_name == 'export-tar':
|
||||
elif action_name == 'export-tar' and action_name not in skip_actions:
|
||||
borgmatic.actions.export_tar.run_export_tar(
|
||||
repository,
|
||||
config,
|
||||
|
@ -379,7 +386,7 @@ def run_actions(
|
|||
local_path,
|
||||
remote_path,
|
||||
)
|
||||
elif action_name == 'mount':
|
||||
elif action_name == 'mount' and action_name not in skip_actions:
|
||||
borgmatic.actions.mount.run_mount(
|
||||
repository,
|
||||
config,
|
||||
|
@ -389,7 +396,7 @@ def run_actions(
|
|||
local_path,
|
||||
remote_path,
|
||||
)
|
||||
elif action_name == 'restore':
|
||||
elif action_name == 'restore' and action_name not in skip_actions:
|
||||
borgmatic.actions.restore.run_restore(
|
||||
repository,
|
||||
config,
|
||||
|
@ -399,7 +406,7 @@ def run_actions(
|
|||
local_path,
|
||||
remote_path,
|
||||
)
|
||||
elif action_name == 'rlist':
|
||||
elif action_name == 'rlist' and action_name not in skip_actions:
|
||||
yield from borgmatic.actions.rlist.run_rlist(
|
||||
repository,
|
||||
config,
|
||||
|
@ -409,7 +416,7 @@ def run_actions(
|
|||
local_path,
|
||||
remote_path,
|
||||
)
|
||||
elif action_name == 'list':
|
||||
elif action_name == 'list' and action_name not in skip_actions:
|
||||
yield from borgmatic.actions.list.run_list(
|
||||
repository,
|
||||
config,
|
||||
|
@ -419,7 +426,7 @@ def run_actions(
|
|||
local_path,
|
||||
remote_path,
|
||||
)
|
||||
elif action_name == 'rinfo':
|
||||
elif action_name == 'rinfo' and action_name not in skip_actions:
|
||||
yield from borgmatic.actions.rinfo.run_rinfo(
|
||||
repository,
|
||||
config,
|
||||
|
@ -429,7 +436,7 @@ def run_actions(
|
|||
local_path,
|
||||
remote_path,
|
||||
)
|
||||
elif action_name == 'info':
|
||||
elif action_name == 'info' and action_name not in skip_actions:
|
||||
yield from borgmatic.actions.info.run_info(
|
||||
repository,
|
||||
config,
|
||||
|
@ -439,7 +446,7 @@ def run_actions(
|
|||
local_path,
|
||||
remote_path,
|
||||
)
|
||||
elif action_name == 'break-lock':
|
||||
elif action_name == 'break-lock' and action_name not in skip_actions:
|
||||
borgmatic.actions.break_lock.run_break_lock(
|
||||
repository,
|
||||
config,
|
||||
|
@ -449,7 +456,7 @@ def run_actions(
|
|||
local_path,
|
||||
remote_path,
|
||||
)
|
||||
elif action_name == 'export':
|
||||
elif action_name == 'export' and action_name not in skip_actions:
|
||||
borgmatic.actions.export_key.run_export_key(
|
||||
repository,
|
||||
config,
|
||||
|
@ -459,7 +466,7 @@ def run_actions(
|
|||
local_path,
|
||||
remote_path,
|
||||
)
|
||||
elif action_name == 'borg':
|
||||
elif action_name == 'borg' and action_name not in skip_actions:
|
||||
borgmatic.actions.borg.run_borg(
|
||||
repository,
|
||||
config,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
def repository_enabled_for_checks(repository, consistency):
|
||||
def repository_enabled_for_checks(repository, config):
|
||||
'''
|
||||
Given a repository name and a consistency configuration dict, return whether the repository
|
||||
is enabled to have consistency checks run.
|
||||
Given a repository name and a configuration dict, return whether the
|
||||
repository is enabled to have consistency checks run.
|
||||
'''
|
||||
if not consistency.get('check_repositories'):
|
||||
if not config.get('check_repositories'):
|
||||
return True
|
||||
|
||||
return repository in consistency['check_repositories']
|
||||
return repository in config['check_repositories']
|
||||
|
|
|
@ -423,7 +423,9 @@ properties:
|
|||
command-line invocation.
|
||||
keep_within:
|
||||
type: string
|
||||
description: Keep all archives within this time interval.
|
||||
description: |
|
||||
Keep all archives within this time interval. See "skip_actions" for
|
||||
disabling pruning altogether.
|
||||
example: 3H
|
||||
keep_secondly:
|
||||
type: integer
|
||||
|
@ -479,13 +481,13 @@ properties:
|
|||
- disabled
|
||||
description: |
|
||||
Name of consistency check to run: "repository",
|
||||
"archives", "data", and/or "extract". Set to "disabled"
|
||||
to disable all consistency checks. "repository" checks
|
||||
the consistency of the repository, "archives" checks all
|
||||
of the archives, "data" verifies the integrity of the
|
||||
data within the archives, and "extract" does an
|
||||
extraction dry-run of the most recent archive. Note that
|
||||
"data" implies "archives".
|
||||
"archives", "data", and/or "extract". "repository"
|
||||
checks the consistency of the repository, "archives"
|
||||
checks all of the archives, "data" verifies the
|
||||
integrity of the data within the archives, and "extract"
|
||||
does an extraction dry-run of the most recent archive.
|
||||
Note that "data" implies "archives". See "skip_actions"
|
||||
for disabling checks altogether.
|
||||
example: repository
|
||||
frequency:
|
||||
type: string
|
||||
|
@ -525,6 +527,18 @@ properties:
|
|||
Apply color to console output. Can be overridden with --no-color
|
||||
command-line flag. Defaults to true.
|
||||
example: false
|
||||
skip_actions:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: |
|
||||
List of one or more actions to skip running for this configuration
|
||||
file, even if specified on the command-line (explicitly or
|
||||
implicitly). This is handy for append-only configurations where you
|
||||
never want to run "compact" or checkless configuration where you
|
||||
want to skip "check". Defaults to not skipping any actions.
|
||||
example:
|
||||
- compact
|
||||
before_actions:
|
||||
type: array
|
||||
items:
|
||||
|
|
|
@ -162,7 +162,16 @@ location:
|
|||
If that's still too slow, you can disable consistency checks entirely,
|
||||
either for a single repository or for all repositories.
|
||||
|
||||
Disabling all consistency checks looks like this:
|
||||
<span class="minilink minilink-addedin">New in version 1.8.5</span> Disabling
|
||||
all consistency checks looks like this:
|
||||
|
||||
```yaml
|
||||
skip_actions:
|
||||
- check
|
||||
```
|
||||
|
||||
<span class="minilink minilink-addedin">Prior to version 1.8.5</span> Use this
|
||||
configuration instead:
|
||||
|
||||
```yaml
|
||||
checks:
|
||||
|
@ -170,10 +179,10 @@ checks:
|
|||
```
|
||||
|
||||
<span class="minilink minilink-addedin">Prior to version 1.8.0</span> Put
|
||||
this option in the `consistency:` section of your configuration.
|
||||
`checks:` in the `consistency:` section of your configuration.
|
||||
|
||||
<span class="minilink minilink-addedin">Prior to version 1.6.2</span> `checks`
|
||||
was a plain list of strings without the `name:` part. For instance:
|
||||
<span class="minilink minilink-addedin">Prior to version 1.6.2</span>
|
||||
`checks:` was a plain list of strings without the `name:` part. For instance:
|
||||
|
||||
```yaml
|
||||
checks:
|
||||
|
|
|
@ -282,6 +282,21 @@ due to things like file damage. For instance:
|
|||
sudo borgmatic --verbosity 1 --list --stats
|
||||
```
|
||||
|
||||
### Skipping actions
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 1.8.5</span> You can
|
||||
configure borgmatic to skip running certain actions (default or otherwise).
|
||||
For instance, to always skip the `compact` action when using [Borg's
|
||||
append-only
|
||||
mode](https://borgbackup.readthedocs.io/en/stable/usage/notes.html#append-only-mode-forbid-compaction),
|
||||
set the `skip_actions` option:
|
||||
|
||||
```
|
||||
skip_actions:
|
||||
- compact
|
||||
```
|
||||
|
||||
|
||||
## Autopilot
|
||||
|
||||
Running backups manually is good for validating your configuration, but I'm
|
||||
|
|
|
@ -193,6 +193,19 @@ def test_filter_checks_on_frequency_restains_check_with_unelapsed_frequency_and_
|
|||
) == ('archives',)
|
||||
|
||||
|
||||
def test_filter_checks_on_frequency_passes_through_empty_checks():
|
||||
assert (
|
||||
module.filter_checks_on_frequency(
|
||||
config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
|
||||
borg_repository_id='repo',
|
||||
checks=(),
|
||||
force=False,
|
||||
archives_check_id='1234',
|
||||
)
|
||||
== ()
|
||||
)
|
||||
|
||||
|
||||
def test_make_archive_filter_flags_with_default_checks_and_prefix_returns_default_flags():
|
||||
flexmock(module.feature).should_receive('available').and_return(True)
|
||||
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
|
||||
|
|
|
@ -23,6 +23,16 @@ def test_run_configuration_runs_actions_for_each_repository():
|
|||
assert results == expected_results
|
||||
|
||||
|
||||
def test_run_configuration_with_skip_actions_does_not_raise():
|
||||
flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module).should_receive('run_actions').and_return(flexmock()).and_return(flexmock())
|
||||
config = {'repositories': [{'path': 'foo'}, {'path': 'bar'}], 'skip_actions': ['compact']}
|
||||
arguments = {'global': flexmock(monitoring_verbosity=1)}
|
||||
|
||||
list(module.run_configuration('test.yaml', config, arguments))
|
||||
|
||||
|
||||
def test_run_configuration_with_invalid_borg_version_errors():
|
||||
flexmock(module).should_receive('verbosity_to_log_level').and_return(logging.INFO)
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_raise(ValueError)
|
||||
|
@ -504,6 +514,24 @@ def test_run_actions_runs_create():
|
|||
assert result == (expected,)
|
||||
|
||||
|
||||
def test_run_actions_with_skip_actions_skips_create():
|
||||
flexmock(module).should_receive('add_custom_log_levels')
|
||||
flexmock(module.command).should_receive('execute_hook')
|
||||
flexmock(borgmatic.actions.create).should_receive('run_create').never()
|
||||
|
||||
tuple(
|
||||
module.run_actions(
|
||||
arguments={'global': flexmock(dry_run=False, log_file='foo'), 'create': flexmock()},
|
||||
config_filename=flexmock(),
|
||||
config={'repositories': [], 'skip_actions': ['create']},
|
||||
local_path=flexmock(),
|
||||
remote_path=flexmock(),
|
||||
local_borg_version=flexmock(),
|
||||
repository={'path': 'repo'},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_run_actions_runs_prune():
|
||||
flexmock(module).should_receive('add_custom_log_levels')
|
||||
flexmock(module.command).should_receive('execute_hook')
|
||||
|
@ -522,6 +550,24 @@ def test_run_actions_runs_prune():
|
|||
)
|
||||
|
||||
|
||||
def test_run_actions_with_skip_actions_skips_prune():
|
||||
flexmock(module).should_receive('add_custom_log_levels')
|
||||
flexmock(module.command).should_receive('execute_hook')
|
||||
flexmock(borgmatic.actions.prune).should_receive('run_prune').never()
|
||||
|
||||
tuple(
|
||||
module.run_actions(
|
||||
arguments={'global': flexmock(dry_run=False, log_file='foo'), 'prune': flexmock()},
|
||||
config_filename=flexmock(),
|
||||
config={'repositories': [], 'skip_actions': ['prune']},
|
||||
local_path=flexmock(),
|
||||
remote_path=flexmock(),
|
||||
local_borg_version=flexmock(),
|
||||
repository={'path': 'repo'},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_run_actions_runs_compact():
|
||||
flexmock(module).should_receive('add_custom_log_levels')
|
||||
flexmock(module.command).should_receive('execute_hook')
|
||||
|
@ -540,6 +586,24 @@ def test_run_actions_runs_compact():
|
|||
)
|
||||
|
||||
|
||||
def test_run_actions_with_skip_actions_skips_compact():
|
||||
flexmock(module).should_receive('add_custom_log_levels')
|
||||
flexmock(module.command).should_receive('execute_hook')
|
||||
flexmock(borgmatic.actions.compact).should_receive('run_compact').never()
|
||||
|
||||
tuple(
|
||||
module.run_actions(
|
||||
arguments={'global': flexmock(dry_run=False, log_file='foo'), 'compact': flexmock()},
|
||||
config_filename=flexmock(),
|
||||
config={'repositories': [], 'skip_actions': ['compact']},
|
||||
local_path=flexmock(),
|
||||
remote_path=flexmock(),
|
||||
local_borg_version=flexmock(),
|
||||
repository={'path': 'repo'},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_run_actions_runs_check_when_repository_enabled_for_checks():
|
||||
flexmock(module).should_receive('add_custom_log_levels')
|
||||
flexmock(module.command).should_receive('execute_hook')
|
||||
|
@ -578,6 +642,25 @@ def test_run_actions_skips_check_when_repository_not_enabled_for_checks():
|
|||
)
|
||||
|
||||
|
||||
def test_run_actions_with_skip_actions_skips_check():
|
||||
flexmock(module).should_receive('add_custom_log_levels')
|
||||
flexmock(module.command).should_receive('execute_hook')
|
||||
flexmock(module.checks).should_receive('repository_enabled_for_checks').and_return(True)
|
||||
flexmock(borgmatic.actions.check).should_receive('run_check').never()
|
||||
|
||||
tuple(
|
||||
module.run_actions(
|
||||
arguments={'global': flexmock(dry_run=False, log_file='foo'), 'check': flexmock()},
|
||||
config_filename=flexmock(),
|
||||
config={'repositories': [], 'skip_actions': ['check']},
|
||||
local_path=flexmock(),
|
||||
remote_path=flexmock(),
|
||||
local_borg_version=flexmock(),
|
||||
repository={'path': 'repo'},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_run_actions_runs_extract():
|
||||
flexmock(module).should_receive('add_custom_log_levels')
|
||||
flexmock(module.command).should_receive('execute_hook')
|
||||
|
|
|
@ -2,14 +2,14 @@ from borgmatic.config import checks as module
|
|||
|
||||
|
||||
def test_repository_enabled_for_checks_defaults_to_enabled_for_all_repositories():
|
||||
enabled = module.repository_enabled_for_checks('repo.borg', consistency={})
|
||||
enabled = module.repository_enabled_for_checks('repo.borg', config={})
|
||||
|
||||
assert enabled
|
||||
|
||||
|
||||
def test_repository_enabled_for_checks_is_enabled_for_specified_repositories():
|
||||
enabled = module.repository_enabled_for_checks(
|
||||
'repo.borg', consistency={'check_repositories': ['repo.borg', 'other.borg']}
|
||||
'repo.borg', config={'check_repositories': ['repo.borg', 'other.borg']}
|
||||
)
|
||||
|
||||
assert enabled
|
||||
|
@ -17,7 +17,7 @@ def test_repository_enabled_for_checks_is_enabled_for_specified_repositories():
|
|||
|
||||
def test_repository_enabled_for_checks_is_disabled_for_other_repositories():
|
||||
enabled = module.repository_enabled_for_checks(
|
||||
'repo.borg', consistency={'check_repositories': ['other.borg']}
|
||||
'repo.borg', config={'check_repositories': ['other.borg']}
|
||||
)
|
||||
|
||||
assert not enabled
|
||||
|
|
Loading…
Reference in a new issue