Run "compact" action by default when no actions are specified (#394).
This commit is contained in:
parent
4498671233
commit
b525e70e1c
17 changed files with 239 additions and 77 deletions
4
NEWS
4
NEWS
|
@ -1,5 +1,7 @@
|
|||
1.5.23.dev0
|
||||
* #394: Compact repository segments with new "borgmatic compact" action. Borg 1.2+ only.
|
||||
* #394: Compact repository segments and free space with new "borgmatic compact" action. Borg 1.2+
|
||||
only. Also run "compact" by default when no actions are specified, as "prune" in Borg 1.2 no
|
||||
longer frees up space unless "compact" is run.
|
||||
* #480, #482: Fix traceback when a YAML validation error occurs.
|
||||
|
||||
1.5.22
|
||||
|
|
|
@ -38,4 +38,4 @@ def compact_segments(
|
|||
+ (repository,)
|
||||
)
|
||||
|
||||
execute_command(full_command, output_log_level=logging.WARNING, borg_local_path=local_path)
|
||||
execute_command(full_command, output_log_level=logging.INFO, borg_local_path=local_path)
|
||||
|
|
20
borgmatic/borg/feature.py
Normal file
20
borgmatic/borg/feature.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from enum import Enum
|
||||
|
||||
from pkg_resources import parse_version
|
||||
|
||||
|
||||
class Feature(Enum):
|
||||
COMPACT = 1
|
||||
|
||||
|
||||
FEATURE_TO_MINIMUM_BORG_VERSION = {
|
||||
Feature.COMPACT: parse_version('1.2.0a2'),
|
||||
}
|
||||
|
||||
|
||||
def available(feature, borg_version):
|
||||
'''
|
||||
Given a Borg Feature constant and a Borg version string, return whether that feature is
|
||||
available in that version of Borg.
|
||||
'''
|
||||
return FEATURE_TO_MINIMUM_BORG_VERSION[feature] <= parse_version(borg_version)
|
25
borgmatic/borg/version.py
Normal file
25
borgmatic/borg/version.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
import logging
|
||||
|
||||
from borgmatic.execute import execute_command
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def local_borg_version(local_path='borg'):
|
||||
'''
|
||||
Given a local Borg binary path, return a version string for it.
|
||||
|
||||
Raise OSError or CalledProcessError if there is a problem running Borg.
|
||||
Raise ValueError if the version cannot be parsed.
|
||||
'''
|
||||
full_command = (
|
||||
(local_path, '--version')
|
||||
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
|
||||
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
|
||||
)
|
||||
output = execute_command(full_command, output_log_level=None, borg_local_path=local_path)
|
||||
|
||||
try:
|
||||
return output.split(' ')[1].strip()
|
||||
except IndexError:
|
||||
raise ValueError('Could not parse Borg version string')
|
|
@ -63,9 +63,9 @@ def parse_subparser_arguments(unparsed_arguments, subparsers):
|
|||
|
||||
arguments[canonical_name] = parsed
|
||||
|
||||
# If no actions are explicitly requested, assume defaults: prune, create, and check.
|
||||
# If no actions are explicitly requested, assume defaults: prune, compact, create, and check.
|
||||
if not arguments and '--help' not in unparsed_arguments and '-h' not in unparsed_arguments:
|
||||
for subparser_name in ('prune', 'create', 'check'):
|
||||
for subparser_name in ('prune', 'compact', 'create', 'check'):
|
||||
subparser = subparsers[subparser_name]
|
||||
parsed, unused_remaining = subparser.parse_known_args(unparsed_arguments)
|
||||
arguments[subparser_name] = parsed
|
||||
|
@ -200,8 +200,8 @@ def parse_arguments(*unparsed_arguments):
|
|||
top_level_parser = ArgumentParser(
|
||||
description='''
|
||||
Simple, configuration-driven backup software for servers and workstations. If none of
|
||||
the action options are given, then borgmatic defaults to: prune, create, and check
|
||||
archives.
|
||||
the action options are given, then borgmatic defaults to: prune, compact, create, and
|
||||
check.
|
||||
''',
|
||||
parents=[global_parser],
|
||||
)
|
||||
|
@ -209,7 +209,7 @@ def parse_arguments(*unparsed_arguments):
|
|||
subparsers = top_level_parser.add_subparsers(
|
||||
title='actions',
|
||||
metavar='',
|
||||
help='Specify zero or more actions. Defaults to prune, create, and check. Use --help with action for details:',
|
||||
help='Specify zero or more actions. Defaults to prune, compact, create, and check. Use --help with action for details:',
|
||||
)
|
||||
init_parser = subparsers.add_parser(
|
||||
'init',
|
||||
|
@ -242,8 +242,8 @@ def parse_arguments(*unparsed_arguments):
|
|||
prune_parser = subparsers.add_parser(
|
||||
'prune',
|
||||
aliases=SUBPARSER_ALIASES['prune'],
|
||||
help='Prune archives according to the retention policy',
|
||||
description='Prune archives according to the retention policy',
|
||||
help='Prune archives according to the retention policy (with Borg 1.2+, run compact afterwards to actually free space)',
|
||||
description='Prune archives according to the retention policy (with Borg 1.2+, run compact afterwards to actually free space)',
|
||||
add_help=False,
|
||||
)
|
||||
prune_group = prune_parser.add_argument_group('prune arguments')
|
||||
|
|
|
@ -18,12 +18,14 @@ from borgmatic.borg import create as borg_create
|
|||
from borgmatic.borg import environment as borg_environment
|
||||
from borgmatic.borg import export_tar as borg_export_tar
|
||||
from borgmatic.borg import extract as borg_extract
|
||||
from borgmatic.borg import feature as borg_feature
|
||||
from borgmatic.borg import info as borg_info
|
||||
from borgmatic.borg import init as borg_init
|
||||
from borgmatic.borg import list as borg_list
|
||||
from borgmatic.borg import mount as borg_mount
|
||||
from borgmatic.borg import prune as borg_prune
|
||||
from borgmatic.borg import umount as borg_umount
|
||||
from borgmatic.borg import version as borg_version
|
||||
from borgmatic.commands.arguments import parse_arguments
|
||||
from borgmatic.config import checks, collect, convert, validate
|
||||
from borgmatic.hooks import command, dispatch, dump, monitor
|
||||
|
@ -39,8 +41,8 @@ LEGACY_CONFIG_PATH = '/etc/borgmatic/config'
|
|||
def run_configuration(config_filename, config, arguments):
|
||||
'''
|
||||
Given a config filename, the corresponding parsed config dict, and command-line arguments as a
|
||||
dict from subparser name to a namespace of parsed arguments, execute its defined pruning,
|
||||
backups, consistency checks, and/or other actions.
|
||||
dict from subparser name to a namespace of parsed arguments, execute the defined prune, compact,
|
||||
create, check, and/or other actions.
|
||||
|
||||
Yield a combination of:
|
||||
|
||||
|
@ -60,11 +62,19 @@ def run_configuration(config_filename, config, arguments):
|
|||
borg_environment.initialize(storage)
|
||||
encountered_error = None
|
||||
error_repository = ''
|
||||
prune_create_or_check = {'prune', 'create', 'check'}.intersection(arguments)
|
||||
using_primary_action = {'prune', 'compact', 'create', 'check'}.intersection(arguments)
|
||||
monitoring_log_level = verbosity_to_log_level(global_arguments.monitoring_verbosity)
|
||||
|
||||
try:
|
||||
if prune_create_or_check:
|
||||
local_borg_version = borg_version.local_borg_version(local_path)
|
||||
except (OSError, CalledProcessError, ValueError) as error:
|
||||
yield from make_error_log_records(
|
||||
'{}: Error getting local Borg version'.format(config_filename), error
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
if using_primary_action:
|
||||
dispatch.call_hooks(
|
||||
'initialize_monitor',
|
||||
hooks,
|
||||
|
@ -113,7 +123,7 @@ def run_configuration(config_filename, config, arguments):
|
|||
'pre-extract',
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
if prune_create_or_check:
|
||||
if using_primary_action:
|
||||
dispatch.call_hooks(
|
||||
'ping_monitor',
|
||||
hooks,
|
||||
|
@ -153,6 +163,7 @@ def run_configuration(config_filename, config, arguments):
|
|||
hooks=hooks,
|
||||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
local_borg_version=local_borg_version,
|
||||
repository_path=repository_path,
|
||||
)
|
||||
except (OSError, CalledProcessError, ValueError) as error:
|
||||
|
@ -218,7 +229,7 @@ def run_configuration(config_filename, config, arguments):
|
|||
'post-extract',
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
if prune_create_or_check:
|
||||
if using_primary_action:
|
||||
dispatch.call_hooks(
|
||||
'ping_monitor',
|
||||
hooks,
|
||||
|
@ -245,7 +256,7 @@ def run_configuration(config_filename, config, arguments):
|
|||
'{}: Error running post hook'.format(config_filename), error
|
||||
)
|
||||
|
||||
if encountered_error and prune_create_or_check:
|
||||
if encountered_error and using_primary_action:
|
||||
try:
|
||||
command.execute_hook(
|
||||
hooks.get('on_error'),
|
||||
|
@ -293,12 +304,13 @@ def run_actions(
|
|||
hooks,
|
||||
local_path,
|
||||
remote_path,
|
||||
local_borg_version,
|
||||
repository_path,
|
||||
): # pragma: no cover
|
||||
'''
|
||||
Given parsed command-line arguments as an argparse.ArgumentParser instance, several different
|
||||
configuration dicts, local and remote paths to Borg, and a repository name, run all actions
|
||||
from the command-line arguments on the given repository.
|
||||
configuration dicts, local and remote paths to Borg, a local Borg version string, and a
|
||||
repository name, run all actions from the command-line arguments on the given repository.
|
||||
|
||||
Yield JSON output strings from executing any actions that produce JSON.
|
||||
|
||||
|
@ -332,17 +344,22 @@ def run_actions(
|
|||
files=arguments['prune'].files,
|
||||
)
|
||||
if 'compact' in arguments:
|
||||
logger.info('{}: Compacting segments{}'.format(repository, dry_run_label))
|
||||
borg_compact.compact_segments(
|
||||
global_arguments.dry_run,
|
||||
repository,
|
||||
storage,
|
||||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
progress=arguments['compact'].progress,
|
||||
cleanup_commits=arguments['compact'].cleanup_commits,
|
||||
threshold=arguments['compact'].threshold,
|
||||
)
|
||||
if borg_feature.available(borg_feature.Feature.COMPACT, local_borg_version):
|
||||
logger.info('{}: Compacting segments{}'.format(repository, dry_run_label))
|
||||
borg_compact.compact_segments(
|
||||
global_arguments.dry_run,
|
||||
repository,
|
||||
storage,
|
||||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
progress=arguments['compact'].progress,
|
||||
cleanup_commits=arguments['compact'].cleanup_commits,
|
||||
threshold=arguments['compact'].threshold,
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
'{}: Skipping compact (only available/needed in Borg 1.2+)'.format(repository)
|
||||
)
|
||||
if 'create' in arguments:
|
||||
logger.info('{}: Creating archive{}'.format(repository, dry_run_label))
|
||||
dispatch.call_hooks(
|
||||
|
|
|
@ -605,10 +605,11 @@ properties:
|
|||
type: string
|
||||
description: |
|
||||
List of one or more shell commands or scripts to execute
|
||||
when an exception occurs during a "prune", "create", or
|
||||
"check" action or an associated before/after hook.
|
||||
when an exception occurs during a "prune", "compact",
|
||||
"create", or "check" action or an associated before/after
|
||||
hook.
|
||||
example:
|
||||
- echo "Error during prune/create/check."
|
||||
- echo "Error during prune/compact/create/check."
|
||||
before_everything:
|
||||
type: array
|
||||
items:
|
||||
|
|
|
@ -33,9 +33,9 @@ configuration file, right before the `create` action. `after_backup` hooks run
|
|||
afterwards, but not if an error occurs in a previous hook or in the backups
|
||||
themselves.
|
||||
|
||||
There are additional hooks for the `prune` and `check` actions as well.
|
||||
`before_prune` and `after_prune` run if there are any `prune` actions, while
|
||||
`before_check` and `after_check` run if there are any `check` actions.
|
||||
There are additional hooks that run before/after other actions as well. For
|
||||
instance, `before_prune` runs before a `prune` action, while `after_prune`
|
||||
runs after it.
|
||||
|
||||
You can also use `before_everything` and `after_everything` hooks to perform
|
||||
global setup or cleanup:
|
||||
|
|
|
@ -115,6 +115,6 @@ There are some caveats you should be aware of with this feature.
|
|||
* The soft failure doesn't have to apply to a repository. You can even perform
|
||||
a test to make sure that individual source directories are mounted and
|
||||
available. Use your imagination!
|
||||
* The soft failure feature also works for `before_prune`, `after_prune`,
|
||||
`before_check`, and `after_check` hooks. But it is not implemented for
|
||||
`before_everything` or `after_everything`.
|
||||
* The soft failure feature also works for before/after hooks for other
|
||||
actions as well. But it is not implemented for `before_everything` or
|
||||
`after_everything`.
|
||||
|
|
|
@ -9,19 +9,20 @@ eleventyNavigation:
|
|||
|
||||
Borg itself is great for efficiently de-duplicating data across successive
|
||||
backup archives, even when dealing with very large repositories. But you may
|
||||
find that while borgmatic's default mode of "prune, create, and check" works
|
||||
well on small repositories, it's not so great on larger ones. That's because
|
||||
running the default pruning and consistency checks take a long time on large
|
||||
repositories.
|
||||
find that while borgmatic's default mode of `prune`, `compact`, `create`, and
|
||||
`check` works well on small repositories, it's not so great on larger ones.
|
||||
That's because running the default pruning, compact, and consistency checks
|
||||
take a long time on large repositories.
|
||||
|
||||
### A la carte actions
|
||||
|
||||
If you find yourself in this situation, you have some options. First, you can
|
||||
run borgmatic's pruning, creating, or checking actions separately. For
|
||||
instance, the following optional actions are available:
|
||||
run borgmatic's `prune`, `compact`, `create`, or `check` actions separately.
|
||||
For instance, the following optional actions are available:
|
||||
|
||||
```bash
|
||||
borgmatic prune
|
||||
borgmatic compact
|
||||
borgmatic create
|
||||
borgmatic check
|
||||
```
|
||||
|
@ -32,7 +33,7 @@ borgmatic check
|
|||
You can run with only one of these actions provided, or you can mix and match
|
||||
any number of them in a single borgmatic run. This supports approaches like
|
||||
skipping certain actions while running others. For instance, this skips
|
||||
`prune` and only runs `create` and `check`:
|
||||
`prune` and `compact` and only runs `create` and `check`:
|
||||
|
||||
```bash
|
||||
borgmatic create check
|
||||
|
|
|
@ -83,10 +83,10 @@ tests](https://torsion.org/borgmatic/docs/how-to/extract-a-backup/).
|
|||
|
||||
## Error hooks
|
||||
|
||||
When an error occurs during a `prune`, `create`, or `check` action, borgmatic
|
||||
can run configurable shell commands to fire off custom error notifications or
|
||||
take other actions, so you can get alerted as soon as something goes wrong.
|
||||
Here's a not-so-useful example:
|
||||
When an error occurs during a `prune`, `compact`, `create`, or `check` action,
|
||||
borgmatic can run configurable shell commands to fire off custom error
|
||||
notifications or take other actions, so you can get alerted as soon as
|
||||
something goes wrong. Here's a not-so-useful example:
|
||||
|
||||
```yaml
|
||||
hooks:
|
||||
|
@ -117,9 +117,9 @@ here:
|
|||
* `output`: output of the command that failed (may be blank if an error
|
||||
occurred without running a command)
|
||||
|
||||
Note that borgmatic runs the `on_error` hooks only for `prune`, `create`, or
|
||||
`check` actions or hooks in which an error occurs, and not other actions.
|
||||
borgmatic does not run `on_error` hooks if an error occurs within a
|
||||
Note that borgmatic runs the `on_error` hooks only for `prune`, `compact`,
|
||||
`create`, or `check` actions or hooks in which an error occurs, and not other
|
||||
actions. borgmatic does not run `on_error` hooks if an error occurs within a
|
||||
`before_everything` or `after_everything` hook. For more about hooks, see the
|
||||
[borgmatic hooks
|
||||
documentation](https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/),
|
||||
|
@ -144,7 +144,7 @@ With this hook in place, borgmatic pings your Healthchecks project when a
|
|||
backup begins, ends, or errors. Specifically, after the <a
|
||||
href="https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/">`before_backup`
|
||||
hooks</a> run, borgmatic lets Healthchecks know that it has started if any of
|
||||
the `prune`, `create`, or `check` actions are run.
|
||||
the `prune`, `compact`, `create`, or `check` actions are run.
|
||||
|
||||
Then, if the actions complete successfully, borgmatic notifies Healthchecks of
|
||||
the success after the `after_backup` hooks run, and includes borgmatic logs in
|
||||
|
@ -155,7 +155,7 @@ in the Healthchecks UI, although be aware that Healthchecks currently has a
|
|||
If an error occurs during any action or hook, borgmatic notifies Healthchecks
|
||||
after the `on_error` hooks run, also tacking on logs including the error
|
||||
itself. But the logs are only included for errors that occur when a `prune`,
|
||||
`create`, or `check` action is run.
|
||||
`compact`, `create`, or `check` action is run.
|
||||
|
||||
You can customize the verbosity of the logs that are sent to Healthchecks with
|
||||
borgmatic's `--monitoring-verbosity` flag. The `--files` and `--stats` flags
|
||||
|
@ -184,8 +184,8 @@ With this hook in place, borgmatic pings your Cronitor monitor when a backup
|
|||
begins, ends, or errors. Specifically, after the <a
|
||||
href="https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/">`before_backup`
|
||||
hooks</a> run, borgmatic lets Cronitor know that it has started if any of the
|
||||
`prune`, `create`, or `check` actions are run. Then, if the actions complete
|
||||
successfully, borgmatic notifies Cronitor of the success after the
|
||||
`prune`, `compact`, `create`, or `check` actions are run. Then, if the actions
|
||||
complete successfully, borgmatic notifies Cronitor of the success after the
|
||||
`after_backup` hooks run. And if an error occurs during any action or hook,
|
||||
borgmatic notifies Cronitor after the `on_error` hooks run.
|
||||
|
||||
|
@ -212,8 +212,8 @@ With this hook in place, borgmatic pings your Cronhub monitor when a backup
|
|||
begins, ends, or errors. Specifically, after the <a
|
||||
href="https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/">`before_backup`
|
||||
hooks</a> run, borgmatic lets Cronhub know that it has started if any of the
|
||||
`prune`, `create`, or `check` actions are run. Then, if the actions complete
|
||||
successfully, borgmatic notifies Cronhub of the success after the
|
||||
`prune`, `compact`, `create`, or `check` actions are run. Then, if the actions
|
||||
complete successfully, borgmatic notifies Cronhub of the success after the
|
||||
`after_backup` hooks run. And if an error occurs during any action or hook,
|
||||
borgmatic notifies Cronhub after the `on_error` hooks run.
|
||||
|
||||
|
@ -252,9 +252,9 @@ hooks:
|
|||
|
||||
With this hook in place, borgmatic creates a PagerDuty event for your service
|
||||
whenever backups fail. Specifically, if an error occurs during a `create`,
|
||||
`prune`, or `check` action, borgmatic sends an event to PagerDuty before the
|
||||
`on_error` hooks run. Note that borgmatic does not contact PagerDuty when a
|
||||
backup starts or ends without error.
|
||||
`prune`, `compact`, or `check` action, borgmatic sends an event to PagerDuty
|
||||
before the `on_error` hooks run. Note that borgmatic does not contact
|
||||
PagerDuty when a backup starts or ends without error.
|
||||
|
||||
You can configure PagerDuty to notify you by a [variety of
|
||||
mechanisms](https://support.pagerduty.com/docs/notifications) when backups
|
||||
|
|
|
@ -227,8 +227,8 @@ sudo borgmatic --verbosity 1 --files
|
|||
borgmatic. So try leaving it out, or upgrade borgmatic!)
|
||||
|
||||
By default, this will also prune any old backups as per the configured
|
||||
retention policy, and check backups for consistency problems due to things
|
||||
like file damage.
|
||||
retention policy, compact segments to free up space (with Borg 1.2+), and
|
||||
check backups for consistency problems due to things like file damage.
|
||||
|
||||
The verbosity flag makes borgmatic show the steps it's performing. And the
|
||||
files flag lists each file that's new or changed since the last backup.
|
||||
|
|
13
tests/integration/borg/test_feature.py
Normal file
13
tests/integration/borg/test_feature.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
from borgmatic.borg import feature as module
|
||||
|
||||
|
||||
def test_available_true_for_new_enough_borg_version():
|
||||
assert module.available(module.Feature.COMPACT, '1.3.7')
|
||||
|
||||
|
||||
def test_available_true_for_borg_version_introducing_feature():
|
||||
assert module.available(module.Feature.COMPACT, '1.2.0a2')
|
||||
|
||||
|
||||
def test_available_false_for_too_old_borg_version():
|
||||
assert not module.available(module.Feature.COMPACT, '1.1.5')
|
|
@ -17,33 +17,33 @@ COMPACT_COMMAND = ('borg', 'compact')
|
|||
|
||||
|
||||
def test_compact_segments_calls_borg_with_parameters():
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('repo',), logging.WARNING)
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('repo',), logging.INFO)
|
||||
|
||||
module.compact_segments(dry_run=False, repository='repo', storage_config={})
|
||||
|
||||
|
||||
def test_compact_segments_with_log_info_calls_borg_with_info_parameter():
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--info', 'repo'), logging.WARNING)
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--info', 'repo'), logging.INFO)
|
||||
insert_logging_mock(logging.INFO)
|
||||
|
||||
module.compact_segments(repository='repo', storage_config={}, dry_run=False)
|
||||
|
||||
|
||||
def test_compact_segments_with_log_debug_calls_borg_with_debug_parameter():
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--debug', '--show-rc', 'repo'), logging.WARNING)
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--debug', '--show-rc', 'repo'), logging.INFO)
|
||||
insert_logging_mock(logging.DEBUG)
|
||||
|
||||
module.compact_segments(repository='repo', storage_config={}, dry_run=False)
|
||||
|
||||
|
||||
def test_compact_segments_with_dry_run_calls_borg_with_dry_run_parameter():
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--dry-run', 'repo'), logging.WARNING)
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--dry-run', 'repo'), logging.INFO)
|
||||
|
||||
module.compact_segments(repository='repo', storage_config={}, dry_run=True)
|
||||
|
||||
|
||||
def test_compact_segments_with_local_path_calls_borg_via_local_path():
|
||||
insert_execute_command_mock(('borg1',) + COMPACT_COMMAND[1:] + ('repo',), logging.WARNING)
|
||||
insert_execute_command_mock(('borg1',) + COMPACT_COMMAND[1:] + ('repo',), logging.INFO)
|
||||
|
||||
module.compact_segments(
|
||||
dry_run=False, repository='repo', storage_config={}, local_path='borg1',
|
||||
|
@ -51,9 +51,7 @@ def test_compact_segments_with_local_path_calls_borg_via_local_path():
|
|||
|
||||
|
||||
def test_compact_segments_with_remote_path_calls_borg_with_remote_path_parameters():
|
||||
insert_execute_command_mock(
|
||||
COMPACT_COMMAND + ('--remote-path', 'borg1', 'repo'), logging.WARNING
|
||||
)
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--remote-path', 'borg1', 'repo'), logging.INFO)
|
||||
|
||||
module.compact_segments(
|
||||
dry_run=False, repository='repo', storage_config={}, remote_path='borg1',
|
||||
|
@ -61,7 +59,7 @@ def test_compact_segments_with_remote_path_calls_borg_with_remote_path_parameter
|
|||
|
||||
|
||||
def test_compact_segments_with_progress_calls_borg_with_progress_parameter():
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--progress', 'repo'), logging.WARNING)
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--progress', 'repo'), logging.INFO)
|
||||
|
||||
module.compact_segments(
|
||||
dry_run=False, repository='repo', storage_config={}, progress=True,
|
||||
|
@ -69,7 +67,7 @@ def test_compact_segments_with_progress_calls_borg_with_progress_parameter():
|
|||
|
||||
|
||||
def test_compact_segments_with_cleanup_commits_calls_borg_with_cleanup_commits_parameter():
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--cleanup-commits', 'repo'), logging.WARNING)
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--cleanup-commits', 'repo'), logging.INFO)
|
||||
|
||||
module.compact_segments(
|
||||
dry_run=False, repository='repo', storage_config={}, cleanup_commits=True,
|
||||
|
@ -77,7 +75,7 @@ def test_compact_segments_with_cleanup_commits_calls_borg_with_cleanup_commits_p
|
|||
|
||||
|
||||
def test_compact_segments_with_threshold_calls_borg_with_threshold_parameter():
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--threshold', '20', 'repo'), logging.WARNING)
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--threshold', '20', 'repo'), logging.INFO)
|
||||
|
||||
module.compact_segments(
|
||||
dry_run=False, repository='repo', storage_config={}, threshold=20,
|
||||
|
@ -86,7 +84,7 @@ def test_compact_segments_with_threshold_calls_borg_with_threshold_parameter():
|
|||
|
||||
def test_compact_segments_with_umask_calls_borg_with_umask_parameters():
|
||||
storage_config = {'umask': '077'}
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--umask', '077', 'repo'), logging.WARNING)
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--umask', '077', 'repo'), logging.INFO)
|
||||
|
||||
module.compact_segments(
|
||||
dry_run=False, repository='repo', storage_config=storage_config,
|
||||
|
@ -95,7 +93,7 @@ def test_compact_segments_with_umask_calls_borg_with_umask_parameters():
|
|||
|
||||
def test_compact_segments_with_lock_wait_calls_borg_with_lock_wait_parameters():
|
||||
storage_config = {'lock_wait': 5}
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--lock-wait', '5', 'repo'), logging.WARNING)
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--lock-wait', '5', 'repo'), logging.INFO)
|
||||
|
||||
module.compact_segments(
|
||||
dry_run=False, repository='repo', storage_config=storage_config,
|
||||
|
@ -103,7 +101,7 @@ def test_compact_segments_with_lock_wait_calls_borg_with_lock_wait_parameters():
|
|||
|
||||
|
||||
def test_compact_segments_with_extra_borg_options_calls_borg_with_extra_options():
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--extra', '--options', 'repo'), logging.WARNING)
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--extra', '--options', 'repo'), logging.INFO)
|
||||
|
||||
module.compact_segments(
|
||||
dry_run=False,
|
||||
|
|
49
tests/unit/borg/test_version.py
Normal file
49
tests/unit/borg/test_version.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
import logging
|
||||
|
||||
import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.borg import version as module
|
||||
|
||||
from ..test_verbosity import insert_logging_mock
|
||||
|
||||
VERSION = '1.2.3'
|
||||
|
||||
|
||||
def insert_execute_command_mock(command, borg_local_path='borg', version_output=f'borg {VERSION}'):
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
command, output_log_level=None, borg_local_path=borg_local_path
|
||||
).once().and_return(version_output)
|
||||
|
||||
|
||||
def test_local_borg_version_calls_borg_with_required_parameters():
|
||||
insert_execute_command_mock(('borg', '--version'))
|
||||
|
||||
assert module.local_borg_version() == VERSION
|
||||
|
||||
|
||||
def test_local_borg_version_with_log_info_calls_borg_with_info_parameter():
|
||||
insert_execute_command_mock(('borg', '--version', '--info'))
|
||||
insert_logging_mock(logging.INFO)
|
||||
|
||||
assert module.local_borg_version() == VERSION
|
||||
|
||||
|
||||
def test_local_borg_version_with_log_debug_calls_borg_with_debug_parameters():
|
||||
insert_execute_command_mock(('borg', '--version', '--debug', '--show-rc'))
|
||||
insert_logging_mock(logging.DEBUG)
|
||||
|
||||
assert module.local_borg_version() == VERSION
|
||||
|
||||
|
||||
def test_local_borg_version_with_local_borg_path_calls_borg_with_it():
|
||||
insert_execute_command_mock(('borg1', '--version'), borg_local_path='borg1')
|
||||
|
||||
assert module.local_borg_version('borg1') == VERSION
|
||||
|
||||
|
||||
def test_local_borg_version_with_invalid_version_raises():
|
||||
insert_execute_command_mock(('borg', '--version'), version_output='wtf')
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.local_borg_version()
|
|
@ -72,12 +72,14 @@ def test_parse_subparser_arguments_consumes_multiple_subparser_arguments():
|
|||
|
||||
def test_parse_subparser_arguments_applies_default_subparsers():
|
||||
prune_namespace = flexmock()
|
||||
compact_namespace = flexmock()
|
||||
create_namespace = flexmock(progress=True)
|
||||
check_namespace = flexmock()
|
||||
subparsers = {
|
||||
'prune': flexmock(
|
||||
parse_known_args=lambda arguments: (prune_namespace, ['prune', '--progress'])
|
||||
),
|
||||
'compact': flexmock(parse_known_args=lambda arguments: (compact_namespace, [])),
|
||||
'create': flexmock(parse_known_args=lambda arguments: (create_namespace, [])),
|
||||
'check': flexmock(parse_known_args=lambda arguments: (check_namespace, [])),
|
||||
'other': flexmock(),
|
||||
|
@ -87,6 +89,7 @@ def test_parse_subparser_arguments_applies_default_subparsers():
|
|||
|
||||
assert arguments == {
|
||||
'prune': prune_namespace,
|
||||
'compact': compact_namespace,
|
||||
'create': create_namespace,
|
||||
'check': check_namespace,
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ from borgmatic.commands import borgmatic as module
|
|||
|
||||
def test_run_configuration_runs_actions_for_each_repository():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
expected_results = [flexmock(), flexmock()]
|
||||
flexmock(module).should_receive('run_actions').and_return(expected_results[:1]).and_return(
|
||||
expected_results[1:]
|
||||
|
@ -22,8 +23,21 @@ def test_run_configuration_runs_actions_for_each_repository():
|
|||
assert results == expected_results
|
||||
|
||||
|
||||
def test_run_configuration_with_invalid_borg_version_errors():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_raise(ValueError)
|
||||
flexmock(module.command).should_receive('execute_hook').never()
|
||||
flexmock(module.dispatch).should_receive('call_hooks').never()
|
||||
flexmock(module).should_receive('run_actions').never()
|
||||
config = {'location': {'repositories': ['foo']}}
|
||||
arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'prune': flexmock()}
|
||||
|
||||
list(module.run_configuration('test.yaml', config, arguments))
|
||||
|
||||
|
||||
def test_run_configuration_calls_hooks_for_prune_action():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module.command).should_receive('execute_hook').twice()
|
||||
flexmock(module.dispatch).should_receive('call_hooks').at_least().twice()
|
||||
flexmock(module).should_receive('run_actions').and_return([])
|
||||
|
@ -35,6 +49,7 @@ def test_run_configuration_calls_hooks_for_prune_action():
|
|||
|
||||
def test_run_configuration_calls_hooks_for_compact_action():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module.command).should_receive('execute_hook').twice()
|
||||
flexmock(module).should_receive('run_actions').and_return([])
|
||||
config = {'location': {'repositories': ['foo']}}
|
||||
|
@ -45,6 +60,7 @@ def test_run_configuration_calls_hooks_for_compact_action():
|
|||
|
||||
def test_run_configuration_executes_and_calls_hooks_for_create_action():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module.command).should_receive('execute_hook').twice()
|
||||
flexmock(module.dispatch).should_receive('call_hooks').at_least().twice()
|
||||
flexmock(module).should_receive('run_actions').and_return([])
|
||||
|
@ -56,6 +72,7 @@ def test_run_configuration_executes_and_calls_hooks_for_create_action():
|
|||
|
||||
def test_run_configuration_calls_hooks_for_check_action():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module.command).should_receive('execute_hook').twice()
|
||||
flexmock(module.dispatch).should_receive('call_hooks').at_least().twice()
|
||||
flexmock(module).should_receive('run_actions').and_return([])
|
||||
|
@ -67,6 +84,7 @@ def test_run_configuration_calls_hooks_for_check_action():
|
|||
|
||||
def test_run_configuration_calls_hooks_for_extract_action():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module.command).should_receive('execute_hook').twice()
|
||||
flexmock(module.dispatch).should_receive('call_hooks').never()
|
||||
flexmock(module).should_receive('run_actions').and_return([])
|
||||
|
@ -78,6 +96,7 @@ def test_run_configuration_calls_hooks_for_extract_action():
|
|||
|
||||
def test_run_configuration_does_not_trigger_hooks_for_list_action():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module.command).should_receive('execute_hook').never()
|
||||
flexmock(module.dispatch).should_receive('call_hooks').never()
|
||||
flexmock(module).should_receive('run_actions').and_return([])
|
||||
|
@ -89,6 +108,7 @@ def test_run_configuration_does_not_trigger_hooks_for_list_action():
|
|||
|
||||
def test_run_configuration_logs_actions_error():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module.command).should_receive('execute_hook')
|
||||
flexmock(module.dispatch).should_receive('call_hooks')
|
||||
expected_results = [flexmock()]
|
||||
|
@ -104,6 +124,7 @@ def test_run_configuration_logs_actions_error():
|
|||
|
||||
def test_run_configuration_logs_pre_hook_error():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module.command).should_receive('execute_hook').and_raise(OSError).and_return(None)
|
||||
expected_results = [flexmock()]
|
||||
flexmock(module).should_receive('make_error_log_records').and_return(expected_results)
|
||||
|
@ -118,6 +139,7 @@ def test_run_configuration_logs_pre_hook_error():
|
|||
|
||||
def test_run_configuration_bails_for_pre_hook_soft_failure():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
error = subprocess.CalledProcessError(borgmatic.hooks.command.SOFT_FAIL_EXIT_CODE, 'try again')
|
||||
flexmock(module.command).should_receive('execute_hook').and_raise(error).and_return(None)
|
||||
flexmock(module).should_receive('make_error_log_records').never()
|
||||
|
@ -132,6 +154,7 @@ def test_run_configuration_bails_for_pre_hook_soft_failure():
|
|||
|
||||
def test_run_configuration_logs_post_hook_error():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module.command).should_receive('execute_hook').and_return(None).and_raise(
|
||||
OSError
|
||||
).and_return(None)
|
||||
|
@ -149,6 +172,7 @@ def test_run_configuration_logs_post_hook_error():
|
|||
|
||||
def test_run_configuration_bails_for_post_hook_soft_failure():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
error = subprocess.CalledProcessError(borgmatic.hooks.command.SOFT_FAIL_EXIT_CODE, 'try again')
|
||||
flexmock(module.command).should_receive('execute_hook').and_return(None).and_raise(
|
||||
error
|
||||
|
@ -166,6 +190,7 @@ def test_run_configuration_bails_for_post_hook_soft_failure():
|
|||
|
||||
def test_run_configuration_logs_on_error_hook_error():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module.command).should_receive('execute_hook').and_raise(OSError)
|
||||
expected_results = [flexmock(), flexmock()]
|
||||
flexmock(module).should_receive('make_error_log_records').and_return(
|
||||
|
@ -182,6 +207,7 @@ def test_run_configuration_logs_on_error_hook_error():
|
|||
|
||||
def test_run_configuration_bails_for_on_error_hook_soft_failure():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
error = subprocess.CalledProcessError(borgmatic.hooks.command.SOFT_FAIL_EXIT_CODE, 'try again')
|
||||
flexmock(module.command).should_receive('execute_hook').and_return(None).and_raise(error)
|
||||
expected_results = [flexmock()]
|
||||
|
@ -198,6 +224,7 @@ def test_run_configuration_bails_for_on_error_hook_soft_failure():
|
|||
def test_run_configuration_retries_soft_error():
|
||||
# Run action first fails, second passes
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module.command).should_receive('execute_hook')
|
||||
flexmock(module).should_receive('run_actions').and_raise(OSError).and_return([])
|
||||
expected_results = [flexmock()]
|
||||
|
@ -211,6 +238,7 @@ def test_run_configuration_retries_soft_error():
|
|||
def test_run_configuration_retries_hard_error():
|
||||
# Run action fails twice
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module.command).should_receive('execute_hook')
|
||||
flexmock(module).should_receive('run_actions').and_raise(OSError).times(2)
|
||||
expected_results = [flexmock(), flexmock()]
|
||||
|
@ -229,6 +257,7 @@ def test_run_configuration_retries_hard_error():
|
|||
|
||||
def test_run_repos_ordered():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module.command).should_receive('execute_hook')
|
||||
flexmock(module).should_receive('run_actions').and_raise(OSError).times(2)
|
||||
expected_results = [flexmock(), flexmock()]
|
||||
|
@ -246,6 +275,7 @@ def test_run_repos_ordered():
|
|||
|
||||
def test_run_configuration_retries_round_robbin():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module.command).should_receive('execute_hook')
|
||||
flexmock(module).should_receive('run_actions').and_raise(OSError).times(4)
|
||||
expected_results = [flexmock(), flexmock(), flexmock(), flexmock()]
|
||||
|
@ -269,6 +299,7 @@ def test_run_configuration_retries_round_robbin():
|
|||
|
||||
def test_run_configuration_retries_one_passes():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module.command).should_receive('execute_hook')
|
||||
flexmock(module).should_receive('run_actions').and_raise(OSError).and_raise(OSError).and_return(
|
||||
[]
|
||||
|
@ -291,6 +322,7 @@ def test_run_configuration_retries_one_passes():
|
|||
|
||||
def test_run_configuration_retry_wait():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module.command).should_receive('execute_hook')
|
||||
flexmock(module).should_receive('run_actions').and_raise(OSError).times(4)
|
||||
expected_results = [flexmock(), flexmock(), flexmock(), flexmock()]
|
||||
|
@ -320,6 +352,7 @@ def test_run_configuration_retry_wait():
|
|||
|
||||
def test_run_configuration_retries_timeout_multiple_repos():
|
||||
flexmock(module.borg_environment).should_receive('initialize')
|
||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||
flexmock(module.command).should_receive('execute_hook')
|
||||
flexmock(module).should_receive('run_actions').and_raise(OSError).and_raise(OSError).and_return(
|
||||
[]
|
||||
|
|
Loading…
Reference in a new issue