From 30abd0e3deedbca640d18ba1b4fa446609424f4d Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Tue, 16 Aug 2022 09:30:00 -0700 Subject: [PATCH] Update borg action for Borg 2 support (#557). --- borgmatic/borg/borg.py | 39 ++++++++------ borgmatic/commands/borgmatic.py | 1 + tests/unit/borg/test_borg.py | 90 +++++++++++++++++++++++++++------ 3 files changed, 99 insertions(+), 31 deletions(-) diff --git a/borgmatic/borg/borg.py b/borgmatic/borg/borg.py index 24a8864..991b736 100644 --- a/borgmatic/borg/borg.py +++ b/borgmatic/borg/borg.py @@ -1,24 +1,29 @@ import logging -from borgmatic.borg import environment -from borgmatic.borg.flags import make_flags +from borgmatic.borg import environment, flags from borgmatic.execute import execute_command logger = logging.getLogger(__name__) REPOSITORYLESS_BORG_COMMANDS = {'serve', None} -BORG_COMMANDS_WITH_SUBCOMMANDS = {'key', 'debug'} -BORG_SUBCOMMANDS_WITHOUT_REPOSITORY = (('debug', 'info'), ('debug', 'convert-profile')) +BORG_SUBCOMMANDS_WITH_SUBCOMMANDS = {'key', 'debug'} +BORG_SUBCOMMANDS_WITHOUT_REPOSITORY = (('debug', 'info'), ('debug', 'convert-profile'), ()) def run_arbitrary_borg( - repository, storage_config, options, archive=None, local_path='borg', remote_path=None + repository, + storage_config, + local_borg_version, + options, + archive=None, + local_path='borg', + remote_path=None, ): ''' - Given a local or remote repository path, a storage config dict, a sequence of arbitrary - command-line Borg options, and an optional archive name, run an arbitrary Borg command on the - given repository/archive. + Given a local or remote repository path, a storage config dict, the local Borg version, a + sequence of arbitrary command-line Borg options, and an optional archive name, run an arbitrary + Borg command on the given repository/archive. ''' lock_wait = storage_config.get('lock_wait', None) @@ -26,7 +31,7 @@ def run_arbitrary_borg( options = options[1:] if options[0] == '--' else options # Borg commands like "key" have a sub-command ("export", etc.) that must follow it. - command_options_start_index = 2 if options[0] in BORG_COMMANDS_WITH_SUBCOMMANDS else 1 + command_options_start_index = 2 if options[0] in BORG_SUBCOMMANDS_WITH_SUBCOMMANDS else 1 borg_command = tuple(options[:command_options_start_index]) command_options = tuple(options[command_options_start_index:]) except IndexError: @@ -34,21 +39,23 @@ def run_arbitrary_borg( command_options = () if borg_command in BORG_SUBCOMMANDS_WITHOUT_REPOSITORY: - repository_archive = None - else: - repository_archive = ( - '::'.join((repository, archive)) if repository and archive else repository + repository_archive_flags = () + elif archive: + repository_archive_flags = flags.make_repository_archive_flags( + repository, archive, local_borg_version ) + else: + repository_archive_flags = flags.make_repository_flags(repository, local_borg_version) full_command = ( (local_path,) + borg_command - + ((repository_archive,) if borg_command and repository_archive else ()) + + repository_archive_flags + command_options + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ()) + (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ()) - + make_flags('remote-path', remote_path) - + make_flags('lock-wait', lock_wait) + + flags.make_flags('remote-path', remote_path) + + flags.make_flags('lock-wait', lock_wait) ) return execute_command( diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index 777b39f..60b71c8 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -722,6 +722,7 @@ def run_actions( borg_borg.run_arbitrary_borg( repository, storage, + local_borg_version, options=arguments['borg'].options, archive=archive_name, local_path=local_path, diff --git a/tests/unit/borg/test_borg.py b/tests/unit/borg/test_borg.py index 8282bd5..96e5915 100644 --- a/tests/unit/borg/test_borg.py +++ b/tests/unit/borg/test_borg.py @@ -8,6 +8,8 @@ from ..test_verbosity import insert_logging_mock def test_run_arbitrary_borg_calls_borg_with_parameters(): + flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) + flexmock(module.flags).should_receive('make_flags').and_return(()) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( ('borg', 'break-lock', 'repo'), @@ -17,11 +19,13 @@ def test_run_arbitrary_borg_calls_borg_with_parameters(): ) module.run_arbitrary_borg( - repository='repo', storage_config={}, options=['break-lock'], + repository='repo', storage_config={}, local_borg_version='1.2.3', options=['break-lock'], ) def test_run_arbitrary_borg_with_log_info_calls_borg_with_info_parameter(): + flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) + flexmock(module.flags).should_receive('make_flags').and_return(()) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( ('borg', 'break-lock', 'repo', '--info'), @@ -32,11 +36,13 @@ def test_run_arbitrary_borg_with_log_info_calls_borg_with_info_parameter(): insert_logging_mock(logging.INFO) module.run_arbitrary_borg( - repository='repo', storage_config={}, options=['break-lock'], + repository='repo', storage_config={}, local_borg_version='1.2.3', options=['break-lock'], ) def test_run_arbitrary_borg_with_log_debug_calls_borg_with_debug_parameter(): + flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) + flexmock(module.flags).should_receive('make_flags').and_return(()) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( ('borg', 'break-lock', 'repo', '--debug', '--show-rc'), @@ -47,12 +53,16 @@ def test_run_arbitrary_borg_with_log_debug_calls_borg_with_debug_parameter(): insert_logging_mock(logging.DEBUG) module.run_arbitrary_borg( - repository='repo', storage_config={}, options=['break-lock'], + repository='repo', storage_config={}, local_borg_version='1.2.3', options=['break-lock'], ) def test_run_arbitrary_borg_with_lock_wait_calls_borg_with_lock_wait_parameters(): storage_config = {'lock_wait': 5} + flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) + flexmock(module.flags).should_receive('make_flags').and_return(()).and_return( + ('--lock-wait', '5') + ) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( ('borg', 'break-lock', 'repo', '--lock-wait', '5'), @@ -62,12 +72,18 @@ def test_run_arbitrary_borg_with_lock_wait_calls_borg_with_lock_wait_parameters( ) module.run_arbitrary_borg( - repository='repo', storage_config=storage_config, options=['break-lock'], + repository='repo', + storage_config=storage_config, + local_borg_version='1.2.3', + options=['break-lock'], ) def test_run_arbitrary_borg_with_archive_calls_borg_with_archive_parameter(): - storage_config = {} + flexmock(module.flags).should_receive('make_repository_archive_flags').and_return( + ('repo::archive',) + ) + flexmock(module.flags).should_receive('make_flags').and_return(()) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( ('borg', 'break-lock', 'repo::archive'), @@ -77,11 +93,17 @@ def test_run_arbitrary_borg_with_archive_calls_borg_with_archive_parameter(): ) module.run_arbitrary_borg( - repository='repo', storage_config=storage_config, options=['break-lock'], archive='archive', + repository='repo', + storage_config={}, + local_borg_version='1.2.3', + options=['break-lock'], + archive='archive', ) def test_run_arbitrary_borg_with_local_path_calls_borg_via_local_path(): + flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) + flexmock(module.flags).should_receive('make_flags').and_return(()) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( ('borg1', 'break-lock', 'repo'), @@ -91,11 +113,19 @@ def test_run_arbitrary_borg_with_local_path_calls_borg_via_local_path(): ) module.run_arbitrary_borg( - repository='repo', storage_config={}, options=['break-lock'], local_path='borg1', + repository='repo', + storage_config={}, + local_borg_version='1.2.3', + options=['break-lock'], + local_path='borg1', ) def test_run_arbitrary_borg_with_remote_path_calls_borg_with_remote_path_parameters(): + flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) + flexmock(module.flags).should_receive('make_flags').and_return( + ('--remote-path', 'borg1') + ).and_return(()) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( ('borg', 'break-lock', 'repo', '--remote-path', 'borg1'), @@ -105,11 +135,17 @@ def test_run_arbitrary_borg_with_remote_path_calls_borg_with_remote_path_paramet ) module.run_arbitrary_borg( - repository='repo', storage_config={}, options=['break-lock'], remote_path='borg1', + repository='repo', + storage_config={}, + local_borg_version='1.2.3', + options=['break-lock'], + remote_path='borg1', ) def test_run_arbitrary_borg_passes_borg_specific_parameters_to_borg(): + flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) + flexmock(module.flags).should_receive('make_flags').and_return(()) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( ('borg', 'list', 'repo', '--progress'), @@ -119,11 +155,16 @@ def test_run_arbitrary_borg_passes_borg_specific_parameters_to_borg(): ) module.run_arbitrary_borg( - repository='repo', storage_config={}, options=['list', '--progress'], + repository='repo', + storage_config={}, + local_borg_version='1.2.3', + options=['list', '--progress'], ) def test_run_arbitrary_borg_omits_dash_dash_in_parameters_passed_to_borg(): + flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) + flexmock(module.flags).should_receive('make_flags').and_return(()) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( ('borg', 'break-lock', 'repo'), @@ -133,22 +174,29 @@ def test_run_arbitrary_borg_omits_dash_dash_in_parameters_passed_to_borg(): ) module.run_arbitrary_borg( - repository='repo', storage_config={}, options=['--', 'break-lock'], + repository='repo', + storage_config={}, + local_borg_version='1.2.3', + options=['--', 'break-lock'], ) def test_run_arbitrary_borg_without_borg_specific_parameters_does_not_raise(): + flexmock(module.flags).should_receive('make_repository_flags').never() + flexmock(module.flags).should_receive('make_flags').and_return(()) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( ('borg',), output_log_level=logging.WARNING, borg_local_path='borg', extra_environment=None, ) module.run_arbitrary_borg( - repository='repo', storage_config={}, options=[], + repository='repo', storage_config={}, local_borg_version='1.2.3', options=[], ) def test_run_arbitrary_borg_passes_key_sub_command_to_borg_before_repository(): + flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) + flexmock(module.flags).should_receive('make_flags').and_return(()) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( ('borg', 'key', 'export', 'repo'), @@ -158,11 +206,13 @@ def test_run_arbitrary_borg_passes_key_sub_command_to_borg_before_repository(): ) module.run_arbitrary_borg( - repository='repo', storage_config={}, options=['key', 'export'], + repository='repo', storage_config={}, local_borg_version='1.2.3', options=['key', 'export'], ) def test_run_arbitrary_borg_passes_debug_sub_command_to_borg_before_repository(): + flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',)) + flexmock(module.flags).should_receive('make_flags').and_return(()) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( ('borg', 'debug', 'dump-manifest', 'repo', 'path'), @@ -172,11 +222,16 @@ def test_run_arbitrary_borg_passes_debug_sub_command_to_borg_before_repository() ) module.run_arbitrary_borg( - repository='repo', storage_config={}, options=['debug', 'dump-manifest', 'path'], + repository='repo', + storage_config={}, + local_borg_version='1.2.3', + options=['debug', 'dump-manifest', 'path'], ) def test_run_arbitrary_borg_with_debug_info_command_does_not_pass_borg_repository(): + flexmock(module.flags).should_receive('make_repository_flags').never() + flexmock(module.flags).should_receive('make_flags').and_return(()) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( ('borg', 'debug', 'info'), @@ -186,11 +241,13 @@ def test_run_arbitrary_borg_with_debug_info_command_does_not_pass_borg_repositor ) module.run_arbitrary_borg( - repository='repo', storage_config={}, options=['debug', 'info'], + repository='repo', storage_config={}, local_borg_version='1.2.3', options=['debug', 'info'], ) def test_run_arbitrary_borg_with_debug_convert_profile_command_does_not_pass_borg_repository(): + flexmock(module.flags).should_receive('make_repository_flags').never() + flexmock(module.flags).should_receive('make_flags').and_return(()) flexmock(module.environment).should_receive('make_environment') flexmock(module).should_receive('execute_command').with_args( ('borg', 'debug', 'convert-profile', 'in', 'out'), @@ -200,5 +257,8 @@ def test_run_arbitrary_borg_with_debug_convert_profile_command_does_not_pass_bor ) module.run_arbitrary_borg( - repository='repo', storage_config={}, options=['debug', 'convert-profile', 'in', 'out'], + repository='repo', + storage_config={}, + local_borg_version='1.2.3', + options=['debug', 'convert-profile', 'in', 'out'], )