From 622caa0c21f97761109cb7ac3153c10280391b13 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Fri, 12 Aug 2022 14:53:20 -0700 Subject: [PATCH] Support for Borg 2's rcreate and rinfo sub-commands (#557). --- NEWS | 3 +- borgmatic/borg/check.py | 8 +- borgmatic/borg/feature.py | 8 + borgmatic/borg/info.py | 28 ++- borgmatic/borg/{init.py => rcreate.py} | 40 ++-- borgmatic/commands/arguments.py | 37 ++-- borgmatic/commands/borgmatic.py | 17 +- docs/how-to/deal-with-very-large-backups.md | 7 +- docs/how-to/inspect-your-backups.md | 13 +- docs/how-to/set-up-backups.md | 30 +-- scripts/run-full-tests | 4 +- setup.py | 2 +- tests/integration/commands/test_arguments.py | 7 + tests/unit/borg/test_check.py | 42 +++-- tests/unit/borg/test_info.py | 106 +++++++++-- tests/unit/borg/test_init.py | 132 ------------- tests/unit/borg/test_rcreate.py | 183 +++++++++++++++++++ tests/unit/commands/test_borgmatic.py | 6 +- 18 files changed, 434 insertions(+), 239 deletions(-) rename borgmatic/borg/{init.py => rcreate.py} (57%) delete mode 100644 tests/unit/borg/test_init.py create mode 100644 tests/unit/borg/test_rcreate.py diff --git a/NEWS b/NEWS index f7002a3..5615aea 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,5 @@ -1.6.7.dev0 +2.0.0.dev0 + * #557: Support for Borg 2 while still working with Borg 1. * #565: Fix handling of "repository" and "data" consistency checks to prevent invalid Borg flags. * #566: Modify "mount" and "extract" actions to require the "--repository" flag when multiple repositories are configured. diff --git a/borgmatic/borg/check.py b/borgmatic/borg/check.py index 665256f..ff3dfa9 100644 --- a/borgmatic/borg/check.py +++ b/borgmatic/borg/check.py @@ -5,7 +5,7 @@ import logging import os import pathlib -from borgmatic.borg import environment, extract, info, state +from borgmatic.borg import environment, extract, rinfo, state from borgmatic.execute import DO_NOT_CAPTURE, execute_command DEFAULT_CHECKS = ( @@ -241,6 +241,7 @@ def check_archives( location_config, storage_config, consistency_config, + local_borg_version, local_path='borg', remote_path=None, progress=None, @@ -260,10 +261,11 @@ def check_archives( ''' try: borg_repository_id = json.loads( - info.display_archives_info( + rinfo.display_repository_info( repository, storage_config, - argparse.Namespace(json=True, archive=None), + local_borg_version, + argparse.Namespace(json=True), local_path, remote_path, ) diff --git a/borgmatic/borg/feature.py b/borgmatic/borg/feature.py index af87d30..0b77eb7 100644 --- a/borgmatic/borg/feature.py +++ b/borgmatic/borg/feature.py @@ -9,6 +9,10 @@ class Feature(Enum): NOFLAGS = 3 NUMERIC_IDS = 4 UPLOAD_RATELIMIT = 5 + SEPARATE_REPOSITORY_ARCHIVE = 6 + RCREATE = 7 + RLIST = 8 + RINFO = 9 FEATURE_TO_MINIMUM_BORG_VERSION = { @@ -17,6 +21,10 @@ FEATURE_TO_MINIMUM_BORG_VERSION = { Feature.NOFLAGS: parse_version('1.2.0a8'), # borg create --noflags Feature.NUMERIC_IDS: parse_version('1.2.0b3'), # borg create/extract/mount --numeric-ids Feature.UPLOAD_RATELIMIT: parse_version('1.2.0b3'), # borg create --upload-ratelimit + Feature.SEPARATE_REPOSITORY_ARCHIVE: parse_version('2.0.0a2'), # --repo with separate archive + Feature.RCREATE: parse_version('2.0.0a2'), # borg rcreate + Feature.RLIST: parse_version('2.0.0a2'), # borg rlist + Feature.RINFO: parse_version('2.0.0a2'), # borg rinfo } diff --git a/borgmatic/borg/info.py b/borgmatic/borg/info.py index 6e783e8..9e56f0e 100644 --- a/borgmatic/borg/info.py +++ b/borgmatic/borg/info.py @@ -1,6 +1,6 @@ import logging -from borgmatic.borg import environment +from borgmatic.borg import environment, feature from borgmatic.borg.flags import make_flags, make_flags_from_arguments from borgmatic.execute import execute_command @@ -8,12 +8,17 @@ logger = logging.getLogger(__name__) def display_archives_info( - repository, storage_config, info_arguments, local_path='borg', remote_path=None + repository, + storage_config, + local_borg_version, + info_arguments, + local_path='borg', + remote_path=None, ): ''' - Given a local or remote repository path, a storage config dict, and the arguments to the info - action, display summary information for Borg archives in the repository or return JSON summary - information. + Given a local or remote repository path, a storage config dict, the local Borg version, and the + arguments to the info action, display summary information for Borg archives in the repository or + return JSON summary information. ''' lock_wait = storage_config.get('lock_wait', None) @@ -33,9 +38,16 @@ def display_archives_info( + make_flags('lock-wait', lock_wait) + make_flags_from_arguments(info_arguments, excludes=('repository', 'archive')) + ( - '::'.join((repository, info_arguments.archive)) - if info_arguments.archive - else repository, + ( + ('--repo', repository) + + (('--glob-archives', info_arguments.archive) if info_arguments.archive else ()) + ) + if feature.available(feature.Feature.SEPARATE_REPOSITORY_ARCHIVE, local_borg_version) + else ( + '::'.join((repository, info_arguments.archive)) + if info_arguments.archive + else repository, + ) ) ) diff --git a/borgmatic/borg/init.py b/borgmatic/borg/rcreate.py similarity index 57% rename from borgmatic/borg/init.py rename to borgmatic/borg/rcreate.py index 9329bc1..8d36093 100644 --- a/borgmatic/borg/init.py +++ b/borgmatic/borg/rcreate.py @@ -2,18 +2,19 @@ import argparse import logging import subprocess -from borgmatic.borg import environment, info +from borgmatic.borg import environment, feature, rinfo from borgmatic.execute import DO_NOT_CAPTURE, execute_command logger = logging.getLogger(__name__) -INFO_REPOSITORY_NOT_FOUND_EXIT_CODE = 2 +RINFO_REPOSITORY_NOT_FOUND_EXIT_CODE = 2 -def initialize_repository( +def create_repository( repository, storage_config, + local_borg_version, encryption_mode, append_only=None, storage_quota=None, @@ -21,28 +22,34 @@ def initialize_repository( remote_path=None, ): ''' - Given a local or remote repository path, a storage configuration dict, a Borg encryption mode, - whether the repository should be append-only, and the storage quota to use, initialize the - repository. If the repository already exists, then log and skip initialization. + Given a local or remote repository path, a storage configuration dict, the local Borg version, a + Borg encryption mode, whether the repository should be append-only, and the storage quota to + use, create the repository. If the repository already exists, then log and skip creation. ''' try: - info.display_archives_info( + rinfo.display_repository_info( repository, storage_config, - argparse.Namespace(json=True, archive=None), + local_borg_version, + argparse.Namespace(json=True), local_path, remote_path, ) - logger.info('Repository already exists. Skipping initialization.') + logger.info('Repository already exists. Skipping creation.') return except subprocess.CalledProcessError as error: - if error.returncode != INFO_REPOSITORY_NOT_FOUND_EXIT_CODE: + if error.returncode != RINFO_REPOSITORY_NOT_FOUND_EXIT_CODE: raise - extra_borg_options = storage_config.get('extra_borg_options', {}).get('init', '') + extra_borg_options = storage_config.get('extra_borg_options', {}).get('rcreate', '') - init_command = ( - (local_path, 'init') + rcreate_command = ( + (local_path,) + + ( + ('rcreate',) + if feature.available(feature.Feature.RCREATE, local_borg_version) + else ('init',) + ) + (('--encryption', encryption_mode) if encryption_mode else ()) + (('--append-only',) if append_only else ()) + (('--storage-quota', storage_quota) if storage_quota else ()) @@ -50,12 +57,17 @@ def initialize_repository( + (('--debug',) if logger.isEnabledFor(logging.DEBUG) else ()) + (('--remote-path', remote_path) if remote_path else ()) + (tuple(extra_borg_options.split(' ')) if extra_borg_options else ()) + + ( + ('--repo',) + if feature.available(feature.Feature.SEPARATE_REPOSITORY_ARCHIVE, local_borg_version) + else () + ) + (repository,) ) # Do not capture output here, so as to support interactive prompts. execute_command( - init_command, + rcreate_command, output_file=DO_NOT_CAPTURE, borg_local_path=local_path, extra_environment=environment.make_environment(storage_config), diff --git a/borgmatic/commands/arguments.py b/borgmatic/commands/arguments.py index 46fcae4..736f22f 100644 --- a/borgmatic/commands/arguments.py +++ b/borgmatic/commands/arguments.py @@ -4,7 +4,7 @@ from argparse import Action, ArgumentParser from borgmatic.config import collect SUBPARSER_ALIASES = { - 'init': ['--init', '-I'], + 'rcreate': ['init', '--init', '-I'], 'prune': ['--prune', '-p'], 'compact': [], 'create': ['--create', '-C'], @@ -222,33 +222,35 @@ def make_parsers(): metavar='', 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', - aliases=SUBPARSER_ALIASES['init'], - help='Initialize an empty Borg repository', - description='Initialize an empty Borg repository', + rcreate_parser = subparsers.add_parser( + 'rcreate', + aliases=SUBPARSER_ALIASES['rcreate'], + help='Create a new, empty Borg repository', + description='Create a new, empty Borg repository', add_help=False, ) - init_group = init_parser.add_argument_group('init arguments') - init_group.add_argument( + rcreate_group = rcreate_parser.add_argument_group('rcreate arguments') + rcreate_group.add_argument( '-e', '--encryption', dest='encryption_mode', help='Borg repository encryption mode', required=True, ) - init_group.add_argument( + rcreate_group.add_argument( '--append-only', dest='append_only', action='store_true', help='Create an append-only repository', ) - init_group.add_argument( + rcreate_group.add_argument( '--storage-quota', dest='storage_quota', help='Create a repository with a fixed storage quota', ) - init_group.add_argument('-h', '--help', action='help', help='Show this help message and exit') + rcreate_group.add_argument( + '-h', '--help', action='help', help='Show this help message and exit' + ) prune_parser = subparsers.add_parser( 'prune', @@ -688,11 +690,11 @@ def parse_arguments(*unparsed_arguments): if arguments['global'].excludes_filename: raise ValueError( - 'The --excludes option has been replaced with exclude_patterns in configuration' + 'The --excludes flag has been replaced with exclude_patterns in configuration' ) - if 'init' in arguments and arguments['global'].dry_run: - raise ValueError('The init action cannot be used with the --dry-run option') + if 'rcreate' in arguments and arguments['global'].dry_run: + raise ValueError('The rcreate/init action cannot be used with the --dry-run flag') if ( 'list' in arguments @@ -700,6 +702,11 @@ def parse_arguments(*unparsed_arguments): and arguments['list'].json and arguments['info'].json ): - raise ValueError('With the --json option, list and info actions cannot be used together') + raise ValueError('With the --json flag, list and info actions cannot be used together') + + if 'info' in arguments and arguments['info'].archive and arguments['info'].glob_archives: + raise ValueError( + 'With the info action, the --archive and --glob-archives flags cannot be used together' + ) return arguments diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index 2e11c97..494f41f 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -20,10 +20,10 @@ 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 rcreate as borg_rcreate from borgmatic.borg import umount as borg_umount from borgmatic.borg import version as borg_version from borgmatic.commands.arguments import parse_arguments @@ -249,14 +249,15 @@ def run_actions( 'repositories': ','.join(location['repositories']), } - if 'init' in arguments: - logger.info('{}: Initializing repository'.format(repository)) - borg_init.initialize_repository( + if 'rcreate' in arguments: + logger.info('{}: Creating repository'.format(repository)) + borg_rcreate.create_repository( repository, storage, - arguments['init'].encryption_mode, - arguments['init'].append_only, - arguments['init'].storage_quota, + local_borg_version, + arguments['rcreate'].encryption_mode, + arguments['rcreate'].append_only, + arguments['rcreate'].storage_quota, local_path=local_path, remote_path=remote_path, ) @@ -396,6 +397,7 @@ def run_actions( location, storage, consistency, + local_borg_version, local_path=local_path, remote_path=remote_path, progress=arguments['check'].progress, @@ -624,6 +626,7 @@ def run_actions( json_output = borg_info.display_archives_info( repository, storage, + local_borg_version, info_arguments=info_arguments, local_path=local_path, remote_path=remote_path, diff --git a/docs/how-to/deal-with-very-large-backups.md b/docs/how-to/deal-with-very-large-backups.md index aa67f1f..8318ebc 100644 --- a/docs/how-to/deal-with-very-large-backups.md +++ b/docs/how-to/deal-with-very-large-backups.md @@ -27,9 +27,6 @@ borgmatic create borgmatic check ``` -(No borgmatic `prune`, `create`, or `check` actions? Try the old-style -`--prune`, `--create`, or `--check`. Or upgrade borgmatic!) - 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 @@ -70,7 +67,9 @@ Here are the available checks from fastest to slowest: * `extract`: Performs an extraction dry-run of the most recent archive. * `data`: Verifies the data integrity of all archives contents, decrypting and decompressing all data (implies `archives` as well). -See [Borg's check documentation](https://borgbackup.readthedocs.io/en/stable/usage/check.html) for more information. +See [Borg's check +documentation](https://borgbackup.readthedocs.io/en/stable/usage/check.html) +for more information. ### Check frequency diff --git a/docs/how-to/inspect-your-backups.md b/docs/how-to/inspect-your-backups.md index 318eb6e..b266745 100644 --- a/docs/how-to/inspect-your-backups.md +++ b/docs/how-to/inspect-your-backups.md @@ -37,19 +37,22 @@ borgmatic --stats ## Existing backups borgmatic provides convenient actions for Borg's -[list](https://borgbackup.readthedocs.io/en/stable/usage/list.html) and -[info](https://borgbackup.readthedocs.io/en/stable/usage/info.html) +[`list`](https://borgbackup.readthedocs.io/en/stable/usage/list.html) and +[`info`](https://borgbackup.readthedocs.io/en/stable/usage/info.html) functionality: - ```bash borgmatic list borgmatic info ``` -(No borgmatic `list` or `info` actions? Try the old-style `--list` or -`--info`. Or upgrade borgmatic!) +New in borgmatic version 2.0.0 +There's also an `rinfo` action for displaying repository information with Borg +2.x: +```bash +borgmatic rinfo +``` ### Searching for a file diff --git a/docs/how-to/set-up-backups.md b/docs/how-to/set-up-backups.md index 4206f6c..6508cd6 100644 --- a/docs/how-to/set-up-backups.md +++ b/docs/how-to/set-up-backups.md @@ -186,32 +186,36 @@ files via configuration management, or you want to double check that your hand edits are valid. -## Initialization +## Repository creation -Before you can create backups with borgmatic, you first need to initialize a -Borg repository so you have a destination for your backup archives. (But skip -this step if you already have a Borg repository.) To create a repository, run -a command like the following: +Before you can create backups with borgmatic, you first need to create a Borg +repository so you have a destination for your backup archives. (But skip this +step if you already have a Borg repository.) To create a repository, run a +command like the following with Borg 1.x: ```bash sudo borgmatic init --encryption repokey ``` -(No borgmatic `init` action? Try the old-style `--init` flag, or upgrade -borgmatic!) +New in borgmatic version 2.0.0 +Or, with Borg 2.x: + +```bash +sudo borgmatic rcreate --encryption repokey-aes-ocb +``` This uses the borgmatic configuration file you created above to determine which local or remote repository to create, and encrypts it with the encryption passphrase specified there if one is provided. Read about [Borg encryption -modes](https://borgbackup.readthedocs.io/en/stable/usage/init.html#encryption-modes) +modes](https://borgbackup.readthedocs.io/en/stable/usage/init.html#encryption-mode-tldr) for the menu of available encryption modes. Also, optionally check out the [Borg Quick Start](https://borgbackup.readthedocs.org/en/stable/quickstart.html) for more -background about repository initialization. +background about repository creation. -Note that borgmatic skips repository initialization if the repository already +Note that borgmatic skips repository creation if the repository already exists. This supports use cases like ensuring a repository exists prior to performing a backup. @@ -221,8 +225,8 @@ key-based SSH access to the desired user account on the remote host. ## Backups -Now that you've configured borgmatic and initialized a repository, it's a -good idea to test that borgmatic is working. So to run borgmatic and start a +Now that you've configured borgmatic and created a repository, it's a good +idea to test that borgmatic is working. So to run borgmatic and start a backup, you can invoke it like this: ```bash @@ -230,7 +234,7 @@ sudo borgmatic create --verbosity 1 --files --stats ``` (No borgmatic `--files` flag? It's only present in newer versions of -borgmatic. So try leaving it out, or upgrade borgmatic!) +borgmatic. So try leaving it out or upgrade borgmatic!) The `--verbosity` flag makes borgmatic show the steps it's performing. The `--files` flag lists each file that's new or changed since the last backup. diff --git a/scripts/run-full-tests b/scripts/run-full-tests index a54e0ab..993c5bd 100755 --- a/scripts/run-full-tests +++ b/scripts/run-full-tests @@ -14,8 +14,8 @@ apk add --no-cache python3 py3-pip borgbackup postgresql-client mariadb-client m py3-ruamel.yaml py3-ruamel.yaml.clib bash # If certain dependencies of black are available in this version of Alpine, install them. apk add --no-cache py3-typed-ast py3-regex || true -python3 -m pip install --no-cache --upgrade pip==22.0.3 setuptools==60.8.1 -pip3 install tox==3.24.5 +python3 -m pip install --no-cache --upgrade pip==22.2.2 setuptools==64.0.1 +pip3 install --ignore-installed tox==3.25.1 export COVERAGE_FILE=/tmp/.coverage tox --workdir /tmp/.tox --sitepackages tox --workdir /tmp/.tox --sitepackages -e end-to-end diff --git a/setup.py b/setup.py index 4b2c6ee..e577a25 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import find_packages, setup -VERSION = '1.6.7.dev0' +VERSION = '2.0.0.dev0' setup( diff --git a/tests/integration/commands/test_arguments.py b/tests/integration/commands/test_arguments.py index e13a31b..5eb5101 100644 --- a/tests/integration/commands/test_arguments.py +++ b/tests/integration/commands/test_arguments.py @@ -496,6 +496,13 @@ def test_parse_arguments_disallows_json_with_both_list_and_info(): module.parse_arguments('list', 'info', '--json') +def test_parse_arguments_disallows_info_with_both_archive_and_glob_archives(): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + + with pytest.raises(ValueError): + module.parse_arguments('info', '--archive', 'foo', '--glob-archives', '*bar') + + def test_parse_arguments_check_only_extract_does_not_raise_extract_subparser_error(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) diff --git a/tests/unit/borg/test_check.py b/tests/unit/borg/test_check.py index 94a9133..d56d31b 100644 --- a/tests/unit/borg/test_check.py +++ b/tests/unit/borg/test_check.py @@ -296,7 +296,7 @@ def test_check_archives_with_progress_calls_borg_with_progress_parameter(): consistency_config = {'check_last': None} flexmock(module).should_receive('parse_checks') flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks) - flexmock(module.info).should_receive('display_archives_info').and_return( + flexmock(module.rinfo).should_receive('display_repository_info').and_return( '{"repository": {"id": "repo"}}' ) flexmock(module).should_receive('make_check_flags').and_return(()) @@ -315,6 +315,7 @@ def test_check_archives_with_progress_calls_borg_with_progress_parameter(): location_config={}, storage_config={}, consistency_config=consistency_config, + local_borg_version='1.2.3', progress=True, ) @@ -324,7 +325,7 @@ def test_check_archives_with_repair_calls_borg_with_repair_parameter(): consistency_config = {'check_last': None} flexmock(module).should_receive('parse_checks') flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks) - flexmock(module.info).should_receive('display_archives_info').and_return( + flexmock(module.rinfo).should_receive('display_repository_info').and_return( '{"repository": {"id": "repo"}}' ) flexmock(module).should_receive('make_check_flags').and_return(()) @@ -343,6 +344,7 @@ def test_check_archives_with_repair_calls_borg_with_repair_parameter(): location_config={}, storage_config={}, consistency_config=consistency_config, + local_borg_version='1.2.3', repair=True, ) @@ -361,7 +363,7 @@ def test_check_archives_calls_borg_with_parameters(checks): consistency_config = {'check_last': check_last} flexmock(module).should_receive('parse_checks') flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks) - flexmock(module.info).should_receive('display_archives_info').and_return( + flexmock(module.rinfo).should_receive('display_repository_info').and_return( '{"repository": {"id": "repo"}}' ) flexmock(module).should_receive('make_check_flags').with_args( @@ -376,6 +378,7 @@ def test_check_archives_calls_borg_with_parameters(checks): location_config={}, storage_config={}, consistency_config=consistency_config, + local_borg_version='1.2.3', ) @@ -385,7 +388,7 @@ def test_check_archives_with_json_error_raises(): consistency_config = {'check_last': check_last} flexmock(module).should_receive('parse_checks') flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks) - flexmock(module.info).should_receive('display_archives_info').and_return( + flexmock(module.rinfo).should_receive('display_repository_info').and_return( '{"unexpected": {"id": "repo"}}' ) @@ -395,6 +398,7 @@ def test_check_archives_with_json_error_raises(): location_config={}, storage_config={}, consistency_config=consistency_config, + local_borg_version='1.2.3', ) @@ -404,7 +408,7 @@ def test_check_archives_with_missing_json_keys_raises(): consistency_config = {'check_last': check_last} flexmock(module).should_receive('parse_checks') flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks) - flexmock(module.info).should_receive('display_archives_info').and_return('{invalid JSON') + flexmock(module.rinfo).should_receive('display_repository_info').and_return('{invalid JSON') with pytest.raises(ValueError): module.check_archives( @@ -412,6 +416,7 @@ def test_check_archives_with_missing_json_keys_raises(): location_config={}, storage_config={}, consistency_config=consistency_config, + local_borg_version='1.2.3', ) @@ -421,7 +426,7 @@ def test_check_archives_with_extract_check_calls_extract_only(): consistency_config = {'check_last': check_last} flexmock(module).should_receive('parse_checks') flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks) - flexmock(module.info).should_receive('display_archives_info').and_return( + flexmock(module.rinfo).should_receive('display_repository_info').and_return( '{"repository": {"id": "repo"}}' ) flexmock(module).should_receive('make_check_flags').never() @@ -434,6 +439,7 @@ def test_check_archives_with_extract_check_calls_extract_only(): location_config={}, storage_config={}, consistency_config=consistency_config, + local_borg_version='1.2.3', ) @@ -442,7 +448,7 @@ def test_check_archives_with_log_info_calls_borg_with_info_parameter(): consistency_config = {'check_last': None} flexmock(module).should_receive('parse_checks') flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks) - flexmock(module.info).should_receive('display_archives_info').and_return( + flexmock(module.rinfo).should_receive('display_repository_info').and_return( '{"repository": {"id": "repo"}}' ) flexmock(module).should_receive('make_check_flags').and_return(()) @@ -456,6 +462,7 @@ def test_check_archives_with_log_info_calls_borg_with_info_parameter(): location_config={}, storage_config={}, consistency_config=consistency_config, + local_borg_version='1.2.3', ) @@ -464,7 +471,7 @@ def test_check_archives_with_log_debug_calls_borg_with_debug_parameter(): consistency_config = {'check_last': None} flexmock(module).should_receive('parse_checks') flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks) - flexmock(module.info).should_receive('display_archives_info').and_return( + flexmock(module.rinfo).should_receive('display_repository_info').and_return( '{"repository": {"id": "repo"}}' ) flexmock(module).should_receive('make_check_flags').and_return(()) @@ -478,6 +485,7 @@ def test_check_archives_with_log_debug_calls_borg_with_debug_parameter(): location_config={}, storage_config={}, consistency_config=consistency_config, + local_borg_version='1.2.3', ) @@ -485,7 +493,7 @@ def test_check_archives_without_any_checks_bails(): consistency_config = {'check_last': None} flexmock(module).should_receive('parse_checks') flexmock(module).should_receive('filter_checks_on_frequency').and_return(()) - flexmock(module.info).should_receive('display_archives_info').and_return( + flexmock(module.rinfo).should_receive('display_repository_info').and_return( '{"repository": {"id": "repo"}}' ) insert_execute_command_never() @@ -495,6 +503,7 @@ def test_check_archives_without_any_checks_bails(): location_config={}, storage_config={}, consistency_config=consistency_config, + local_borg_version='1.2.3', ) @@ -504,7 +513,7 @@ def test_check_archives_with_local_path_calls_borg_via_local_path(): consistency_config = {'check_last': check_last} flexmock(module).should_receive('parse_checks') flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks) - flexmock(module.info).should_receive('display_archives_info').and_return( + flexmock(module.rinfo).should_receive('display_repository_info').and_return( '{"repository": {"id": "repo"}}' ) flexmock(module).should_receive('make_check_flags').with_args( @@ -519,6 +528,7 @@ def test_check_archives_with_local_path_calls_borg_via_local_path(): location_config={}, storage_config={}, consistency_config=consistency_config, + local_borg_version='1.2.3', local_path='borg1', ) @@ -529,7 +539,7 @@ def test_check_archives_with_remote_path_calls_borg_with_remote_path_parameters( consistency_config = {'check_last': check_last} flexmock(module).should_receive('parse_checks') flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks) - flexmock(module.info).should_receive('display_archives_info').and_return( + flexmock(module.rinfo).should_receive('display_repository_info').and_return( '{"repository": {"id": "repo"}}' ) flexmock(module).should_receive('make_check_flags').with_args( @@ -544,6 +554,7 @@ def test_check_archives_with_remote_path_calls_borg_with_remote_path_parameters( location_config={}, storage_config={}, consistency_config=consistency_config, + local_borg_version='1.2.3', remote_path='borg1', ) @@ -554,7 +565,7 @@ def test_check_archives_with_lock_wait_calls_borg_with_lock_wait_parameters(): consistency_config = {'check_last': check_last} flexmock(module).should_receive('parse_checks') flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks) - flexmock(module.info).should_receive('display_archives_info').and_return( + flexmock(module.rinfo).should_receive('display_repository_info').and_return( '{"repository": {"id": "repo"}}' ) flexmock(module).should_receive('make_check_flags').with_args( @@ -569,6 +580,7 @@ def test_check_archives_with_lock_wait_calls_borg_with_lock_wait_parameters(): location_config={}, storage_config={'lock_wait': 5}, consistency_config=consistency_config, + local_borg_version='1.2.3', ) @@ -579,7 +591,7 @@ def test_check_archives_with_retention_prefix(): consistency_config = {'check_last': check_last, 'prefix': prefix} flexmock(module).should_receive('parse_checks') flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks) - flexmock(module.info).should_receive('display_archives_info').and_return( + flexmock(module.rinfo).should_receive('display_repository_info').and_return( '{"repository": {"id": "repo"}}' ) flexmock(module).should_receive('make_check_flags').with_args( @@ -594,6 +606,7 @@ def test_check_archives_with_retention_prefix(): location_config={}, storage_config={}, consistency_config=consistency_config, + local_borg_version='1.2.3', ) @@ -602,7 +615,7 @@ def test_check_archives_with_extra_borg_options_calls_borg_with_extra_options(): consistency_config = {'check_last': None} flexmock(module).should_receive('parse_checks') flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks) - flexmock(module.info).should_receive('display_archives_info').and_return( + flexmock(module.rinfo).should_receive('display_repository_info').and_return( '{"repository": {"id": "repo"}}' ) flexmock(module).should_receive('make_check_flags').and_return(()) @@ -615,4 +628,5 @@ def test_check_archives_with_extra_borg_options_calls_borg_with_extra_options(): location_config={}, storage_config={'extra_borg_options': {'check': '--extra --options'}}, consistency_config=consistency_config, + local_borg_version='1.2.3', ) diff --git a/tests/unit/borg/test_info.py b/tests/unit/borg/test_info.py index b91f560..8817bb7 100644 --- a/tests/unit/borg/test_info.py +++ b/tests/unit/borg/test_info.py @@ -9,6 +9,25 @@ from ..test_verbosity import insert_logging_mock def test_display_archives_info_calls_borg_with_parameters(): + flexmock(module.feature).should_receive('available').and_return(True) + flexmock(module.environment).should_receive('make_environment') + flexmock(module).should_receive('execute_command').with_args( + ('borg', 'info', '--repo', 'repo'), + output_log_level=logging.WARNING, + borg_local_path='borg', + extra_environment=None, + ) + + module.display_archives_info( + repository='repo', + storage_config={}, + local_borg_version='2.3.4', + info_arguments=flexmock(archive=None, json=False), + ) + + +def test_display_archives_info_without_borg_features_calls_borg_without_repo_flag(): + flexmock(module.feature).should_receive('available').and_return(False) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( ('borg', 'info', 'repo'), @@ -18,28 +37,36 @@ def test_display_archives_info_calls_borg_with_parameters(): ) module.display_archives_info( - repository='repo', storage_config={}, info_arguments=flexmock(archive=None, json=False) + repository='repo', + storage_config={}, + local_borg_version='2.3.4', + info_arguments=flexmock(archive=None, json=False), ) def test_display_archives_info_with_log_info_calls_borg_with_info_parameter(): + flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( - ('borg', 'info', '--info', 'repo'), + ('borg', 'info', '--info', '--repo', 'repo'), output_log_level=logging.WARNING, borg_local_path='borg', extra_environment=None, ) insert_logging_mock(logging.INFO) module.display_archives_info( - repository='repo', storage_config={}, info_arguments=flexmock(archive=None, json=False) + repository='repo', + storage_config={}, + local_borg_version='2.3.4', + info_arguments=flexmock(archive=None, json=False), ) def test_display_archives_info_with_log_info_and_json_suppresses_most_borg_output(): + flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( - ('borg', 'info', '--json', 'repo'), + ('borg', 'info', '--json', '--repo', 'repo'), output_log_level=None, borg_local_path='borg', extra_environment=None, @@ -47,16 +74,20 @@ def test_display_archives_info_with_log_info_and_json_suppresses_most_borg_outpu insert_logging_mock(logging.INFO) json_output = module.display_archives_info( - repository='repo', storage_config={}, info_arguments=flexmock(archive=None, json=True) + repository='repo', + storage_config={}, + local_borg_version='2.3.4', + info_arguments=flexmock(archive=None, json=True), ) assert json_output == '[]' def test_display_archives_info_with_log_debug_calls_borg_with_debug_parameter(): + flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( - ('borg', 'info', '--debug', '--show-rc', 'repo'), + ('borg', 'info', '--debug', '--show-rc', '--repo', 'repo'), output_log_level=logging.WARNING, borg_local_path='borg', extra_environment=None, @@ -64,14 +95,18 @@ def test_display_archives_info_with_log_debug_calls_borg_with_debug_parameter(): insert_logging_mock(logging.DEBUG) module.display_archives_info( - repository='repo', storage_config={}, info_arguments=flexmock(archive=None, json=False) + repository='repo', + storage_config={}, + local_borg_version='2.3.4', + info_arguments=flexmock(archive=None, json=False), ) def test_display_archives_info_with_log_debug_and_json_suppresses_most_borg_output(): + flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( - ('borg', 'info', '--json', 'repo'), + ('borg', 'info', '--json', '--repo', 'repo'), output_log_level=None, borg_local_path='borg', extra_environment=None, @@ -79,29 +114,55 @@ def test_display_archives_info_with_log_debug_and_json_suppresses_most_borg_outp insert_logging_mock(logging.DEBUG) json_output = module.display_archives_info( - repository='repo', storage_config={}, info_arguments=flexmock(archive=None, json=True) + repository='repo', + storage_config={}, + local_borg_version='2.3.4', + info_arguments=flexmock(archive=None, json=True), ) assert json_output == '[]' def test_display_archives_info_with_json_calls_borg_with_json_parameter(): + flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( - ('borg', 'info', '--json', 'repo'), + ('borg', 'info', '--json', '--repo', 'repo'), output_log_level=None, borg_local_path='borg', extra_environment=None, ).and_return('[]') json_output = module.display_archives_info( - repository='repo', storage_config={}, info_arguments=flexmock(archive=None, json=True) + repository='repo', + storage_config={}, + local_borg_version='2.3.4', + info_arguments=flexmock(archive=None, json=True), ) assert json_output == '[]' -def test_display_archives_info_with_archive_calls_borg_with_archive_parameter(): +def test_display_archives_info_with_archive_calls_borg_with_glob_archives_parameter(): + flexmock(module.feature).should_receive('available').and_return(True) + flexmock(module.environment).should_receive('make_environment') + flexmock(module).should_receive('execute_command').with_args( + ('borg', 'info', '--repo', 'repo', '--glob-archives', 'archive'), + output_log_level=logging.WARNING, + borg_local_path='borg', + extra_environment=None, + ) + + module.display_archives_info( + repository='repo', + storage_config={}, + local_borg_version='2.3.4', + info_arguments=flexmock(archive='archive', json=False), + ) + + +def test_display_archives_info_with_archive_and_without_borg_features_calls_borg_with_repo_archive_parameter(): + flexmock(module.feature).should_receive('available').and_return(False) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( ('borg', 'info', 'repo::archive'), @@ -111,14 +172,18 @@ def test_display_archives_info_with_archive_calls_borg_with_archive_parameter(): ) module.display_archives_info( - repository='repo', storage_config={}, info_arguments=flexmock(archive='archive', json=False) + repository='repo', + storage_config={}, + local_borg_version='2.3.4', + info_arguments=flexmock(archive='archive', json=False), ) def test_display_archives_info_with_local_path_calls_borg_via_local_path(): + flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( - ('borg1', 'info', 'repo'), + ('borg1', 'info', '--repo', 'repo'), output_log_level=logging.WARNING, borg_local_path='borg1', extra_environment=None, @@ -127,15 +192,17 @@ def test_display_archives_info_with_local_path_calls_borg_via_local_path(): module.display_archives_info( repository='repo', storage_config={}, + local_borg_version='2.3.4', info_arguments=flexmock(archive=None, json=False), local_path='borg1', ) def test_display_archives_info_with_remote_path_calls_borg_with_remote_path_parameters(): + flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( - ('borg', 'info', '--remote-path', 'borg1', 'repo'), + ('borg', 'info', '--remote-path', 'borg1', '--repo', 'repo'), output_log_level=logging.WARNING, borg_local_path='borg', extra_environment=None, @@ -144,6 +211,7 @@ def test_display_archives_info_with_remote_path_calls_borg_with_remote_path_para module.display_archives_info( repository='repo', storage_config={}, + local_borg_version='2.3.4', info_arguments=flexmock(archive=None, json=False), remote_path='borg1', ) @@ -151,9 +219,10 @@ def test_display_archives_info_with_remote_path_calls_borg_with_remote_path_para def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_parameters(): storage_config = {'lock_wait': 5} + flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( - ('borg', 'info', '--lock-wait', '5', 'repo'), + ('borg', 'info', '--lock-wait', '5', '--repo', 'repo'), output_log_level=logging.WARNING, borg_local_path='borg', extra_environment=None, @@ -162,15 +231,17 @@ def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_paramete module.display_archives_info( repository='repo', storage_config=storage_config, + local_borg_version='2.3.4', info_arguments=flexmock(archive=None, json=False), ) @pytest.mark.parametrize('argument_name', ('prefix', 'glob_archives', 'sort_by', 'first', 'last')) def test_display_archives_info_passes_through_arguments_to_borg(argument_name): + flexmock(module.feature).should_receive('available').and_return(True) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( - ('borg', 'info', '--' + argument_name.replace('_', '-'), 'value', 'repo'), + ('borg', 'info', '--' + argument_name.replace('_', '-'), 'value', '--repo', 'repo'), output_log_level=logging.WARNING, borg_local_path='borg', extra_environment=None, @@ -179,5 +250,6 @@ def test_display_archives_info_passes_through_arguments_to_borg(argument_name): module.display_archives_info( repository='repo', storage_config={}, + local_borg_version='2.3.4', info_arguments=flexmock(archive=None, json=False, **{argument_name: 'value'}), ) diff --git a/tests/unit/borg/test_init.py b/tests/unit/borg/test_init.py deleted file mode 100644 index fb81f34..0000000 --- a/tests/unit/borg/test_init.py +++ /dev/null @@ -1,132 +0,0 @@ -import logging -import subprocess - -import pytest -from flexmock import flexmock - -from borgmatic.borg import init as module - -from ..test_verbosity import insert_logging_mock - -INFO_SOME_UNKNOWN_EXIT_CODE = -999 -INIT_COMMAND = ('borg', 'init', '--encryption', 'repokey') - - -def insert_info_command_found_mock(): - flexmock(module.info).should_receive('display_archives_info') - - -def insert_info_command_not_found_mock(): - flexmock(module.info).should_receive('display_archives_info').and_raise( - subprocess.CalledProcessError(module.INFO_REPOSITORY_NOT_FOUND_EXIT_CODE, []) - ) - - -def insert_init_command_mock(init_command, **kwargs): - flexmock(module.environment).should_receive('make_environment') - flexmock(module).should_receive('execute_command').with_args( - init_command, - output_file=module.DO_NOT_CAPTURE, - borg_local_path=init_command[0], - extra_environment=None, - ).once() - - -def test_initialize_repository_calls_borg_with_parameters(): - insert_info_command_not_found_mock() - insert_init_command_mock(INIT_COMMAND + ('repo',)) - - module.initialize_repository(repository='repo', storage_config={}, encryption_mode='repokey') - - -def test_initialize_repository_raises_for_borg_init_error(): - insert_info_command_not_found_mock() - flexmock(module.environment).should_receive('make_environment') - flexmock(module).should_receive('execute_command').and_raise( - module.subprocess.CalledProcessError(2, 'borg init') - ) - - with pytest.raises(subprocess.CalledProcessError): - module.initialize_repository( - repository='repo', storage_config={}, encryption_mode='repokey' - ) - - -def test_initialize_repository_skips_initialization_when_repository_already_exists(): - insert_info_command_found_mock() - - module.initialize_repository(repository='repo', storage_config={}, encryption_mode='repokey') - - -def test_initialize_repository_raises_for_unknown_info_command_error(): - flexmock(module.info).should_receive('display_archives_info').and_raise( - subprocess.CalledProcessError(INFO_SOME_UNKNOWN_EXIT_CODE, []) - ) - - with pytest.raises(subprocess.CalledProcessError): - module.initialize_repository( - repository='repo', storage_config={}, encryption_mode='repokey' - ) - - -def test_initialize_repository_with_append_only_calls_borg_with_append_only_parameter(): - insert_info_command_not_found_mock() - insert_init_command_mock(INIT_COMMAND + ('--append-only', 'repo')) - - module.initialize_repository( - repository='repo', storage_config={}, encryption_mode='repokey', append_only=True - ) - - -def test_initialize_repository_with_storage_quota_calls_borg_with_storage_quota_parameter(): - insert_info_command_not_found_mock() - insert_init_command_mock(INIT_COMMAND + ('--storage-quota', '5G', 'repo')) - - module.initialize_repository( - repository='repo', storage_config={}, encryption_mode='repokey', storage_quota='5G' - ) - - -def test_initialize_repository_with_log_info_calls_borg_with_info_parameter(): - insert_info_command_not_found_mock() - insert_init_command_mock(INIT_COMMAND + ('--info', 'repo')) - insert_logging_mock(logging.INFO) - - module.initialize_repository(repository='repo', storage_config={}, encryption_mode='repokey') - - -def test_initialize_repository_with_log_debug_calls_borg_with_debug_parameter(): - insert_info_command_not_found_mock() - insert_init_command_mock(INIT_COMMAND + ('--debug', 'repo')) - insert_logging_mock(logging.DEBUG) - - module.initialize_repository(repository='repo', storage_config={}, encryption_mode='repokey') - - -def test_initialize_repository_with_local_path_calls_borg_via_local_path(): - insert_info_command_not_found_mock() - insert_init_command_mock(('borg1',) + INIT_COMMAND[1:] + ('repo',)) - - module.initialize_repository( - repository='repo', storage_config={}, encryption_mode='repokey', local_path='borg1' - ) - - -def test_initialize_repository_with_remote_path_calls_borg_with_remote_path_parameter(): - insert_info_command_not_found_mock() - insert_init_command_mock(INIT_COMMAND + ('--remote-path', 'borg1', 'repo')) - - module.initialize_repository( - repository='repo', storage_config={}, encryption_mode='repokey', remote_path='borg1' - ) - - -def test_initialize_repository_with_extra_borg_options_calls_borg_with_extra_options(): - insert_info_command_not_found_mock() - insert_init_command_mock(INIT_COMMAND + ('--extra', '--options', 'repo')) - - module.initialize_repository( - repository='repo', - storage_config={'extra_borg_options': {'init': '--extra --options'}}, - encryption_mode='repokey', - ) diff --git a/tests/unit/borg/test_rcreate.py b/tests/unit/borg/test_rcreate.py new file mode 100644 index 0000000..652de7b --- /dev/null +++ b/tests/unit/borg/test_rcreate.py @@ -0,0 +1,183 @@ +import logging +import subprocess + +import pytest +from flexmock import flexmock + +from borgmatic.borg import rcreate as module + +from ..test_verbosity import insert_logging_mock + +RINFO_SOME_UNKNOWN_EXIT_CODE = -999 +RCREATE_COMMAND = ('borg', 'rcreate', '--encryption', 'repokey') + + +def insert_rinfo_command_found_mock(): + flexmock(module.rinfo).should_receive('display_repository_info') + + +def insert_rinfo_command_not_found_mock(): + flexmock(module.rinfo).should_receive('display_repository_info').and_raise( + subprocess.CalledProcessError(module.RINFO_REPOSITORY_NOT_FOUND_EXIT_CODE, []) + ) + + +def insert_rcreate_command_mock(rcreate_command, **kwargs): + flexmock(module.environment).should_receive('make_environment') + flexmock(module).should_receive('execute_command').with_args( + rcreate_command, + output_file=module.DO_NOT_CAPTURE, + borg_local_path=rcreate_command[0], + extra_environment=None, + ).once() + + +def test_create_repository_calls_borg_with_parameters(): + insert_rinfo_command_not_found_mock() + insert_rcreate_command_mock(RCREATE_COMMAND + ('--repo', 'repo')) + flexmock(module.feature).should_receive('available').and_return(True) + + module.create_repository( + repository='repo', storage_config={}, local_borg_version='2.3.4', encryption_mode='repokey' + ) + + +def test_create_repository_without_borg_features_calls_borg_with_init_sub_command(): + insert_rinfo_command_not_found_mock() + insert_rcreate_command_mock(('borg', 'init', '--encryption', 'repokey', 'repo')) + flexmock(module.feature).should_receive('available').and_return(False) + + module.create_repository( + repository='repo', storage_config={}, local_borg_version='2.3.4', encryption_mode='repokey' + ) + + +def test_create_repository_raises_for_borg_rcreate_error(): + insert_rinfo_command_not_found_mock() + flexmock(module.feature).should_receive('available').and_return(True) + flexmock(module.environment).should_receive('make_environment') + flexmock(module).should_receive('execute_command').and_raise( + module.subprocess.CalledProcessError(2, 'borg rcreate') + ) + + with pytest.raises(subprocess.CalledProcessError): + module.create_repository( + repository='repo', + storage_config={}, + local_borg_version='2.3.4', + encryption_mode='repokey', + ) + + +def test_create_repository_skips_creation_when_repository_already_exists(): + insert_rinfo_command_found_mock() + flexmock(module.feature).should_receive('available').and_return(True) + + module.create_repository( + repository='repo', storage_config={}, local_borg_version='2.3.4', encryption_mode='repokey' + ) + + +def test_create_repository_raises_for_unknown_rinfo_command_error(): + flexmock(module.rinfo).should_receive('display_repository_info').and_raise( + subprocess.CalledProcessError(RINFO_SOME_UNKNOWN_EXIT_CODE, []) + ) + + with pytest.raises(subprocess.CalledProcessError): + module.create_repository( + repository='repo', + storage_config={}, + local_borg_version='2.3.4', + encryption_mode='repokey', + ) + + +def test_create_repository_with_append_only_calls_borg_with_append_only_parameter(): + insert_rinfo_command_not_found_mock() + insert_rcreate_command_mock(RCREATE_COMMAND + ('--append-only', '--repo', 'repo')) + flexmock(module.feature).should_receive('available').and_return(True) + + module.create_repository( + repository='repo', + storage_config={}, + local_borg_version='2.3.4', + encryption_mode='repokey', + append_only=True, + ) + + +def test_create_repository_with_storage_quota_calls_borg_with_storage_quota_parameter(): + insert_rinfo_command_not_found_mock() + insert_rcreate_command_mock(RCREATE_COMMAND + ('--storage-quota', '5G', '--repo', 'repo')) + flexmock(module.feature).should_receive('available').and_return(True) + + module.create_repository( + repository='repo', + storage_config={}, + local_borg_version='2.3.4', + encryption_mode='repokey', + storage_quota='5G', + ) + + +def test_create_repository_with_log_info_calls_borg_with_info_parameter(): + insert_rinfo_command_not_found_mock() + insert_rcreate_command_mock(RCREATE_COMMAND + ('--info', '--repo', 'repo')) + insert_logging_mock(logging.INFO) + flexmock(module.feature).should_receive('available').and_return(True) + + module.create_repository( + repository='repo', storage_config={}, local_borg_version='2.3.4', encryption_mode='repokey' + ) + + +def test_create_repository_with_log_debug_calls_borg_with_debug_parameter(): + insert_rinfo_command_not_found_mock() + insert_rcreate_command_mock(RCREATE_COMMAND + ('--debug', '--repo', 'repo')) + insert_logging_mock(logging.DEBUG) + flexmock(module.feature).should_receive('available').and_return(True) + + module.create_repository( + repository='repo', storage_config={}, local_borg_version='2.3.4', encryption_mode='repokey' + ) + + +def test_create_repository_with_local_path_calls_borg_via_local_path(): + insert_rinfo_command_not_found_mock() + insert_rcreate_command_mock(('borg1',) + RCREATE_COMMAND[1:] + ('--repo', 'repo')) + flexmock(module.feature).should_receive('available').and_return(True) + + module.create_repository( + repository='repo', + storage_config={}, + local_borg_version='2.3.4', + encryption_mode='repokey', + local_path='borg1', + ) + + +def test_create_repository_with_remote_path_calls_borg_with_remote_path_parameter(): + insert_rinfo_command_not_found_mock() + insert_rcreate_command_mock(RCREATE_COMMAND + ('--remote-path', 'borg1', '--repo', 'repo')) + flexmock(module.feature).should_receive('available').and_return(True) + + module.create_repository( + repository='repo', + storage_config={}, + local_borg_version='2.3.4', + encryption_mode='repokey', + remote_path='borg1', + ) + + +def test_create_repository_with_extra_borg_options_calls_borg_with_extra_options(): + insert_rinfo_command_not_found_mock() + insert_rcreate_command_mock(RCREATE_COMMAND + ('--extra', '--options', '--repo', 'repo')) + flexmock(module.feature).should_receive('available').and_return(True) + + module.create_repository( + repository='repo', + storage_config={'extra_borg_options': {'rcreate': '--extra --options'}}, + local_borg_version='2.3.4', + encryption_mode='repokey', + ) diff --git a/tests/unit/commands/test_borgmatic.py b/tests/unit/commands/test_borgmatic.py index 66c5abe..57ce1b4 100644 --- a/tests/unit/commands/test_borgmatic.py +++ b/tests/unit/commands/test_borgmatic.py @@ -340,11 +340,11 @@ def test_run_configuration_retries_timeout_multiple_repos(): assert results == error_logs -def test_run_actions_does_not_raise_for_init_action(): - flexmock(module.borg_init).should_receive('initialize_repository') +def test_run_actions_does_not_raise_for_rcreate_action(): + flexmock(module.borg_rcreate).should_receive('create_repository') arguments = { 'global': flexmock(monitoring_verbosity=1, dry_run=False), - 'init': flexmock( + 'rcreate': flexmock( encryption_mode=flexmock(), append_only=flexmock(), storage_quota=flexmock() ), }