From f1a008ee18ddcf15c7668accf1f77139d89b0d29 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Tue, 2 Sep 2025 09:47:53 -0700 Subject: [PATCH 1/3] Rephrase NEWS entry for clarity (#1139). --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 717e9c48..1de700b2 100644 --- a/NEWS +++ b/NEWS @@ -4,7 +4,7 @@ use. * #1126: Create LVM snapshots as read-write to avoid an error when snapshotting ext4 filesystems with orphaned files that need recovery. - * #1139: When making HTTP requests in monitoring hooks, set "borgmatic" as the user agent. + * #1139: Set "borgmatic" as the user agent when connecting to monitoring services. * When running tests, use Ruff for faster and more comprehensive code linting and formatting, replacing Flake8, Black, isort, etc. * Switch from pipx to uv for installing development tools, and added tox-uv for speeding up test From e3f4b79e76ca6d9e9f6b3d18af9d9ee1141cc9b9 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Mon, 8 Sep 2025 12:16:59 -0700 Subject: [PATCH 2/3] Fix the "spot" check to include borgmatic configuration files that were backed up to support the "bootstrap" action (#1133). --- NEWS | 2 + borgmatic/actions/check.py | 51 ++++++++---- borgmatic/actions/config/bootstrap.py | 107 ++++++++++++++------------ 3 files changed, 95 insertions(+), 65 deletions(-) diff --git a/NEWS b/NEWS index 1de700b2..fe0d7ac8 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,8 @@ use. * #1126: Create LVM snapshots as read-write to avoid an error when snapshotting ext4 filesystems with orphaned files that need recovery. + * #1133: Fix the "spot" check to include borgmatic configuration files that were backed up to + support the "bootstrap" action. * #1139: Set "borgmatic" as the user agent when connecting to monitoring services. * When running tests, use Ruff for faster and more comprehensive code linting and formatting, replacing Flake8, Black, isort, etc. diff --git a/borgmatic/actions/check.py b/borgmatic/actions/check.py index e0b6d2e9..7f006d40 100644 --- a/borgmatic/actions/check.py +++ b/borgmatic/actions/check.py @@ -11,12 +11,14 @@ import shlex import shutil import textwrap +import borgmatic.actions.config.bootstrap import borgmatic.actions.pattern import borgmatic.borg.check import borgmatic.borg.create import borgmatic.borg.environment import borgmatic.borg.extract import borgmatic.borg.list +import borgmatic.borg.pattern import borgmatic.borg.repo_list import borgmatic.borg.state import borgmatic.config.paths @@ -358,11 +360,15 @@ def collect_spot_check_source_paths( local_path, remote_path, borgmatic_runtime_directory, + bootstrap_config_paths, ): ''' Given a repository configuration dict, a configuration dict, the local Borg version, global - arguments as an argparse.Namespace instance, the local Borg path, and the remote Borg path, - collect the source paths that Borg would use in an actual create (but only include files). + arguments as an argparse.Namespace instance, the local Borg path, the remote Borg path, and the + bootstrap configuration paths as read from an archive's manifest, collect the source paths that + Borg would use in an actual create (but only include files). As part of this, include the + bootstrap configuration paths, so that any configuration files included in the archive to + support bootstrapping are also spot checked. ''' stream_processes = any( borgmatic.hooks.dispatch.call_hooks( @@ -382,7 +388,14 @@ def collect_spot_check_source_paths( list_details=True, ), patterns=borgmatic.actions.pattern.process_patterns( - borgmatic.actions.pattern.collect_patterns(config), + borgmatic.actions.pattern.collect_patterns(config) + + tuple( + borgmatic.borg.pattern.Pattern( + config_path, + source=borgmatic.borg.pattern.Pattern_source.INTERNAL, + ) + for config_path in bootstrap_config_paths + ), config, working_directory, ), @@ -609,17 +622,6 @@ def spot_check( 'The data_tolerance_percentage must be less than or equal to the data_sample_percentage', ) - source_paths = collect_spot_check_source_paths( - repository, - config, - local_borg_version, - global_arguments, - local_path, - remote_path, - borgmatic_runtime_directory, - ) - logger.debug(f'{len(source_paths)} total source paths for spot check') - archive = borgmatic.borg.repo_list.resolve_archive_name( repository['path'], 'latest', @@ -631,6 +633,27 @@ def spot_check( ) logger.debug(f'Using archive {archive} for spot check') + bootstrap_config_paths = borgmatic.actions.config.bootstrap.load_config_paths_from_archive( + repository['path'], + archive, + config, + local_borg_version, + global_arguments, + borgmatic_runtime_directory, + ) + + source_paths = collect_spot_check_source_paths( + repository, + config, + local_borg_version, + global_arguments, + local_path, + remote_path, + borgmatic_runtime_directory, + bootstrap_config_paths, + ) + logger.debug(f'{len(source_paths)} total source paths for spot check') + archive_paths = collect_spot_check_archive_paths( repository, archive, diff --git a/borgmatic/actions/config/bootstrap.py b/borgmatic/actions/config/bootstrap.py index 10ee8c56..e38e6e72 100644 --- a/borgmatic/actions/config/bootstrap.py +++ b/borgmatic/actions/config/bootstrap.py @@ -16,67 +16,68 @@ def make_bootstrap_config(bootstrap_arguments): Given the bootstrap arguments as an argparse.Namespace, return a corresponding config dict. ''' return { - 'ssh_command': bootstrap_arguments.ssh_command, + 'borgmatic_source_directory': bootstrap_arguments.borgmatic_source_directory, + 'local_path': bootstrap_arguments.local_path, + 'remote_path': bootstrap_arguments.remote_path, # In case the repo has been moved or is accessed from a different path at the point of # bootstrapping. 'relocated_repo_access_is_ok': True, + 'ssh_command': bootstrap_arguments.ssh_command, + 'user_runtime_directory': bootstrap_arguments.user_runtime_directory, } -def get_config_paths(archive_name, bootstrap_arguments, global_arguments, local_borg_version): +def load_config_paths_from_archive( + repository_path, + archive_name, + config, + local_borg_version, + global_arguments, + borgmatic_runtime_directory, +): ''' - Given an archive name, the bootstrap arguments as an argparse.Namespace (containing the - repository and archive name, Borg local path, Borg remote path, borgmatic runtime directory, - borgmatic source directory, destination directory, and whether to strip components), the global - arguments as an argparse.Namespace (containing the dry run flag and the local borg version), - return the config paths from the manifest.json file in the borgmatic source directory or runtime - directory after extracting it from the repository archive. + Given a repository path, an archive name, a configuration dict, the local Borg version, the + global arguments as an argparse.Namespace, and the borgmatic runtime directory, return the + config paths from the manifest.json file in the borgmatic source directory or runtime directory + within the repository archive. Raise ValueError if the manifest JSON is missing, can't be decoded, or doesn't contain the expected configuration path data. ''' - borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory( - {'borgmatic_source_directory': bootstrap_arguments.borgmatic_source_directory}, - ) - config = make_bootstrap_config(bootstrap_arguments) - # Probe for the manifest file in multiple locations, as the default location has moved to the # borgmatic runtime directory (which gets stored as just "/borgmatic" with Borg 1.4+). But we # still want to support reading the manifest from previously created archives as well. - with borgmatic.config.paths.Runtime_directory( - {'user_runtime_directory': bootstrap_arguments.user_runtime_directory}, - ) as borgmatic_runtime_directory: - for base_directory in ( - 'borgmatic', - borgmatic.config.paths.make_runtime_directory_glob(borgmatic_runtime_directory), - borgmatic_source_directory, - ): - borgmatic_manifest_path = 'sh:' + os.path.join( - base_directory, - 'bootstrap', - 'manifest.json', - ) + for base_directory in ( + 'borgmatic', + borgmatic.config.paths.make_runtime_directory_glob(borgmatic_runtime_directory), + borgmatic.config.paths.get_borgmatic_source_directory(config), + ): + borgmatic_manifest_path = 'sh:' + os.path.join( + base_directory, + 'bootstrap', + 'manifest.json', + ) - extract_process = borgmatic.borg.extract.extract_archive( - global_arguments.dry_run, - bootstrap_arguments.repository, - archive_name, - [borgmatic_manifest_path], - config, - local_borg_version, - global_arguments, - local_path=bootstrap_arguments.local_path, - remote_path=bootstrap_arguments.remote_path, - extract_to_stdout=True, - ) - manifest_json = extract_process.stdout.read() + extract_process = borgmatic.borg.extract.extract_archive( + global_arguments.dry_run, + repository_path, + archive_name, + [borgmatic_manifest_path], + config, + local_borg_version, + global_arguments, + local_path=config.get('local_path'), + remote_path=config.get('remote_path'), + extract_to_stdout=True, + ) + manifest_json = extract_process.stdout.read() - if manifest_json: - break - else: - raise ValueError( - 'Cannot read configuration paths from archive due to missing bootstrap manifest', - ) + if manifest_json: + break + else: + raise ValueError( + 'Cannot read configuration paths from archive due to missing archive or bootstrap manifest', + ) try: manifest_data = json.loads(manifest_json) @@ -110,12 +111,16 @@ def run_bootstrap(bootstrap_arguments, global_arguments, local_borg_version): local_path=bootstrap_arguments.local_path, remote_path=bootstrap_arguments.remote_path, ) - manifest_config_paths = get_config_paths( - archive_name, - bootstrap_arguments, - global_arguments, - local_borg_version, - ) + + with borgmatic.config.paths.Runtime_directory(config) as borgmatic_runtime_directory: + manifest_config_paths = load_config_paths_from_archive( + bootstrap_arguments.repository, + archive_name, + config, + local_borg_version, + global_arguments, + borgmatic_runtime_directory, + ) logger.info(f"Bootstrapping config paths: {', '.join(manifest_config_paths)}") From 85b198feb20f1095a394090584b6918b4d283fa9 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Mon, 8 Sep 2025 23:16:27 -0700 Subject: [PATCH 3/3] Get automated tests passing (#1143). --- borgmatic/actions/check.py | 18 +- tests/unit/actions/config/test_bootstrap.py | 300 +++++++------------- tests/unit/actions/test_check.py | 41 ++- 3 files changed, 136 insertions(+), 223 deletions(-) diff --git a/borgmatic/actions/check.py b/borgmatic/actions/check.py index 7f006d40..0872d528 100644 --- a/borgmatic/actions/check.py +++ b/borgmatic/actions/check.py @@ -633,15 +633,6 @@ def spot_check( ) logger.debug(f'Using archive {archive} for spot check') - bootstrap_config_paths = borgmatic.actions.config.bootstrap.load_config_paths_from_archive( - repository['path'], - archive, - config, - local_borg_version, - global_arguments, - borgmatic_runtime_directory, - ) - source_paths = collect_spot_check_source_paths( repository, config, @@ -650,7 +641,14 @@ def spot_check( local_path, remote_path, borgmatic_runtime_directory, - bootstrap_config_paths, + bootstrap_config_paths=borgmatic.actions.config.bootstrap.load_config_paths_from_archive( + repository['path'], + archive, + config, + local_borg_version, + global_arguments, + borgmatic_runtime_directory, + ), ) logger.debug(f'{len(source_paths)} total source paths for spot check') diff --git a/tests/unit/actions/config/test_bootstrap.py b/tests/unit/actions/config/test_bootstrap.py index 07a81329..6aa967a9 100644 --- a/tests/unit/actions/config/test_bootstrap.py +++ b/tests/unit/actions/config/test_bootstrap.py @@ -4,38 +4,32 @@ from flexmock import flexmock from borgmatic.actions.config import bootstrap as module -def test_make_bootstrap_config_uses_ssh_command_argument(): - ssh_command = flexmock() +def test_make_bootstrap_config_uses_bootstrap_arguments(): + config = module.make_bootstrap_config( + flexmock( + borgmatic_source_directory='/source', + local_path='borg1', + remote_path='borg2', + ssh_command='ssh', + user_runtime_directory='/run', + ) + ) - config = module.make_bootstrap_config(flexmock(ssh_command=ssh_command)) - assert config['ssh_command'] == ssh_command + assert config['borgmatic_source_directory'] == '/source' + assert config['local_path'] == 'borg1' + assert config['remote_path'] == 'borg2' assert config['relocated_repo_access_is_ok'] + assert config['ssh_command'] == 'ssh' + assert config['user_runtime_directory'] == '/run' -def test_get_config_paths_returns_list_of_config_paths(): - flexmock(module.borgmatic.config.paths).should_receive( - 'get_borgmatic_source_directory', - ).and_return('/source') - flexmock(module).should_receive('make_bootstrap_config').and_return({}) - bootstrap_arguments = flexmock( - repository='repo', - archive='archive', - ssh_command=None, - local_path='borg7', - remote_path='borg8', - borgmatic_source_directory=None, - user_runtime_directory=None, - ) - global_arguments = flexmock( - dry_run=False, - ) - local_borg_version = flexmock() - flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return( - flexmock(), - ) +def test_load_config_paths_from_archive_returns_list_of_config_paths(): flexmock(module.borgmatic.config.paths).should_receive( 'make_runtime_directory_glob', ).replace_with(lambda path: path) + flexmock(module.borgmatic.config.paths).should_receive( + 'get_borgmatic_source_directory', + ).and_return('/source') extract_process = flexmock( stdout=flexmock( read=lambda: '{"config_paths": ["/borgmatic/config.yaml"]}', @@ -45,54 +39,38 @@ def test_get_config_paths_returns_list_of_config_paths(): extract_process, ) - assert module.get_config_paths( + assert module.load_config_paths_from_archive( + 'repo', 'archive', - bootstrap_arguments, - global_arguments, - local_borg_version, + config={}, + local_borg_version=flexmock(), + global_arguments=flexmock(dry_run=False), + borgmatic_runtime_directory='/run', ) == ['/borgmatic/config.yaml'] -def test_get_config_paths_probes_for_manifest(): - flexmock(module.borgmatic.config.paths).should_receive( - 'get_borgmatic_source_directory', - ).and_return('/source') - flexmock(module).should_receive('make_bootstrap_config').and_return({}) - bootstrap_arguments = flexmock( - repository='repo', - archive='archive', - ssh_command=None, - local_path='borg7', - remote_path='borg8', - borgmatic_source_directory=None, - user_runtime_directory=None, - ) - global_arguments = flexmock( - dry_run=False, - ) - local_borg_version = flexmock() - borgmatic_runtime_directory = flexmock() - flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return( - borgmatic_runtime_directory, - ) +def test_load_config_paths_from_archive_probes_for_manifest(): flexmock(module.borgmatic.config.paths).should_receive( 'make_runtime_directory_glob', ).replace_with(lambda path: path) - flexmock(module.os.path).should_receive('join').with_args( + flexmock(module.borgmatic.config.paths).should_receive( + 'get_borgmatic_source_directory', + ).and_return('/source') + flexmock(module.os.path).should_call('join').with_args( 'borgmatic', 'bootstrap', 'manifest.json', - ).and_return('borgmatic/bootstrap/manifest.json').once() - flexmock(module.os.path).should_receive('join').with_args( - borgmatic_runtime_directory, + ).once() + flexmock(module.os.path).should_call('join').with_args( + '/run', 'bootstrap', 'manifest.json', - ).and_return('run/borgmatic/bootstrap/manifest.json').once() - flexmock(module.os.path).should_receive('join').with_args( + ).once() + flexmock(module.os.path).should_call('join').with_args( '/source', 'bootstrap', 'manifest.json', - ).and_return('/source/bootstrap/manifest.json').once() + ).once() manifest_missing_extract_process = flexmock( stdout=flexmock(read=lambda: None), ) @@ -105,128 +83,46 @@ def test_get_config_paths_probes_for_manifest(): manifest_missing_extract_process, ).and_return(manifest_missing_extract_process).and_return(manifest_found_extract_process) - assert module.get_config_paths( - 'archive', - bootstrap_arguments, - global_arguments, - local_borg_version, - ) == ['/borgmatic/config.yaml'] - - -def test_get_config_paths_translates_ssh_command_argument_to_config(): - flexmock(module.borgmatic.config.paths).should_receive( - 'get_borgmatic_source_directory', - ).and_return('/source') - config = {} - flexmock(module).should_receive('make_bootstrap_config').and_return(config) - bootstrap_arguments = flexmock( - repository='repo', - archive='archive', - ssh_command='ssh -i key', - local_path='borg7', - remote_path='borg8', - borgmatic_source_directory=None, - user_runtime_directory=None, - ) - global_arguments = flexmock( - dry_run=False, - ) - local_borg_version = flexmock() - flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return( - flexmock(), - ) - flexmock(module.borgmatic.config.paths).should_receive( - 'make_runtime_directory_glob', - ).replace_with(lambda path: path) - extract_process = flexmock( - stdout=flexmock( - read=lambda: '{"config_paths": ["/borgmatic/config.yaml"]}', - ), - ) - flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').with_args( - False, + assert module.load_config_paths_from_archive( 'repo', 'archive', - object, - config, - object, - object, - extract_to_stdout=True, - local_path='borg7', - remote_path='borg8', - ).and_return(extract_process) - - assert module.get_config_paths( - 'archive', - bootstrap_arguments, - global_arguments, - local_borg_version, + config={}, + local_borg_version=flexmock(), + global_arguments=flexmock(dry_run=False), + borgmatic_runtime_directory='/run', ) == ['/borgmatic/config.yaml'] -def test_get_config_paths_with_missing_manifest_raises_value_error(): - flexmock(module.borgmatic.config.paths).should_receive( - 'get_borgmatic_source_directory', - ).and_return('/source') - flexmock(module).should_receive('make_bootstrap_config').and_return({}) - bootstrap_arguments = flexmock( - repository='repo', - archive='archive', - ssh_command=None, - local_path='borg7', - remote_path='borg7', - borgmatic_source_directory=None, - user_runtime_directory=None, - ) - global_arguments = flexmock( - dry_run=False, - ) - local_borg_version = flexmock() - flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return( - flexmock(), - ) +def test_load_config_paths_from_archive_with_missing_manifest_raises_value_error(): flexmock(module.borgmatic.config.paths).should_receive( 'make_runtime_directory_glob', ).replace_with(lambda path: path) - flexmock(module.os.path).should_receive('join').and_return('run/borgmatic') + flexmock(module.borgmatic.config.paths).should_receive( + 'get_borgmatic_source_directory', + ).and_return('/source') extract_process = flexmock(stdout=flexmock(read=lambda: '')) flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_return( extract_process, ) with pytest.raises(ValueError): - module.get_config_paths( + module.load_config_paths_from_archive( + 'repo', 'archive', - bootstrap_arguments, - global_arguments, - local_borg_version, + config={}, + local_borg_version=flexmock(), + global_arguments=flexmock(dry_run=False), + borgmatic_runtime_directory='/run', ) -def test_get_config_paths_with_broken_json_raises_value_error(): - flexmock(module.borgmatic.config.paths).should_receive( - 'get_borgmatic_source_directory', - ).and_return('/source') - flexmock(module).should_receive('make_bootstrap_config').and_return({}) - bootstrap_arguments = flexmock( - repository='repo', - archive='archive', - ssh_command=None, - local_path='borg7', - remote_path='borg7', - borgmatic_source_directory=None, - user_runtime_directory=None, - ) - global_arguments = flexmock( - dry_run=False, - ) - local_borg_version = flexmock() - flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return( - flexmock(), - ) +def test_load_config_paths_from_archive_with_broken_json_raises_value_error(): flexmock(module.borgmatic.config.paths).should_receive( 'make_runtime_directory_glob', ).replace_with(lambda path: path) + flexmock(module.borgmatic.config.paths).should_receive( + 'get_borgmatic_source_directory', + ).and_return('/source') extract_process = flexmock( stdout=flexmock(read=lambda: '{"config_paths": ["/oops'), ) @@ -235,38 +131,23 @@ def test_get_config_paths_with_broken_json_raises_value_error(): ) with pytest.raises(ValueError): - module.get_config_paths( + module.load_config_paths_from_archive( + 'repo', 'archive', - bootstrap_arguments, - global_arguments, - local_borg_version, + config={}, + local_borg_version=flexmock(), + global_arguments=flexmock(dry_run=False), + borgmatic_runtime_directory='/run', ) -def test_get_config_paths_with_json_missing_key_raises_value_error(): - flexmock(module.borgmatic.config.paths).should_receive( - 'get_borgmatic_source_directory', - ).and_return('/source') - flexmock(module).should_receive('make_bootstrap_config').and_return({}) - bootstrap_arguments = flexmock( - repository='repo', - archive='archive', - ssh_command=None, - local_path='borg7', - remote_path='borg7', - borgmatic_source_directory=None, - user_runtime_directory=None, - ) - global_arguments = flexmock( - dry_run=False, - ) - local_borg_version = flexmock() - flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return( - flexmock(), - ) +def test_load_config_paths_from_archive_with_json_missing_key_raises_value_error(): flexmock(module.borgmatic.config.paths).should_receive( 'make_runtime_directory_glob', ).replace_with(lambda path: path) + flexmock(module.borgmatic.config.paths).should_receive( + 'get_borgmatic_source_directory', + ).and_return('/source') extract_process = flexmock( stdout=flexmock(read=lambda: '{}'), ) @@ -275,17 +156,27 @@ def test_get_config_paths_with_json_missing_key_raises_value_error(): ) with pytest.raises(ValueError): - module.get_config_paths( + module.load_config_paths_from_archive( + 'repo', 'archive', - bootstrap_arguments, - global_arguments, - local_borg_version, + config={}, + local_borg_version=flexmock(), + global_arguments=flexmock(dry_run=False), + borgmatic_runtime_directory='/run', ) def test_run_bootstrap_does_not_raise(): flexmock(module).should_receive('make_bootstrap_config').and_return({}) - flexmock(module).should_receive('get_config_paths').and_return(['/borgmatic/config.yaml']) + flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return( + 'archive', + ) + flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return( + flexmock(), + ) + flexmock(module).should_receive('load_config_paths_from_archive').and_return( + ['/borgmatic/config.yaml'] + ) bootstrap_arguments = flexmock( repository='repo', archive='archive', @@ -315,9 +206,6 @@ def test_run_bootstrap_does_not_raise(): flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_return( extract_process, ).once() - flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return( - 'archive', - ) module.run_bootstrap(bootstrap_arguments, global_arguments, local_borg_version) @@ -325,7 +213,21 @@ def test_run_bootstrap_does_not_raise(): def test_run_bootstrap_translates_ssh_command_argument_to_config(): config = {} flexmock(module).should_receive('make_bootstrap_config').and_return(config) - flexmock(module).should_receive('get_config_paths').and_return(['/borgmatic/config.yaml']) + flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').with_args( + 'repo', + 'archive', + config, + object, + object, + local_path='borg7', + remote_path='borg8', + ).and_return('archive') + flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return( + flexmock(), + ) + flexmock(module).should_receive('load_config_paths_from_archive').and_return( + ['/borgmatic/config.yaml'] + ) bootstrap_arguments = flexmock( repository='repo', archive='archive', @@ -341,9 +243,6 @@ def test_run_bootstrap_translates_ssh_command_argument_to_config(): dry_run=False, ) local_borg_version = flexmock() - flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return( - flexmock(), - ) flexmock(module.borgmatic.config.paths).should_receive( 'make_runtime_directory_glob', ).replace_with(lambda path: path) @@ -366,14 +265,5 @@ def test_run_bootstrap_translates_ssh_command_argument_to_config(): local_path='borg7', remote_path='borg8', ).and_return(extract_process).once() - flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').with_args( - 'repo', - 'archive', - config, - object, - object, - local_path='borg7', - remote_path='borg8', - ).and_return('archive') module.run_bootstrap(bootstrap_arguments, global_arguments, local_borg_version) diff --git a/tests/unit/actions/test_check.py b/tests/unit/actions/test_check.py index 3334a3d2..a3cbc599 100644 --- a/tests/unit/actions/test_check.py +++ b/tests/unit/actions/test_check.py @@ -580,7 +580,7 @@ def test_upgrade_check_times_renames_stale_temporary_check_path(): module.upgrade_check_times(flexmock(), flexmock()) -def test_collect_spot_check_source_paths_parses_borg_output(): +def test_collect_spot_check_source_paths_parses_borg_output_and_includes_bootstrap_config_paths(): flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return( {'hook1': False, 'hook2': True}, ) @@ -588,9 +588,16 @@ def test_collect_spot_check_source_paths_parses_borg_output(): flexmock(), ) flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return( - flexmock(), + (Pattern('collected'),), ) - flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return( + flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').with_args( + ( + Pattern('collected', source=module.borgmatic.borg.pattern.Pattern_source.HOOK), + Pattern('extra.yaml', source=module.borgmatic.borg.pattern.Pattern_source.INTERNAL), + ), + config=object, + working_directory=None, + ).and_return( [Pattern('foo'), Pattern('bar')], ) flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args( @@ -624,6 +631,7 @@ def test_collect_spot_check_source_paths_parses_borg_output(): local_path=flexmock(), remote_path=flexmock(), borgmatic_runtime_directory='/run/borgmatic', + bootstrap_config_paths=('extra.yaml',), ) == ('/etc/path', '/etc/other') @@ -635,7 +643,7 @@ def test_collect_spot_check_source_paths_omits_progress_from_create_dry_run_comm flexmock(), ) flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return( - flexmock(), + (Pattern('collected'),), ) flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return( [Pattern('foo'), Pattern('bar')], @@ -671,6 +679,7 @@ def test_collect_spot_check_source_paths_omits_progress_from_create_dry_run_comm local_path=flexmock(), remote_path=flexmock(), borgmatic_runtime_directory='/run/borgmatic', + bootstrap_config_paths=(), ) == ('/etc/path', '/etc/other') @@ -682,7 +691,7 @@ def test_collect_spot_check_source_paths_passes_through_stream_processes_false() flexmock(), ) flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return( - flexmock(), + (Pattern('collected'),), ) flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return( [Pattern('foo'), Pattern('bar')], @@ -718,6 +727,7 @@ def test_collect_spot_check_source_paths_passes_through_stream_processes_false() local_path=flexmock(), remote_path=flexmock(), borgmatic_runtime_directory='/run/borgmatic', + bootstrap_config_paths=(), ) == ('/etc/path', '/etc/other') @@ -729,7 +739,7 @@ def test_collect_spot_check_source_paths_without_working_directory_parses_borg_o flexmock(), ) flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return( - flexmock(), + (Pattern('collected'),), ) flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return( [Pattern('foo'), Pattern('bar')], @@ -765,6 +775,7 @@ def test_collect_spot_check_source_paths_without_working_directory_parses_borg_o local_path=flexmock(), remote_path=flexmock(), borgmatic_runtime_directory='/run/borgmatic', + bootstrap_config_paths=(), ) == ('/etc/path', '/etc/other') @@ -776,7 +787,7 @@ def test_collect_spot_check_source_paths_skips_directories(): flexmock(), ) flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return( - flexmock(), + (Pattern('collected'),), ) flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return( [Pattern('foo'), Pattern('bar')], @@ -814,6 +825,7 @@ def test_collect_spot_check_source_paths_skips_directories(): local_path=flexmock(), remote_path=flexmock(), borgmatic_runtime_directory='/run/borgmatic', + bootstrap_config_paths=(), ) == () ) @@ -921,7 +933,7 @@ def test_collect_spot_check_source_paths_uses_working_directory(): flexmock(), ) flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return( - flexmock(), + (Pattern('collected'),), ) flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return( [Pattern('foo'), Pattern('bar')], @@ -960,6 +972,7 @@ def test_collect_spot_check_source_paths_uses_working_directory(): local_path=flexmock(), remote_path=flexmock(), borgmatic_runtime_directory='/run/borgmatic', + bootstrap_config_paths=(), ) == ('foo', 'bar') @@ -1384,6 +1397,9 @@ def test_spot_check_with_count_delta_greater_than_count_tolerance_percentage_err flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return( 'archive', ) + flexmock(module.borgmatic.actions.config.bootstrap).should_receive( + 'load_config_paths_from_archive' + ).and_return(('bootstrap.yaml',)) flexmock(module).should_receive('collect_spot_check_archive_paths').and_return( ('/foo', '/bar'), ).once() @@ -1416,6 +1432,9 @@ def test_spot_check_with_failing_percentage_greater_than_data_tolerance_percenta flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return( 'archive', ) + flexmock(module.borgmatic.actions.config.bootstrap).should_receive( + 'load_config_paths_from_archive' + ).and_return(('bootstrap.yaml',)) flexmock(module).should_receive('collect_spot_check_archive_paths').and_return(('/foo', '/bar')) flexmock(module).should_receive('compare_spot_check_hashes').and_return( ('/bar', '/baz', '/quux'), @@ -1449,6 +1468,9 @@ def test_spot_check_with_high_enough_tolerances_does_not_raise(): flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return( 'archive', ) + flexmock(module.borgmatic.actions.config.bootstrap).should_receive( + 'load_config_paths_from_archive' + ).and_return(('bootstrap.yaml',)) flexmock(module).should_receive('collect_spot_check_archive_paths').and_return(('/foo', '/bar')) flexmock(module).should_receive('compare_spot_check_hashes').and_return( ('/bar', '/baz', '/quux'), @@ -1479,6 +1501,9 @@ def test_spot_check_without_any_source_paths_errors(): flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return( 'archive', ) + flexmock(module.borgmatic.actions.config.bootstrap).should_receive( + 'load_config_paths_from_archive' + ).and_return(('bootstrap.yaml',)) flexmock(module).should_receive('collect_spot_check_archive_paths').and_return(('/foo', '/bar')) flexmock(module).should_receive('compare_spot_check_hashes').never()