From 65cc4c9429040e10a185e78f54ff5a1d737b4918 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Fri, 6 Dec 2019 16:29:41 -0800 Subject: [PATCH] Fix "--repository" flag to accept relative paths. --- NEWS | 1 + borgmatic/commands/borgmatic.py | 20 +++++++++++---- borgmatic/config/validate.py | 25 ++++++++++++++++--- tests/unit/config/test_validate.py | 40 ++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 9 deletions(-) diff --git a/NEWS b/NEWS index 63185bd0..bc4a687a 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,5 @@ 1.4.18 + * Fix "--repository" flag to accept relative paths. * #253: Mount whole repositories via "borgmatic mount" without any "--archive" flag. 1.4.17 diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index 3a1434dd..5e285ae8 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -234,7 +234,9 @@ def run_actions( only_checks=arguments['check'].only, ) if 'extract' in arguments: - if arguments['extract'].repository is None or repository == arguments['extract'].repository: + if arguments['extract'].repository is None or validate.repositories_match( + repository, arguments['extract'].repository + ): logger.info( '{}: Extracting archive {}'.format(repository, arguments['extract'].archive) ) @@ -251,7 +253,9 @@ def run_actions( progress=arguments['extract'].progress, ) if 'mount' in arguments: - if arguments['mount'].repository is None or repository == arguments['mount'].repository: + if arguments['mount'].repository is None or validate.repositories_match( + repository, arguments['mount'].repository + ): if arguments['mount'].archive: logger.info( '{}: Mounting archive {}'.format(repository, arguments['mount'].archive) @@ -278,7 +282,9 @@ def run_actions( mount_point=arguments['umount'].mount_point, local_path=local_path ) if 'restore' in arguments: - if arguments['restore'].repository is None or repository == arguments['restore'].repository: + if arguments['restore'].repository is None or validate.repositories_match( + repository, arguments['restore'].repository + ): logger.info( '{}: Restoring databases from archive {}'.format( repository, arguments['restore'].archive @@ -336,7 +342,9 @@ def run_actions( global_arguments.dry_run, ) if 'list' in arguments: - if arguments['list'].repository is None or repository == arguments['list'].repository: + if arguments['list'].repository is None or validate.repositories_match( + repository, arguments['list'].repository + ): logger.info('{}: Listing archives'.format(repository)) json_output = borg_list.list_archives( repository, @@ -348,7 +356,9 @@ def run_actions( if json_output: yield json.loads(json_output) if 'info' in arguments: - if arguments['info'].repository is None or repository == arguments['info'].repository: + if arguments['info'].repository is None or validate.repositories_match( + repository, arguments['info'].repository + ): logger.info('{}: Displaying summary info for archives'.format(repository)) json_output = borg_info.display_archives_info( repository, diff --git a/borgmatic/config/validate.py b/borgmatic/config/validate.py index 8c9e8e9b..1e421d18 100644 --- a/borgmatic/config/validate.py +++ b/borgmatic/config/validate.py @@ -1,4 +1,5 @@ import logging +import os import pkg_resources import pykwalify.core @@ -112,6 +113,24 @@ def parse_configuration(config_filename, schema_filename): return parsed_result +def normalize_repository_path(repository): + ''' + Given a repository path, return the absolute path of it (for local repositories). + ''' + # A colon in the repository indicates it's a remote repository. Bail. + if ':' in repository: + return repository + + return os.path.abspath(repository) + + +def repositories_match(first, second): + ''' + Given two repository paths (relative and/or absolute), return whether they match. + ''' + return normalize_repository_path(first) == normalize_repository_path(second) + + def guard_configuration_contains_repository(repository, configurations): ''' Given a repository path and a dict mapping from config filename to corresponding parsed config @@ -133,9 +152,7 @@ def guard_configuration_contains_repository(repository, configurations): if count > 1: raise ValueError( - 'Can\'t determine which repository to use. Use --repository option to disambiguate'.format( - repository - ) + 'Can\'t determine which repository to use. Use --repository option to disambiguate' ) return @@ -145,7 +162,7 @@ def guard_configuration_contains_repository(repository, configurations): config_repository for config in configurations.values() for config_repository in config['location']['repositories'] - if repository == config_repository + if repositories_match(repository, config_repository) ) ) diff --git a/tests/unit/config/test_validate.py b/tests/unit/config/test_validate.py index bdf30bec..4fc4a620 100644 --- a/tests/unit/config/test_validate.py +++ b/tests/unit/config/test_validate.py @@ -1,4 +1,5 @@ import pytest +from flexmock import flexmock from borgmatic.config import validate as module @@ -95,7 +96,38 @@ def test_remove_examples_strips_examples_from_sequence_of_maps(): assert schema == {'seq': [{'map': {'foo': {'desc': 'thing'}}}]} +def test_normalize_repository_path_passes_through_remote_repository(): + repository = 'example.org:test.borg' + + module.normalize_repository_path(repository) == repository + + +def test_normalize_repository_path_passes_through_absolute_repository(): + repository = '/foo/bar/test.borg' + flexmock(module.os.path).should_receive('abspath').and_return(repository) + + module.normalize_repository_path(repository) == repository + + +def test_normalize_repository_path_resolves_relative_repository(): + repository = 'test.borg' + absolute = '/foo/bar/test.borg' + flexmock(module.os.path).should_receive('abspath').and_return(absolute) + + module.normalize_repository_path(repository) == absolute + + +def test_repositories_match_does_not_raise(): + flexmock(module).should_receive('normalize_repository_path') + + module.repositories_match('foo', 'bar') + + def test_guard_configuration_contains_repository_does_not_raise_when_repository_in_config(): + flexmock(module).should_receive('repositories_match').replace_with( + lambda first, second: first == second + ) + module.guard_configuration_contains_repository( repository='repo', configurations={'config.yaml': {'location': {'repositories': ['repo']}}} ) @@ -116,6 +148,10 @@ def test_guard_configuration_contains_repository_errors_when_repository_assumed_ def test_guard_configuration_contains_repository_errors_when_repository_missing_from_config(): + flexmock(module).should_receive('repositories_match').replace_with( + lambda first, second: first == second + ) + with pytest.raises(ValueError): module.guard_configuration_contains_repository( repository='nope', @@ -124,6 +160,10 @@ def test_guard_configuration_contains_repository_errors_when_repository_missing_ def test_guard_configuration_contains_repository_errors_when_repository_matches_config_twice(): + flexmock(module).should_receive('repositories_match').replace_with( + lambda first, second: first == second + ) + with pytest.raises(ValueError): module.guard_configuration_contains_repository( repository='repo',