This commit is contained in:
Divyansh Singh 2023-04-01 22:12:41 +05:30
commit 32ab17fa46
27 changed files with 542 additions and 149 deletions

View File

@ -24,6 +24,8 @@ clone:
steps:
- name: build
image: alpine:3.13
environment:
TEST_CONTAINER: true
pull: always
commands:
- scripts/run-full-tests

9
NEWS
View File

@ -1,3 +1,12 @@
1.7.11.dev0
* #479: Automatically use the "archive_name_format" option to filter which archives get used for
borgmatic actions that operate on multiple archives. See the documentation for more information:
https://torsion.org/borgmatic/docs/how-to/make-per-application-backups/#archive-naming
* #479: The "prefix" options have been deprecated in favor of the new "archive_name_format"
auto-matching behavior (see above).
* #662: Fix regression in which "check_repositories" option failed to match repositories.
* #663: Fix regression in which the "transfer" action produced a traceback.
1.7.10
* #396: When a database command errors, display and log the error message instead of swallowing it.
* #501: Optionally error if a source directory does not exist via "source_directories_must_exist"

View File

@ -17,10 +17,10 @@ def run_transfer(
'''
Run the "transfer" action for the given repository.
'''
logger.info(f'{repository}: Transferring archives to repository')
logger.info(f'{repository["path"]}: Transferring archives to repository')
borgmatic.borg.transfer.transfer_archives(
global_arguments.dry_run,
repository,
repository['path'],
storage,
local_borg_version,
transfer_arguments,

View File

@ -12,7 +12,6 @@ DEFAULT_CHECKS = (
{'name': 'repository', 'frequency': '1 month'},
{'name': 'archives', 'frequency': '1 month'},
)
DEFAULT_PREFIX = '{hostname}-' # noqa: FS003
logger = logging.getLogger(__name__)
@ -146,9 +145,10 @@ def filter_checks_on_frequency(
return tuple(filtered_checks)
def make_check_flags(local_borg_version, checks, check_last=None, prefix=None):
def make_check_flags(local_borg_version, storage_config, checks, check_last=None, prefix=None):
'''
Given the local Borg version and a parsed sequence of checks, transform the checks into tuple of
Given the local Borg version, a storge configuration dict, a parsed sequence of checks, the
check last value, and a consistency check prefix, transform the checks into tuple of
command-line flags.
For example, given parsed checks of:
@ -174,10 +174,19 @@ def make_check_flags(local_borg_version, checks, check_last=None, prefix=None):
if 'archives' in checks:
last_flags = ('--last', str(check_last)) if check_last else ()
if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version):
match_archives_flags = ('--match-archives', f'sh:{prefix}*') if prefix else ()
else:
match_archives_flags = ('--glob-archives', f'{prefix}*') if prefix else ()
match_archives_flags = (
(
('--match-archives', f'sh:{prefix}*')
if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version)
else ('--glob-archives', f'{prefix}*')
)
if prefix
else (
flags.make_match_archives_flags(
storage_config.get('archive_name_format'), local_borg_version
)
)
)
else:
last_flags = ()
match_archives_flags = ()
@ -291,7 +300,7 @@ def check_archives(
extra_borg_options = storage_config.get('extra_borg_options', {}).get('check', '')
if set(checks).intersection({'repository', 'archives', 'data'}):
lock_wait = storage_config.get('lock_wait', None)
lock_wait = storage_config.get('lock_wait')
verbosity_flags = ()
if logger.isEnabledFor(logging.INFO):
@ -299,12 +308,12 @@ def check_archives(
if logger.isEnabledFor(logging.DEBUG):
verbosity_flags = ('--debug', '--show-rc')
prefix = consistency_config.get('prefix', DEFAULT_PREFIX)
prefix = consistency_config.get('prefix')
full_command = (
(local_path, 'check')
+ (('--repair',) if repair else ())
+ make_check_flags(local_borg_version, checks, check_last, prefix)
+ make_check_flags(local_borg_version, storage_config, checks, check_last, prefix)
+ (('--remote-path', remote_path) if remote_path else ())
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
+ verbosity_flags

View File

@ -1,4 +1,5 @@
import itertools
import re
from borgmatic.borg import feature
@ -56,3 +57,20 @@ def make_repository_archive_flags(repository_path, archive, local_borg_version):
if feature.available(feature.Feature.SEPARATE_REPOSITORY_ARCHIVE, local_borg_version)
else (f'{repository_path}::{archive}',)
)
def make_match_archives_flags(archive_name_format, local_borg_version):
'''
Return the match archives flags that would match archives created with the given archive name
format (if any). This is done by replacing certain archive name format placeholders for
ephemeral data (like "{now}") with globs.
'''
if not archive_name_format:
return ()
match_archives = re.sub(r'\{(now|utcnow|pid)([:%\w\.-]*)\}', '*', archive_name_format)
if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version):
return ('--match-archives', f'sh:{match_archives}')
else:
return ('--glob-archives', f'{match_archives}')

View File

@ -44,7 +44,11 @@ def display_archives_info(
else flags.make_flags('glob-archives', f'{info_arguments.prefix}*')
)
if info_arguments.prefix
else ()
else (
flags.make_match_archives_flags(
storage_config.get('archive_name_format'), local_borg_version
)
)
)
+ flags.make_flags_from_arguments(
info_arguments, excludes=('repository', 'archive', 'prefix')

View File

@ -7,10 +7,10 @@ from borgmatic.execute import execute_command
logger = logging.getLogger(__name__)
def make_prune_flags(retention_config, local_borg_version):
def make_prune_flags(storage_config, retention_config, local_borg_version):
'''
Given a retention config dict mapping from option name to value, transform it into an iterable of
command-line name-value flag pairs.
Given a retention config dict mapping from option name to value, transform it into an sequence of
command-line flags.
For example, given a retention config of:
@ -24,7 +24,7 @@ def make_prune_flags(retention_config, local_borg_version):
)
'''
config = retention_config.copy()
prefix = config.pop('prefix', '{hostname}-') # noqa: FS003
prefix = config.pop('prefix', None)
if prefix:
if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version):
@ -32,10 +32,16 @@ def make_prune_flags(retention_config, local_borg_version):
else:
config['glob_archives'] = f'{prefix}*'
return (
flag_pairs = (
('--' + option_name.replace('_', '-'), str(value)) for option_name, value in config.items()
)
return tuple(
element for pair in flag_pairs for element in pair
) + flags.make_match_archives_flags(
storage_config.get('archive_name_format'), local_borg_version
)
def prune_archives(
dry_run,
@ -60,11 +66,7 @@ def prune_archives(
full_command = (
(local_path, 'prune')
+ tuple(
element
for pair in make_prune_flags(retention_config, local_borg_version)
for element in pair
)
+ make_prune_flags(storage_config, retention_config, local_borg_version)
+ (('--remote-path', remote_path) if remote_path else ())
+ (('--umask', str(umask)) if umask else ())
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())

View File

@ -94,7 +94,11 @@ def make_rlist_command(
else flags.make_flags('glob-archives', f'{rlist_arguments.prefix}*')
)
if rlist_arguments.prefix
else ()
else (
flags.make_match_archives_flags(
storage_config.get('archive_name_format'), local_borg_version
)
)
)
+ flags.make_flags_from_arguments(rlist_arguments, excludes=MAKE_FLAGS_EXCLUDES)
+ flags.make_repository_flags(repository_path, local_borg_version)

View File

@ -34,9 +34,16 @@ def transfer_archives(
'match-archives', transfer_arguments.match_archives or transfer_arguments.archive
)
)
+ flags.make_flags_from_arguments(
transfer_arguments,
excludes=('repository', 'source_repository', 'archive', 'match_archives'),
+ (
flags.make_flags_from_arguments(
transfer_arguments,
excludes=('repository', 'source_repository', 'archive', 'match_archives'),
)
or (
flags.make_match_archives_flags(
storage_config.get('archive_name_format'), local_borg_version
)
)
)
+ flags.make_repository_flags(repository_path, local_borg_version)
+ flags.make_flags('other-repo', transfer_arguments.source_repository)

View File

@ -378,11 +378,9 @@ properties:
description: |
Name of the archive. Borg placeholders can be used. See the
output of "borg help placeholders" for details. Defaults to
"{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}". If you specify this
option, consider also specifying a prefix in the retention
and consistency sections to avoid accidental
pruning/checking of archives with different archive name
formats.
"{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}". When running
actions like rlist, info, or check, borgmatic automatically
tries to match only archives created with this name format.
example: "{hostname}-documents-{now}"
relocated_repo_access_is_ok:
type: boolean
@ -477,10 +475,12 @@ properties:
prefix:
type: string
description: |
When pruning, only consider archive names starting with this
prefix. Borg placeholders can be used. See the output of
"borg help placeholders" for details. Defaults to
"{hostname}-". Use an empty value to disable the default.
Deprecated. When pruning, only consider archive names
starting with this prefix. Borg placeholders can be used.
See the output of "borg help placeholders" for details.
If a prefix is not specified, borgmatic defaults to
matching archives based on the archive_name_format (see
above).
example: sourcehostname
consistency:
type: object
@ -538,12 +538,12 @@ properties:
items:
type: string
description: |
Paths to a subset of the repositories in the location
section on which to run consistency checks. Handy in case
some of your repositories are very large, and so running
consistency checks on them would take too long. Defaults to
running consistency checks on all repositories configured in
the location section.
Paths or labels for a subset of the repositories in the
location section on which to run consistency checks. Handy
in case some of your repositories are very large, and so
running consistency checks on them would take too long.
Defaults to running consistency checks on all repositories
configured in the location section.
example:
- user@backupserver:sourcehostname.borg
check_last:
@ -556,11 +556,12 @@ properties:
prefix:
type: string
description: |
When performing the "archives" check, only consider archive
names starting with this prefix. Borg placeholders can be
used. See the output of "borg help placeholders" for
details. Defaults to "{hostname}-". Use an empty value to
disable the default.
Deprecated. When performing the "archives" check, only
consider archive names starting with this prefix. Borg
placeholders can be used. See the output of "borg help
placeholders" for details. If a prefix is not specified,
borgmatic defaults to matching archives based on the
archive_name_format (see above).
example: sourcehostname
output:
type: object

View File

@ -69,7 +69,10 @@ def apply_logical_validation(config_filename, parsed_configuration):
location_repositories = parsed_configuration.get('location', {}).get('repositories')
check_repositories = parsed_configuration.get('consistency', {}).get('check_repositories', [])
for repository in check_repositories:
if repository not in location_repositories:
if not any(
repositories_match(repository, config_repository)
for config_repository in location_repositories
):
raise Validation_error(
config_filename,
(

View File

@ -25,7 +25,7 @@ so that you can run borgmatic commands while you're hacking on them to
make sure your changes work.
```bash
cd borgmatic/
cd borgmatic
pip3 install --user --editable .
```
@ -51,7 +51,6 @@ pip3 install --user tox
Finally, to actually run tests, run:
```bash
cd borgmatic
tox
```

View File

@ -54,6 +54,71 @@ choice](https://torsion.org/borgmatic/docs/how-to/set-up-backups/#autopilot),
each entry using borgmatic's `--config` flag instead of relying on
`/etc/borgmatic.d`.
## Archive naming
If you've got multiple borgmatic configuration files, you might want to create
archives with different naming schemes for each one. This is especially handy
if each configuration file is backing up to the same Borg repository but you
still want to be able to distinguish backup archives for one application from
another.
borgmatic supports this use case with an `archive_name_format` option. The
idea is that you define a string format containing a number of [Borg
placeholders](https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-placeholders),
and borgmatic uses that format to name any new archive it creates. For
instance:
```yaml
location:
...
archive_name_format: home-directories-{now}
```
This means that when borgmatic creates an archive, its name will start with
the string `home-directories-` and end with a timestamp for its creation time.
If `archive_name_format` is unspecified, the default is
`{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}`, meaning your system hostname plus a
timestamp in a particular format.
<span class="minilink minilink-addedin">New in version 1.7.11</span> borgmatic
uses the `archive_name_format` option to automatically limit which archives
get used for actions operating on multiple archives. This prevents, for
instance, duplicate archives from showing up in `rlist` or `info` results—even
if the same repository appears in multiple borgmatic configuration files. To
take advantage of this feature, simply use a different `archive_name_format`
in each configuration file.
Under the hood, borgmatic accomplishes this by substituting globs for certain
ephemeral data placeholders in your `archive_name_format`—and using the result
to filter archives when running supported actions.
For instance, let's say that you have this in your configuration:
```yaml
location:
...
archive_name_format: {hostname}-user-data-{now}
```
borgmatic considers `{now}` an emphemeral data placeholder that will probably
change per archive, while `{hostname}` won't. So it turns the example value
into `{hostname}-user-data-*` and applies it to filter down the set of
archives used for actions like `rlist`, `info`, `prune`, `check`, etc.
The end result is that when borgmatic runs the actions for a particular
application-specific configuration file, it only operates on the archives
created for that application. Of course, this doesn't apply to actions like
`compact` that operate on an entire repository.
<span class="minilink minilink-addedin">Prior to 1.7.11</span> The way to
limit the archives used for the `prune` action was a `prefix` option in the
`retention` section for matching against the start of archive names. And the
option for limiting the archives used for the `check` action was a separate
`prefix` in the `consistency` section. Both of these options are deprecated in
favor of the auto-matching behavior in newer versions of borgmatic.
## Configuration includes
Once you have multiple different configuration files, you might want to share
@ -272,7 +337,7 @@ Here's an example usage:
```yaml
constants:
user: foo
my_prefix: bar-
archive_prefix: bar
location:
source_directories:
@ -281,20 +346,14 @@ location:
...
storage:
archive_name_format: '{my_prefix}{now}'
retention:
prefix: {my_prefix}
consistency:
prefix: {my_prefix}
archive_name_format: '{archive_prefix}-{now}'
```
In this example, when borgmatic runs, all instances of `{user}` get replaced
with `foo` and all instances of `{my_prefix}` get replaced with `bar-`. (And
in this particular example, `{now}` doesn't get replaced with anything, but
gets passed directly to Borg.) After substitution, the logical result looks
something like this:
with `foo` and all instances of `{archive-prefix}` get replaced with `bar-`.
(And in this particular example, `{now}` doesn't get replaced with anything,
but gets passed directly to Borg.) After substitution, the logical result
looks something like this:
```yaml
location:
@ -305,12 +364,6 @@ location:
storage:
archive_name_format: 'bar-{now}'
retention:
prefix: bar-
consistency:
prefix: bar-
```
An alternate to constants is passing in your values via [environment

View File

@ -90,7 +90,7 @@ installing borgmatic:
* [Fedora unofficial](https://copr.fedorainfracloud.org/coprs/heffer/borgmatic/)
* [Arch Linux](https://www.archlinux.org/packages/community/any/borgmatic/)
* [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=borgmatic)
* [OpenBSD](http://ports.su/sysutils/borgmatic)
* [OpenBSD](https://openports.pl/path/sysutils/borgmatic)
* [openSUSE](https://software.opensuse.org/package/borgmatic)
* [macOS (via Homebrew)](https://formulae.brew.sh/formula/borgmatic)
* [macOS (via MacPorts)](https://ports.macports.org/port/borgmatic/)

View File

@ -1,7 +1,7 @@
#!/bin/sh
# This script is for running all tests, including end-to-end tests, on a developer machine. It sets
# up database containers to run tests against, runs the tests, and then tears down the containers.
# This script is for running end-to-end tests on a developer machine. It sets up database containers
# to run tests against, runs the tests, and then tears down the containers.
#
# Run this script from the root directory of the borgmatic source.
#

View File

@ -8,7 +8,14 @@
# For more information, see:
# https://torsion.org/borgmatic/docs/how-to/develop-on-borgmatic/
set -ex
set -e
if [ -z "$TEST_CONTAINER" ] ; then
echo "This script is designed to work inside a test container and is not intended to"
echo "be run manually. If you're trying to run borgmatic's end-to-end tests, execute"
echo "scripts/run-end-to-end-dev-tests instead."
exit 1
fi
apk add --no-cache python3 py3-pip borgbackup postgresql-client mariadb-client mongodb-tools \
py3-ruamel.yaml py3-ruamel.yaml.clib bash sqlite

View File

@ -1,6 +1,6 @@
from setuptools import find_packages, setup
VERSION = '1.7.10'
VERSION = '1.7.11.dev0'
setup(

View File

@ -17,6 +17,8 @@ services:
MONGO_INITDB_ROOT_PASSWORD: test
tests:
image: alpine:3.13
environment:
TEST_CONTAINER: true
volumes:
- "../..:/app:ro"
tmpfs:
@ -28,3 +30,4 @@ services:
depends_on:
- postgresql
- mysql
- mongodb

View File

@ -147,7 +147,7 @@ def test_log_outputs_kills_other_processes_when_one_errors():
['sleep', '2'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
flexmock(module).should_receive('exit_code_indicates_error').with_args(
other_process, None, 'borg'
['sleep', '2'], None, 'borg'
).and_return(False)
flexmock(module).should_receive('output_buffer_for_process').with_args(process, ()).and_return(
process.stdout

View File

@ -10,7 +10,7 @@ def test_run_transfer_does_not_raise():
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
module.run_transfer(
repository='repo',
repository={'path': 'repo'},
storage={},
local_borg_version=None,
transfer_arguments=transfer_arguments,

View File

@ -189,150 +189,170 @@ def test_filter_checks_on_frequency_restains_check_with_unelapsed_frequency_and_
def test_make_check_flags_with_repository_check_returns_flag():
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flags = module.make_check_flags('1.2.3', ('repository',))
flags = module.make_check_flags('1.2.3', {}, ('repository',))
assert flags == ('--repository-only',)
def test_make_check_flags_with_archives_check_returns_flag():
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flags = module.make_check_flags('1.2.3', ('archives',))
flags = module.make_check_flags('1.2.3', {}, ('archives',))
assert flags == ('--archives-only',)
def test_make_check_flags_with_data_check_returns_flag_and_implies_archives():
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flags = module.make_check_flags('1.2.3', ('data',))
flags = module.make_check_flags('1.2.3', {}, ('data',))
assert flags == ('--archives-only', '--verify-data',)
def test_make_check_flags_with_extract_omits_extract_flag():
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flags = module.make_check_flags('1.2.3', ('extract',))
flags = module.make_check_flags('1.2.3', {}, ('extract',))
assert flags == ()
def test_make_check_flags_with_repository_and_data_checks_does_not_return_repository_only():
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flags = module.make_check_flags('1.2.3', ('repository', 'data',))
flags = module.make_check_flags('1.2.3', {}, ('repository', 'data',))
assert flags == ('--verify-data',)
def test_make_check_flags_with_default_checks_and_default_prefix_returns_default_flags():
def test_make_check_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(())
flags = module.make_check_flags('1.2.3', {}, ('repository', 'archives'), prefix='foo',)
assert flags == ('--match-archives', 'sh:foo*')
def test_make_check_flags_with_all_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(())
flags = module.make_check_flags(
'1.2.3', ('repository', 'archives'), prefix=module.DEFAULT_PREFIX
'1.2.3', {}, ('repository', 'archives', 'extract'), prefix='foo',
)
assert flags == ('--match-archives', f'sh:{module.DEFAULT_PREFIX}*')
assert flags == ('--match-archives', 'sh:foo*')
def test_make_check_flags_with_all_checks_and_default_prefix_returns_default_flags():
flexmock(module.feature).should_receive('available').and_return(True)
flags = module.make_check_flags(
'1.2.3', ('repository', 'archives', 'extract'), prefix=module.DEFAULT_PREFIX
)
assert flags == ('--match-archives', f'sh:{module.DEFAULT_PREFIX}*')
def test_make_check_flags_with_all_checks_and_default_prefix_without_borg_features_returns_glob_archives_flags():
def test_make_check_flags_with_all_checks_and_prefix_without_borg_features_returns_glob_archives_flags():
flexmock(module.feature).should_receive('available').and_return(False)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flags = module.make_check_flags(
'1.2.3', ('repository', 'archives', 'extract'), prefix=module.DEFAULT_PREFIX
'1.2.3', {}, ('repository', 'archives', 'extract'), prefix='foo',
)
assert flags == ('--glob-archives', f'{module.DEFAULT_PREFIX}*')
assert flags == ('--glob-archives', 'foo*')
def test_make_check_flags_with_archives_check_and_last_includes_last_flag():
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flags = module.make_check_flags('1.2.3', ('archives',), check_last=3)
flags = module.make_check_flags('1.2.3', {}, ('archives',), check_last=3)
assert flags == ('--archives-only', '--last', '3')
def test_make_check_flags_with_data_check_and_last_includes_last_flag():
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flags = module.make_check_flags('1.2.3', ('data',), check_last=3)
flags = module.make_check_flags('1.2.3', {}, ('data',), check_last=3)
assert flags == ('--archives-only', '--last', '3', '--verify-data')
def test_make_check_flags_with_repository_check_and_last_omits_last_flag():
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flags = module.make_check_flags('1.2.3', ('repository',), check_last=3)
flags = module.make_check_flags('1.2.3', {}, ('repository',), check_last=3)
assert flags == ('--repository-only',)
def test_make_check_flags_with_default_checks_and_last_includes_last_flag():
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flags = module.make_check_flags('1.2.3', ('repository', 'archives'), check_last=3)
flags = module.make_check_flags('1.2.3', {}, ('repository', 'archives'), check_last=3)
assert flags == ('--last', '3')
def test_make_check_flags_with_archives_check_and_prefix_includes_match_archives_flag():
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flags = module.make_check_flags('1.2.3', ('archives',), prefix='foo-')
flags = module.make_check_flags('1.2.3', {}, ('archives',), prefix='foo-')
assert flags == ('--archives-only', '--match-archives', 'sh:foo-*')
def test_make_check_flags_with_data_check_and_prefix_includes_match_archives_flag():
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flags = module.make_check_flags('1.2.3', ('data',), prefix='foo-')
flags = module.make_check_flags('1.2.3', {}, ('data',), prefix='foo-')
assert flags == ('--archives-only', '--match-archives', 'sh:foo-*', '--verify-data')
def test_make_check_flags_with_archives_check_and_empty_prefix_omits_match_archives_flag():
def test_make_check_flags_with_archives_check_and_empty_prefix_uses_archive_name_format_instead():
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
'bar-{now}', '1.2.3' # noqa: FS003
).and_return(('--match-archives', 'sh:bar-*'))
flags = module.make_check_flags('1.2.3', ('archives',), prefix='')
flags = module.make_check_flags(
'1.2.3', {'archive_name_format': 'bar-{now}'}, ('archives',), prefix='' # noqa: FS003
)
assert flags == ('--archives-only',)
assert flags == ('--archives-only', '--match-archives', 'sh:bar-*')
def test_make_check_flags_with_archives_check_and_none_prefix_omits_match_archives_flag():
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flags = module.make_check_flags('1.2.3', ('archives',), prefix=None)
flags = module.make_check_flags('1.2.3', {}, ('archives',), prefix=None)
assert flags == ('--archives-only',)
def test_make_check_flags_with_repository_check_and_prefix_omits_match_archives_flag():
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flags = module.make_check_flags('1.2.3', ('repository',), prefix='foo-')
flags = module.make_check_flags('1.2.3', {}, ('repository',), prefix='foo-')
assert flags == ('--repository-only',)
def test_make_check_flags_with_default_checks_and_prefix_includes_match_archives_flag():
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flags = module.make_check_flags('1.2.3', ('repository', 'archives'), prefix='foo-')
flags = module.make_check_flags('1.2.3', {}, ('repository', 'archives'), prefix='foo-')
assert flags == ('--match-archives', 'sh:foo-*')
@ -427,7 +447,7 @@ def test_check_archives_calls_borg_with_parameters(checks):
'{"repository": {"id": "repo"}}'
)
flexmock(module).should_receive('make_check_flags').with_args(
'1.2.3', checks, check_last, module.DEFAULT_PREFIX
'1.2.3', {}, checks, check_last, prefix=None,
).and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg', 'check', 'repo'))
@ -581,7 +601,7 @@ def test_check_archives_with_local_path_calls_borg_via_local_path():
'{"repository": {"id": "repo"}}'
)
flexmock(module).should_receive('make_check_flags').with_args(
'1.2.3', checks, check_last, module.DEFAULT_PREFIX
'1.2.3', {}, checks, check_last, prefix=None,
).and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg1', 'check', 'repo'))
@ -608,7 +628,7 @@ def test_check_archives_with_remote_path_calls_borg_with_remote_path_parameters(
'{"repository": {"id": "repo"}}'
)
flexmock(module).should_receive('make_check_flags').with_args(
'1.2.3', checks, check_last, module.DEFAULT_PREFIX
'1.2.3', {}, checks, check_last, prefix=None,
).and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg', 'check', '--remote-path', 'borg1', 'repo'))
@ -628,6 +648,7 @@ def test_check_archives_with_remote_path_calls_borg_with_remote_path_parameters(
def test_check_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
checks = ('repository',)
check_last = flexmock()
storage_config = {'lock_wait': 5}
consistency_config = {'check_last': check_last}
flexmock(module).should_receive('parse_checks')
flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
@ -635,7 +656,7 @@ def test_check_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
'{"repository": {"id": "repo"}}'
)
flexmock(module).should_receive('make_check_flags').with_args(
'1.2.3', checks, check_last, module.DEFAULT_PREFIX
'1.2.3', storage_config, checks, check_last, None,
).and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg', 'check', '--lock-wait', '5', 'repo'))
@ -645,7 +666,7 @@ def test_check_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
module.check_archives(
repository_path='repo',
location_config={},
storage_config={'lock_wait': 5},
storage_config=storage_config,
consistency_config=consistency_config,
local_borg_version='1.2.3',
)
@ -662,7 +683,7 @@ def test_check_archives_with_retention_prefix():
'{"repository": {"id": "repo"}}'
)
flexmock(module).should_receive('make_check_flags').with_args(
'1.2.3', checks, check_last, prefix
'1.2.3', {}, checks, check_last, prefix
).and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg', 'check', 'repo'))

View File

@ -1,3 +1,4 @@
import pytest
from flexmock import flexmock
from borgmatic.borg import flags as module
@ -78,3 +79,35 @@ def test_make_repository_archive_flags_with_borg_features_joins_repository_and_a
assert module.make_repository_archive_flags(
repository_path='repo', archive='archive', local_borg_version='1.2.3'
) == ('repo::archive',)
@pytest.mark.parametrize(
'archive_name_format,feature_available,expected_result',
(
(None, True, ()),
('', True, ()),
(
'{hostname}-docs-{now}', # noqa: FS003
True,
('--match-archives', 'sh:{hostname}-docs-*'), # noqa: FS003
),
('{utcnow}-docs-{user}', True, ('--match-archives', 'sh:*-docs-{user}')), # noqa: FS003
('{fqdn}-{pid}', True, ('--match-archives', 'sh:{fqdn}-*')), # noqa: FS003
(
'stuff-{now:%Y-%m-%dT%H:%M:%S.%f}', # noqa: FS003
True,
('--match-archives', 'sh:stuff-*'),
),
('{hostname}-docs-{now}', False, ('--glob-archives', '{hostname}-docs-*')), # noqa: FS003
('{utcnow}-docs-{user}', False, ('--glob-archives', '*-docs-{user}')), # noqa: FS003
),
)
def test_make_match_archives_flags_makes_flags_with_globs(
archive_name_format, feature_available, expected_result
):
flexmock(module.feature).should_receive('available').and_return(feature_available)
assert (
module.make_match_archives_flags(archive_name_format, local_borg_version=flexmock())
== expected_result
)

View File

@ -12,6 +12,9 @@ def test_display_archives_info_calls_borg_with_parameters():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '2.3.4'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -34,6 +37,9 @@ def test_display_archives_info_with_log_info_calls_borg_with_info_parameter():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '2.3.4'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -56,6 +62,9 @@ def test_display_archives_info_with_log_info_and_json_suppresses_most_borg_outpu
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '2.3.4'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -78,6 +87,9 @@ def test_display_archives_info_with_log_debug_calls_borg_with_debug_parameter():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '2.3.4'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -101,6 +113,9 @@ def test_display_archives_info_with_log_debug_and_json_suppresses_most_borg_outp
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '2.3.4'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -123,6 +138,9 @@ def test_display_archives_info_with_json_calls_borg_with_json_parameter():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '2.3.4'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -147,6 +165,9 @@ def test_display_archives_info_with_archive_calls_borg_with_match_archives_param
flexmock(module.flags).should_receive('make_flags').with_args(
'match-archives', 'archive'
).and_return(('--match-archives', 'archive'))
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '2.3.4'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -169,6 +190,9 @@ def test_display_archives_info_with_local_path_calls_borg_via_local_path():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '2.3.4'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -195,6 +219,9 @@ def test_display_archives_info_with_remote_path_calls_borg_with_remote_path_para
flexmock(module.flags).should_receive('make_flags').with_args(
'remote-path', 'borg1'
).and_return(('--remote-path', 'borg1'))
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '2.3.4'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -221,6 +248,9 @@ def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_paramete
flexmock(module.flags).should_receive('make_flags').with_args('lock-wait', 5).and_return(
('--lock-wait', '5')
)
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '2.3.4'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
storage_config = {'lock_wait': 5}
@ -240,13 +270,16 @@ def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_paramete
)
def test_display_archives_info_with_prefix_calls_borg_with_match_archives_parameters():
def test_display_archives_info_transforms_prefix_into_match_archives_parameters():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_flags').with_args(
'match-archives', 'sh:foo*'
).and_return(('--match-archives', 'sh:foo*'))
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '2.3.4'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -265,12 +298,68 @@ def test_display_archives_info_with_prefix_calls_borg_with_match_archives_parame
)
def test_display_archives_info_prefers_prefix_over_archive_name_format():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_flags').with_args(
'match-archives', 'sh:foo*'
).and_return(('--match-archives', 'sh:foo*'))
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '2.3.4'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'info', '--match-archives', 'sh:foo*', '--repo', 'repo'),
output_log_level=module.borgmatic.logger.ANSWER,
borg_local_path='borg',
extra_environment=None,
)
module.display_archives_info(
repository_path='repo',
storage_config={'archive_name_format': 'bar-{now}'}, # noqa: FS003
local_borg_version='2.3.4',
info_arguments=flexmock(archive=None, json=False, prefix='foo'),
)
def test_display_archives_info_transforms_archive_name_format_into_match_archives_parameters():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
'bar-{now}', '2.3.4' # noqa: FS003
).and_return(('--match-archives', 'sh:bar-*'))
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'info', '--match-archives', 'sh:bar-*', '--repo', 'repo'),
output_log_level=module.borgmatic.logger.ANSWER,
borg_local_path='borg',
extra_environment=None,
)
module.display_archives_info(
repository_path='repo',
storage_config={'archive_name_format': 'bar-{now}'}, # noqa: FS003
local_borg_version='2.3.4',
info_arguments=flexmock(archive=None, json=False, prefix=None),
)
@pytest.mark.parametrize('argument_name', ('match_archives', 'sort_by', 'first', 'last'))
def test_display_archives_info_passes_through_arguments_to_borg(argument_name):
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flag_name = f"--{argument_name.replace('_', ' ')}"
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '2.3.4'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(
(flag_name, 'value')
)

View File

@ -18,18 +18,17 @@ def insert_execute_command_mock(prune_command, output_log_level):
).once()
BASE_PRUNE_FLAGS = (('--keep-daily', '1'), ('--keep-weekly', '2'), ('--keep-monthly', '3'))
BASE_PRUNE_FLAGS = ('--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly', '3')
def test_make_prune_flags_returns_flags_from_config_plus_default_prefix_glob():
def test_make_prune_flags_returns_flags_from_config():
retention_config = OrderedDict((('keep_daily', 1), ('keep_weekly', 2), ('keep_monthly', 3)))
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
result = module.make_prune_flags({}, retention_config, local_borg_version='1.2.3')
assert tuple(result) == BASE_PRUNE_FLAGS + (
('--match-archives', 'sh:{hostname}-*'), # noqa: FS003
)
assert result == BASE_PRUNE_FLAGS
def test_make_prune_flags_accepts_prefix_with_placeholders():
@ -37,15 +36,18 @@ def test_make_prune_flags_accepts_prefix_with_placeholders():
(('keep_daily', 1), ('prefix', 'Documents_{hostname}-{now}')) # noqa: FS003
)
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
result = module.make_prune_flags({}, retention_config, local_borg_version='1.2.3')
expected = (
('--keep-daily', '1'),
('--match-archives', 'sh:Documents_{hostname}-{now}*'), # noqa: FS003
'--keep-daily',
'1',
'--match-archives',
'sh:Documents_{hostname}-{now}*', # noqa: FS003
)
assert tuple(result) == expected
assert result == expected
def test_make_prune_flags_with_prefix_without_borg_features_uses_glob_archives():
@ -53,37 +55,38 @@ def test_make_prune_flags_with_prefix_without_borg_features_uses_glob_archives()
(('keep_daily', 1), ('prefix', 'Documents_{hostname}-{now}')) # noqa: FS003
)
flexmock(module.feature).should_receive('available').and_return(False)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
result = module.make_prune_flags({}, retention_config, local_borg_version='1.2.3')
expected = (
('--keep-daily', '1'),
('--glob-archives', 'Documents_{hostname}-{now}*'), # noqa: FS003
'--keep-daily',
'1',
'--glob-archives',
'Documents_{hostname}-{now}*', # noqa: FS003
)
assert tuple(result) == expected
assert result == expected
def test_make_prune_flags_treats_empty_prefix_as_no_prefix():
retention_config = OrderedDict((('keep_daily', 1), ('prefix', '')))
flexmock(module.feature).should_receive('available').and_return(True)
result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
expected = (('--keep-daily', '1'),)
assert tuple(result) == expected
def test_make_prune_flags_treats_none_prefix_as_no_prefix():
def test_make_prune_flags_without_prefix_uses_archive_name_format_instead():
storage_config = {'archive_name_format': 'bar-{now}'} # noqa: FS003
retention_config = OrderedDict((('keep_daily', 1), ('prefix', None)))
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
'bar-{now}', '1.2.3' # noqa: FS003
).and_return(('--match-archives', 'sh:bar-*'))
result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
result = module.make_prune_flags(storage_config, retention_config, local_borg_version='1.2.3')
expected = (('--keep-daily', '1'),)
expected = (
'--keep-daily',
'1',
'--match-archives',
'sh:bar-*', # noqa: FS003
)
assert tuple(result) == expected
assert result == expected
PRUNE_COMMAND = ('borg', 'prune', '--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly', '3')

View File

@ -127,6 +127,9 @@ def test_resolve_archive_name_with_lock_wait_calls_borg_with_lock_wait_parameter
def test_make_rlist_command_includes_log_info():
insert_logging_mock(logging.INFO)
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '1.2.3'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@ -143,6 +146,9 @@ def test_make_rlist_command_includes_log_info():
def test_make_rlist_command_includes_json_but_not_info():
insert_logging_mock(logging.INFO)
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '1.2.3'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@ -159,6 +165,9 @@ def test_make_rlist_command_includes_json_but_not_info():
def test_make_rlist_command_includes_log_debug():
insert_logging_mock(logging.DEBUG)
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '1.2.3'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@ -175,6 +184,9 @@ def test_make_rlist_command_includes_log_debug():
def test_make_rlist_command_includes_json_but_not_debug():
insert_logging_mock(logging.DEBUG)
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '1.2.3'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@ -190,6 +202,9 @@ def test_make_rlist_command_includes_json_but_not_debug():
def test_make_rlist_command_includes_json():
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '1.2.3'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@ -207,6 +222,9 @@ def test_make_rlist_command_includes_lock_wait():
flexmock(module.flags).should_receive('make_flags').and_return(()).and_return(
('--lock-wait', '5')
).and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '1.2.3'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@ -222,6 +240,9 @@ def test_make_rlist_command_includes_lock_wait():
def test_make_rlist_command_includes_local_path():
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '1.2.3'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@ -240,6 +261,9 @@ def test_make_rlist_command_includes_remote_path():
flexmock(module.flags).should_receive('make_flags').and_return(
('--remote-path', 'borg2')
).and_return(()).and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '1.2.3'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@ -258,6 +282,9 @@ def test_make_rlist_command_transforms_prefix_into_match_archives():
flexmock(module.flags).should_receive('make_flags').and_return(()).and_return(()).and_return(
('--match-archives', 'sh:foo*')
)
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '1.2.3'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@ -271,8 +298,47 @@ def test_make_rlist_command_transforms_prefix_into_match_archives():
assert command == ('borg', 'list', '--match-archives', 'sh:foo*', 'repo')
def test_make_rlist_command_prefers_prefix_over_archive_name_format():
flexmock(module.flags).should_receive('make_flags').and_return(()).and_return(()).and_return(
('--match-archives', 'sh:foo*')
)
flexmock(module.flags).should_receive('make_match_archives_flags').never()
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
command = module.make_rlist_command(
repository_path='repo',
storage_config={'archive_name_format': 'bar-{now}'}, # noqa: FS003
local_borg_version='1.2.3',
rlist_arguments=flexmock(archive=None, paths=None, json=False, prefix='foo'),
)
assert command == ('borg', 'list', '--match-archives', 'sh:foo*', 'repo')
def test_make_rlist_command_transforms_archive_name_format_into_match_archives():
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
'bar-{now}', '1.2.3' # noqa: FS003
).and_return(('--match-archives', 'sh:bar-*'))
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
command = module.make_rlist_command(
repository_path='repo',
storage_config={'archive_name_format': 'bar-{now}'}, # noqa: FS003
local_borg_version='1.2.3',
rlist_arguments=flexmock(archive=None, paths=None, json=False, prefix=None),
)
assert command == ('borg', 'list', '--match-archives', 'sh:bar-*', 'repo')
def test_make_rlist_command_includes_short():
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '1.2.3'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--short',))
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@ -301,6 +367,9 @@ def test_make_rlist_command_includes_short():
)
def test_make_rlist_command_includes_additional_flags(argument_name):
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
None, '1.2.3'
).and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(
(f"--{argument_name.replace('_', '-')}", 'value')
)

View File

@ -12,6 +12,7 @@ def test_transfer_archives_calls_borg_with_flags():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -41,6 +42,7 @@ def test_transfer_archives_with_dry_run_calls_borg_with_dry_run_flag():
flexmock(module.flags).should_receive('make_flags').with_args('dry-run', True).and_return(
('--dry-run',)
)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -67,6 +69,7 @@ def test_transfer_archives_with_log_info_calls_borg_with_info_flag():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -93,6 +96,7 @@ def test_transfer_archives_with_log_debug_calls_borg_with_debug_flag():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -123,6 +127,7 @@ def test_transfer_archives_with_archive_calls_borg_with_match_archives_flag():
flexmock(module.flags).should_receive('make_flags').with_args(
'match-archives', 'archive'
).and_return(('--match-archives', 'archive'))
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -137,7 +142,7 @@ def test_transfer_archives_with_archive_calls_borg_with_match_archives_flag():
module.transfer_archives(
dry_run=False,
repository_path='repo',
storage_config={},
storage_config={'archive_name_format': 'bar-{now}'}, # noqa: FS003
local_borg_version='2.3.4',
transfer_arguments=flexmock(
archive='archive', progress=None, match_archives=None, source_repository=None
@ -152,6 +157,7 @@ def test_transfer_archives_with_match_archives_calls_borg_with_match_archives_fl
flexmock(module.flags).should_receive('make_flags').with_args(
'match-archives', 'sh:foo*'
).and_return(('--match-archives', 'sh:foo*'))
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -166,7 +172,7 @@ def test_transfer_archives_with_match_archives_calls_borg_with_match_archives_fl
module.transfer_archives(
dry_run=False,
repository_path='repo',
storage_config={},
storage_config={'archive_name_format': 'bar-{now}'}, # noqa: FS003
local_borg_version='2.3.4',
transfer_arguments=flexmock(
archive=None, progress=None, match_archives='sh:foo*', source_repository=None
@ -174,10 +180,40 @@ def test_transfer_archives_with_match_archives_calls_borg_with_match_archives_fl
)
def test_transfer_archives_with_archive_name_format_calls_borg_with_match_archives_flag():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
'bar-{now}', '2.3.4' # noqa: FS003
).and_return(('--match-archives', 'sh:bar-*'))
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'transfer', '--match-archives', 'sh:bar-*', '--repo', 'repo'),
output_log_level=module.borgmatic.logger.ANSWER,
output_file=None,
borg_local_path='borg',
extra_environment=None,
)
module.transfer_archives(
dry_run=False,
repository_path='repo',
storage_config={'archive_name_format': 'bar-{now}'}, # noqa: FS003
local_borg_version='2.3.4',
transfer_arguments=flexmock(
archive=None, progress=None, match_archives=None, source_repository=None
),
)
def test_transfer_archives_with_local_path_calls_borg_via_local_path():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -208,6 +244,7 @@ def test_transfer_archives_with_remote_path_calls_borg_with_remote_path_flags():
flexmock(module.flags).should_receive('make_flags').with_args(
'remote-path', 'borg2'
).and_return(('--remote-path', 'borg2'))
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -238,6 +275,7 @@ def test_transfer_archives_with_lock_wait_calls_borg_with_lock_wait_flags():
flexmock(module.flags).should_receive('make_flags').with_args('lock-wait', 5).and_return(
('--lock-wait', '5')
)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
storage_config = {'lock_wait': 5}
@ -265,6 +303,7 @@ def test_transfer_archives_with_progress_calls_borg_with_progress_flag():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
@ -293,6 +332,7 @@ def test_transfer_archives_passes_through_arguments_to_borg(argument_name):
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flag_name = f"--{argument_name.replace('_', ' ')}"
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(
(flag_name, 'value')
)
@ -327,6 +367,7 @@ def test_transfer_archives_with_source_repository_calls_borg_with_other_repo_fla
flexmock(module.flags).should_receive('make_flags').with_args('other-repo', 'other').and_return(
('--other-repo', 'other')
)
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')

View File

@ -37,7 +37,7 @@ def test_validation_error_string_contains_errors():
assert 'uh oh' in result
def test_apply_locical_validation_raises_if_unknown_repository_in_check_repositories():
def test_apply_logical_validation_raises_if_unknown_repository_in_check_repositories():
flexmock(module).format_json_error = lambda error: error.message
with pytest.raises(module.Validation_error):
@ -51,17 +51,33 @@ def test_apply_locical_validation_raises_if_unknown_repository_in_check_reposito
)
def test_apply_locical_validation_does_not_raise_if_known_repository_in_check_repositories():
def test_apply_logical_validation_does_not_raise_if_known_repository_path_in_check_repositories():
module.apply_logical_validation(
'config.yaml',
{
'location': {'repositories': ['repo.borg', 'other.borg']},
'location': {'repositories': [{'path': 'repo.borg'}, {'path': 'other.borg'}]},
'retention': {'keep_secondly': 1000},
'consistency': {'check_repositories': ['repo.borg']},
},
)
def test_apply_logical_validation_does_not_raise_if_known_repository_label_in_check_repositories():
module.apply_logical_validation(
'config.yaml',
{
'location': {
'repositories': [
{'path': 'repo.borg', 'label': 'my_repo'},
{'path': 'other.borg', 'label': 'other_repo'},
]
},
'retention': {'keep_secondly': 1000},
'consistency': {'check_repositories': ['my_repo']},
},
)
def test_apply_logical_validation_does_not_raise_if_archive_name_format_and_prefix_present():
module.apply_logical_validation(
'config.yaml',