From a08c7fc77a69ebf6cb509bc3e288497a2466c0ca Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Mon, 11 Mar 2024 11:24:36 -0700 Subject: [PATCH] When running the "rcreate" action and the repository already exists but with a different encryption mode than requested, error (#840). --- NEWS | 2 ++ borgmatic/borg/rcreate.py | 30 ++++++++++++++++++++++-------- tests/unit/borg/test_rcreate.py | 25 ++++++++++++++++++++++++- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/NEWS b/NEWS index 0d34a806..7a124bf4 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,8 @@ https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#apprise-hook * #839: Document a potentially breaking shell quoting edge case within error hooks: https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#error-hooks + * #840: When running the "rcreate" action and the repository already exists but with a different + encryption mode than requested, error. * Switch from Drone to Gitea Actions for continuous integration. * Rename scripts/run-end-to-end-dev-tests to scripts/run-end-to-end-tests and use it in both dev and CI for better dev-CI parity. diff --git a/borgmatic/borg/rcreate.py b/borgmatic/borg/rcreate.py index 0acd6b30..7735c916 100644 --- a/borgmatic/borg/rcreate.py +++ b/borgmatic/borg/rcreate.py @@ -1,4 +1,5 @@ import argparse +import json import logging import subprocess @@ -31,17 +32,30 @@ def create_repository( version, a Borg encryption mode, the path to another repo whose key material should be reused, 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. + + Raise ValueError if the requested encryption mode does not match that of the repository. + Raise json.decoder.JSONDecodeError if the "borg info" JSON outputcannot be decoded. + Raise subprocess.CalledProcessError if "borg info" returns an error exit code. ''' try: - rinfo.display_repository_info( - repository_path, - config, - local_borg_version, - argparse.Namespace(json=True), - global_arguments, - local_path, - remote_path, + info_data = json.loads( + rinfo.display_repository_info( + repository_path, + config, + local_borg_version, + argparse.Namespace(json=True), + global_arguments, + local_path, + remote_path, + ) ) + repository_encryption_mode = info_data.get('encryption', {}).get('mode') + + if repository_encryption_mode != encryption_mode: + raise ValueError( + f'Requested encryption mode "{encryption_mode}" does not match existing repository encryption mode "{repository_encryption_mode}"' + ) + logger.info(f'{repository_path}: Repository already exists. Skipping creation.') return except subprocess.CalledProcessError as error: diff --git a/tests/unit/borg/test_rcreate.py b/tests/unit/borg/test_rcreate.py index 56e83309..c5532826 100644 --- a/tests/unit/borg/test_rcreate.py +++ b/tests/unit/borg/test_rcreate.py @@ -13,7 +13,9 @@ RCREATE_COMMAND = ('borg', 'rcreate', '--encryption', 'repokey') def insert_rinfo_command_found_mock(): - flexmock(module.rinfo).should_receive('display_repository_info') + flexmock(module.rinfo).should_receive('display_repository_info').and_return( + '{"encryption": {"mode": "repokey"}}' + ) def insert_rinfo_command_not_found_mock(): @@ -120,6 +122,27 @@ def test_create_repository_skips_creation_when_repository_already_exists(): ) +def test_create_repository_errors_when_repository_with_differing_encryption_mode_already_exists(): + insert_rinfo_command_found_mock() + flexmock(module.feature).should_receive('available').and_return(True) + flexmock(module.flags).should_receive('make_repository_flags').and_return( + ( + '--repo', + 'repo', + ) + ) + + with pytest.raises(ValueError): + module.create_repository( + dry_run=False, + repository_path='repo', + config={}, + local_borg_version='2.3.4', + global_arguments=flexmock(log_json=False), + encryption_mode='repokey-blake2', + ) + + 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, [])