Răsfoiți Sursa

Move bootstrap manifest file creation into a hook so it can actually clean up after itself.

Dan Helfman 6 luni în urmă
părinte
comite
689643e5fa

+ 5 - 0
NEWS

@@ -1,6 +1,11 @@
 1.9.3.dev0
  * #261 (beta): Add a ZFS hook for snapshotting and backing up ZFS datasets. See the documentation
    for more information: https://torsion.org/borgmatic/docs/how-to/snapshot-your-filesystems/
+ * After it's stored in a Borg archive, remove the manifest file created in support of
+   the "bootstrap" action.
+ * Deprecate the "store_config_files" option at the global scope and move it under the "bootstrap"
+   hook. See the documentation for more information:
+   https://torsion.org/borgmatic/docs/how-to/extract-a-backup/#extract-the-configuration-files-used-to-create-an-archive
  * Add a "--deleted" flag to the "repo-list" action for listing deleted archives that haven't
    yet been compacted (Borg 2 only).
 

+ 1 - 1
borgmatic/actions/check.py

@@ -374,7 +374,7 @@ def collect_spot_check_source_paths(
             repository_path=repository['path'],
             config=config,
             source_directories=borgmatic.actions.create.process_source_directories(
-                config, config_paths=()
+                config,
             ),
             local_borg_version=local_borg_version,
             global_arguments=global_arguments,

+ 6 - 46
borgmatic/actions/create.py

@@ -1,7 +1,5 @@
 import glob
-import importlib.metadata
 import itertools
-import json
 import logging
 import os
 import pathlib
@@ -17,32 +15,6 @@ import borgmatic.hooks.dump
 logger = logging.getLogger(__name__)
 
 
-def create_borgmatic_manifest(config, config_paths, borgmatic_runtime_directory, dry_run):
-    '''
-    Given a configuration dict, a sequence of config file paths, the borgmatic runtime directory,
-    and whether this is a dry run, create a borgmatic manifest file to store the paths to the
-    configuration files used to create the archive.
-    '''
-    if dry_run:
-        return
-
-    borgmatic_manifest_path = os.path.join(
-        borgmatic_runtime_directory, 'bootstrap', 'manifest.json'
-    )
-
-    if not os.path.exists(borgmatic_manifest_path):
-        os.makedirs(os.path.dirname(borgmatic_manifest_path), exist_ok=True)
-
-    with open(borgmatic_manifest_path, 'w') as config_list_file:
-        json.dump(
-            {
-                'borgmatic_version': importlib.metadata.version('borgmatic'),
-                'config_paths': config_paths,
-            },
-            config_list_file,
-        )
-
-
 def expand_directory(directory, working_directory):
     '''
     Given a directory path, expand any tilde (representing a user's home directory) and any globs
@@ -146,18 +118,15 @@ def pattern_root_directories(patterns=None):
     ]
 
 
-def process_source_directories(config, config_paths, source_directories=None):
+def process_source_directories(config, source_directories=None):
     '''
     Given a sequence of source directories (either in the source_directories argument or, lacking
-    that, from config) and a sequence of config paths to append, expand and deduplicate the source
-    directories, returning the result.
+    that, from config), expand and deduplicate the source directories, returning the result.
     '''
     working_directory = borgmatic.config.paths.get_working_directory(config)
 
     if source_directories is None:
-        source_directories = tuple(config.get('source_directories', ())) + (
-            tuple(config_paths) if config.get('store_config_files', True) else ()
-        )
+        source_directories = tuple(config.get('source_directories', ()))
 
     return deduplicate_directories(
         map_directories_to_devices(
@@ -221,12 +190,13 @@ def run_create(
             borgmatic_runtime_directory,
             global_arguments.dry_run,
         )
-        source_directories = process_source_directories(config, config_paths)
+        source_directories = process_source_directories(config)
         active_dumps = borgmatic.hooks.dispatch.call_hooks(
             'dump_data_sources',
             config,
             repository['path'],
             borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
+            config_paths,
             borgmatic_runtime_directory,
             source_directories,
             global_arguments.dry_run,
@@ -235,19 +205,9 @@ def run_create(
         # Process source directories again in case any data source hooks updated them. Without this
         # step, we could end up with duplicate paths that cause Borg to hang when it tries to read
         # from the same named pipe twice.
-        source_directories = process_source_directories(config, config_paths, source_directories)
+        source_directories = process_source_directories(config, source_directories)
         stream_processes = [process for processes in active_dumps.values() for process in processes]
 
-        if config.get('store_config_files', True):
-            create_borgmatic_manifest(
-                config,
-                config_paths,
-                borgmatic_runtime_directory,
-                global_arguments.dry_run,
-            )
-            if not global_arguments.dry_run:
-                source_directories.append(os.path.join(borgmatic_runtime_directory, 'bootstrap'))
-
         json_output = borgmatic.borg.create.create_archive(
             global_arguments.dry_run,
             repository['path'],

+ 19 - 0
borgmatic/config/normalize.py

@@ -93,6 +93,25 @@ def normalize(config_filename, config):
         )
         config['exclude_if_present'] = [exclude_if_present]
 
+    # Unconditionally set the bootstrap hook so that it's enabled by default and config files get
+    # stored in each Borg archive.
+    config.setdefault('bootstrap', {})
+
+    # Move store_config_files from the global scope to the bootstrap hook.
+    store_config_files = config.get('store_config_files')
+    if store_config_files is not None:
+        logs.append(
+            logging.makeLogRecord(
+                dict(
+                    levelno=logging.WARNING,
+                    levelname='WARNING',
+                    msg=f'{config_filename}: The store_config_files option has moved under the bootstrap hook. Specifying store_config_files at the global scope is deprecated and support will be removed from a future release.',
+                )
+            )
+        )
+        del config['store_config_files']
+        config['bootstrap']['store_config_files'] = store_config_files
+
     # Upgrade various monitoring hooks from a string to a dict.
     healthchecks = config.get('healthchecks')
     if isinstance(healthchecks, str):

+ 14 - 7
borgmatic/config/schema.yaml

@@ -229,13 +229,6 @@ properties:
             create the check records again (and therefore re-run checks).
             Defaults to $XDG_STATE_HOME or ~/.local/state.
         example: /var/lib/borgmatic
-    store_config_files:
-        type: boolean
-        description: |
-            Store configuration files used to create a backup in the backup
-            itself. Defaults to true. Changing this to false prevents "borgmatic
-            bootstrap" from extracting configuration files from the backup.
-        example: false
     source_directories_must_exist:
         type: boolean
         description: |
@@ -942,6 +935,20 @@ properties:
             of them (after any action).
         example:
             - "echo Completed actions."
+    bootstrap:
+        type: object
+        properties:
+            store_config_files:
+                type: boolean
+                description: |
+                    Store configuration files used to create a backup inside the
+                    backup itself. Defaults to true. Changing this to false
+                    prevents "borgmatic bootstrap" from extracting configuration
+                    files from the backup.
+                example: false
+        description: |
+            Support for the "borgmatic bootstrap" action, used to extract
+            borgmatic configuration files from a backup archive.
     postgresql_databases:
         type: array
         items:

+ 2 - 0
borgmatic/hooks/dispatch.py

@@ -2,6 +2,7 @@ import logging
 
 from borgmatic.hooks import (
     apprise,
+    bootstrap,
     cronhub,
     cronitor,
     healthchecks,
@@ -23,6 +24,7 @@ logger = logging.getLogger(__name__)
 
 HOOK_NAME_TO_MODULE = {
     'apprise': apprise,
+    'bootstrap': bootstrap,
     'cronhub': cronhub,
     'cronitor': cronitor,
     'healthchecks': healthchecks,

+ 1 - 0
borgmatic/hooks/dump.py

@@ -6,6 +6,7 @@ import shutil
 logger = logging.getLogger(__name__)
 
 DATA_SOURCE_HOOK_NAMES = (
+    'bootstrap',
     'mariadb_databases',
     'mysql_databases',
     'mongodb_databases',

+ 1 - 0
borgmatic/hooks/mariadb.py

@@ -126,6 +126,7 @@ def dump_data_sources(
     databases,
     config,
     log_prefix,
+    config_paths,
     borgmatic_runtime_directory,
     source_directories,
     dry_run,

+ 1 - 0
borgmatic/hooks/mongodb.py

@@ -28,6 +28,7 @@ def dump_data_sources(
     databases,
     config,
     log_prefix,
+    config_paths,
     borgmatic_runtime_directory,
     source_directories,
     dry_run,

+ 1 - 0
borgmatic/hooks/mysql.py

@@ -125,6 +125,7 @@ def dump_data_sources(
     databases,
     config,
     log_prefix,
+    config_paths,
     borgmatic_runtime_directory,
     source_directories,
     dry_run,

+ 1 - 0
borgmatic/hooks/postgresql.py

@@ -108,6 +108,7 @@ def dump_data_sources(
     databases,
     config,
     log_prefix,
+    config_paths,
     borgmatic_runtime_directory,
     source_directories,
     dry_run,

+ 1 - 0
borgmatic/hooks/sqlite.py

@@ -28,6 +28,7 @@ def dump_data_sources(
     databases,
     config,
     log_prefix,
+    config_paths,
     borgmatic_runtime_directory,
     source_directories,
     dry_run,

+ 20 - 9
borgmatic/hooks/zfs.py

@@ -120,17 +120,19 @@ def dump_data_sources(
     hook_config,
     config,
     log_prefix,
+    config_paths,
     borgmatic_runtime_directory,
     source_directories,
     dry_run,
 ):
     '''
-    Given a ZFS configuration dict, a configuration dict, a log prefix, the borgmatic runtime
-    directory, the configured source directories, and whether this is a dry run, auto-detect and
-    snapshot any ZFS dataset mount points listed in the given source directories and any dataset
-    with a borgmatic-specific user property. Also update those source directories, replacing dataset
-    mount points with corresponding snapshot directories so they get stored in the Borg archive
-    instead of the dataset mount points. Use the log prefix in any log entries.
+    Given a ZFS configuration dict, a configuration dict, a log prefix, the borgmatic configuration
+    file paths, the borgmatic runtime directory, the configured source directories, and whether this
+    is a dry run, auto-detect and snapshot any ZFS dataset mount points listed in the given source
+    directories and any dataset with a borgmatic-specific user property. Also update those source
+    directories, replacing dataset mount points with corresponding snapshot directories so they get
+    stored in the Borg archive instead of the dataset mount points. Use the log prefix in any log
+    entries.
 
     Return an empty sequence, since there are no ongoing dump processes from this hook.
 
@@ -306,15 +308,24 @@ def remove_data_source_dumps(hook_config, config, log_prefix, borgmatic_runtime_
             destroy_snapshot(zfs_command, full_snapshot_name)
 
 
-def make_data_source_dump_patterns(hook_config, config, log_prefix, name=None):  # pragma: no cover
+def make_data_source_dump_patterns(
+    hook_config, config, log_prefix, borgmatic_runtime_directory, name=None
+):  # pragma: no cover
     '''
     Restores aren't implemented, because stored files can be extracted directly with "extract".
     '''
-    raise NotImplementedError()
+    return ()
 
 
 def restore_data_source_dump(
-    hook_config, config, log_prefix, data_source, dry_run, extract_process, connection_params
+    hook_config,
+    config,
+    log_prefix,
+    data_source,
+    dry_run,
+    extract_process,
+    connection_params,
+    borgmatic_runtime_directory,
 ):  # pragma: no cover
     '''
     Restores aren't implemented, because stored files can be extracted directly with "extract".

+ 22 - 10
docs/how-to/extract-a-backup.md

@@ -202,13 +202,25 @@ borgmatic config bootstrap --repository repo.borg --archive host-2023-01-02T04:0
 See the output of `config bootstrap --help` for additional flags you may need
 for bootstrapping.
 
-<span class="minilink minilink-addedin">New in version 1.8.1</span> Set the
-`store_config_files` option to `false` to disable the automatic backup of
-borgmatic configuration files, for instance if they contain sensitive
-information you don't want to store even inside your encrypted backups. If you
-do this though, the `config bootstrap` action will no longer work.
-
-<span class="minilink minilink-addedin">New in version 1.8.7</span> Included
-configuration files are stored in each backup archive. This means that the
-`config bootstrap` action not only extracts the top-level configuration files
-but also the includes they depend upon.
+<span class="minilink minilink-addedin">New in version 1.9.3</span>
+If your borgmatic configuration files contain sensitive information you don't
+want to store even inside your encrypted backups, you can disable the
+automatic backup of the configuration files. To do this, set the
+`store_config_files` option under the `bootstrap` hook to `false`. For
+instance:
+
+```yaml
+bootstrap:
+    store_config_files: false
+```
+
+If you do this though, the `config bootstrap` action will no longer work.
+
+<span class="minilink minilink-addedin">In version 1.8.1 through 1.9.2</span>
+The `store_config_files` option was at the global scope instead of under the
+`bootstrap` hook.
+
+<span class="minilink minilink-addedin">New in version 1.8.7</span>
+Configuration file includes are stored in each backup archive. This means that
+the `config bootstrap` action not only extracts the top-level configuration
+files but also the includes they depend upon.

+ 6 - 0
tests/integration/config/test_validate.py

@@ -67,6 +67,7 @@ def test_parse_configuration_transforms_file_into_mapping():
         'keep_hourly': 24,
         'keep_minutely': 60,
         'checks': [{'name': 'repository'}, {'name': 'archives'}],
+        'bootstrap': {},
     }
     assert config_paths == {'/tmp/config.yaml'}
     assert logs == []
@@ -90,6 +91,7 @@ def test_parse_configuration_passes_through_quoted_punctuation():
     assert config == {
         'source_directories': [f'/home/{string.punctuation}'],
         'repositories': [{'path': 'test.borg'}],
+        'bootstrap': {},
     }
     assert config_paths == {'/tmp/config.yaml'}
     assert logs == []
@@ -150,6 +152,7 @@ def test_parse_configuration_inlines_include_inside_deprecated_section():
         'repositories': [{'path': 'hostname.borg'}],
         'keep_daily': 7,
         'keep_hourly': 24,
+        'bootstrap': {},
     }
     assert config_paths == {'/tmp/include.yaml', '/tmp/config.yaml'}
     assert len(logs) == 1
@@ -185,6 +188,7 @@ def test_parse_configuration_merges_include():
         'repositories': [{'path': 'hostname.borg'}],
         'keep_daily': 1,
         'keep_hourly': 24,
+        'bootstrap': {},
     }
     assert config_paths == {'/tmp/include.yaml', '/tmp/config.yaml'}
     assert logs == []
@@ -248,6 +252,7 @@ def test_parse_configuration_applies_overrides():
         'source_directories': ['/home'],
         'repositories': [{'path': 'hostname.borg'}],
         'local_path': 'borg2',
+        'bootstrap': {},
     }
     assert config_paths == {'/tmp/config.yaml'}
     assert logs == []
@@ -274,6 +279,7 @@ def test_parse_configuration_applies_normalization_after_environment_variable_in
         'source_directories': ['/home'],
         'repositories': [{'path': 'ssh://user@hostname/./repo'}],
         'exclude_if_present': ['.nobackup'],
+        'bootstrap': {},
     }
     assert config_paths == {'/tmp/config.yaml'}
     assert logs

+ 2 - 122
tests/unit/actions/test_create.py

@@ -1,61 +1,9 @@
-import sys
-
 import pytest
 from flexmock import flexmock
 
 from borgmatic.actions import create as module
 
 
-def test_create_borgmatic_manifest_creates_manifest_file():
-    flexmock(module.os.path).should_receive('join').with_args(
-        '/run/borgmatic', 'bootstrap', 'manifest.json'
-    ).and_return('/run/borgmatic/bootstrap/manifest.json')
-    flexmock(module.os.path).should_receive('exists').and_return(False)
-    flexmock(module.os).should_receive('makedirs').and_return(True)
-
-    flexmock(module.importlib.metadata).should_receive('version').and_return('1.0.0')
-    flexmock(sys.modules['builtins']).should_receive('open').with_args(
-        '/run/borgmatic/bootstrap/manifest.json', 'w'
-    ).and_return(
-        flexmock(
-            __enter__=lambda *args: flexmock(write=lambda *args: None, close=lambda *args: None),
-            __exit__=lambda *args: None,
-        )
-    )
-    flexmock(module.json).should_receive('dump').and_return(True).once()
-
-    module.create_borgmatic_manifest({}, 'test.yaml', '/run/borgmatic', False)
-
-
-def test_create_borgmatic_manifest_creates_manifest_file_with_custom_borgmatic_runtime_directory():
-    flexmock(module.os.path).should_receive('join').with_args(
-        '/run/borgmatic', 'bootstrap', 'manifest.json'
-    ).and_return('/run/borgmatic/bootstrap/manifest.json')
-    flexmock(module.os.path).should_receive('exists').and_return(False)
-    flexmock(module.os).should_receive('makedirs').and_return(True)
-
-    flexmock(module.importlib.metadata).should_receive('version').and_return('1.0.0')
-    flexmock(sys.modules['builtins']).should_receive('open').with_args(
-        '/run/borgmatic/bootstrap/manifest.json', 'w'
-    ).and_return(
-        flexmock(
-            __enter__=lambda *args: flexmock(write=lambda *args: None, close=lambda *args: None),
-            __exit__=lambda *args: None,
-        )
-    )
-    flexmock(module.json).should_receive('dump').and_return(True).once()
-
-    module.create_borgmatic_manifest(
-        {'borgmatic_runtime_directory': '/borgmatic'}, 'test.yaml', '/run/borgmatic', False
-    )
-
-
-def test_create_borgmatic_manifest_does_not_create_manifest_file_on_dry_run():
-    flexmock(module.json).should_receive('dump').never()
-
-    module.create_borgmatic_manifest({}, 'test.yaml', '/run/borgmatic', True)
-
-
 def test_expand_directory_with_basic_path_passes_it_through():
     flexmock(module.os.path).should_receive('expanduser').and_return('foo')
     flexmock(module.glob).should_receive('glob').and_return([])
@@ -207,28 +155,7 @@ def test_pattern_root_directories_parses_roots_and_ignores_others():
     ) == ['/root', '/baz']
 
 
-def test_process_source_directories_includes_source_directories_and_config_paths():
-    flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
-        '/working'
-    )
-    flexmock(module).should_receive('deduplicate_directories').and_return(
-        ('foo', 'bar', 'test.yaml')
-    )
-    flexmock(module).should_receive('map_directories_to_devices').and_return({})
-    flexmock(module).should_receive('expand_directories').with_args(
-        ('foo', 'bar', 'test.yaml'), working_directory='/working'
-    ).and_return(()).once()
-    flexmock(module).should_receive('pattern_root_directories').and_return(())
-    flexmock(module).should_receive('expand_directories').with_args(
-        (), working_directory='/working'
-    ).and_return(())
-
-    assert module.process_source_directories(
-        config={'source_directories': ['foo', 'bar']}, config_paths=('test.yaml',)
-    ) == ('foo', 'bar', 'test.yaml')
-
-
-def test_process_source_directories_does_not_include_config_paths_when_store_config_files_is_false():
+def test_process_source_directories_includes_source_directories():
     flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
         '/working'
     )
@@ -243,8 +170,7 @@ def test_process_source_directories_does_not_include_config_paths_when_store_con
     ).and_return(())
 
     assert module.process_source_directories(
-        config={'source_directories': ['foo', 'bar'], 'store_config_files': False},
-        config_paths=('test.yaml',),
+        config={'source_directories': ['foo', 'bar']},
     ) == ('foo', 'bar')
 
 
@@ -264,7 +190,6 @@ def test_process_source_directories_prefers_source_directory_argument_to_config(
 
     assert module.process_source_directories(
         config={'source_directories': ['nope']},
-        config_paths=('test.yaml',),
         source_directories=['foo', 'bar'],
     ) == ('foo', 'bar')
 
@@ -276,7 +201,6 @@ def test_run_create_executes_and_calls_hooks_for_configured_repository():
         flexmock()
     )
     flexmock(module.borgmatic.borg.create).should_receive('create_archive').once()
-    flexmock(module).should_receive('create_borgmatic_manifest').once()
     flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return({})
     flexmock(module.borgmatic.hooks.dispatch).should_receive(
@@ -310,47 +234,6 @@ def test_run_create_executes_and_calls_hooks_for_configured_repository():
     )
 
 
-def test_run_create_with_store_config_files_false_does_not_create_borgmatic_manifest():
-    flexmock(module.logger).answer = lambda message: None
-    flexmock(module.borgmatic.config.validate).should_receive('repositories_match').never()
-    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
-        flexmock()
-    )
-    flexmock(module.borgmatic.borg.create).should_receive('create_archive').once()
-    flexmock(module).should_receive('create_borgmatic_manifest').never()
-    flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
-    flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return({})
-    flexmock(module.borgmatic.hooks.dispatch).should_receive(
-        'call_hooks_even_if_unconfigured'
-    ).and_return({})
-    flexmock(module).should_receive('process_source_directories').and_return([])
-    flexmock(module.os.path).should_receive('join').and_return('/run/borgmatic/bootstrap')
-    create_arguments = flexmock(
-        repository=None,
-        progress=flexmock(),
-        stats=flexmock(),
-        json=False,
-        list_files=flexmock(),
-    )
-    global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
-
-    list(
-        module.run_create(
-            config_filename='test.yaml',
-            repository={'path': 'repo'},
-            config={'store_config_files': False},
-            config_paths=['/tmp/test.yaml'],
-            hook_context={},
-            local_borg_version=None,
-            create_arguments=create_arguments,
-            global_arguments=global_arguments,
-            dry_run_label='',
-            local_path=None,
-            remote_path=None,
-        )
-    )
-
-
 def test_run_create_runs_with_selected_repository():
     flexmock(module.logger).answer = lambda message: None
     flexmock(module.borgmatic.config.validate).should_receive(
@@ -360,7 +243,6 @@ def test_run_create_runs_with_selected_repository():
         flexmock()
     )
     flexmock(module.borgmatic.borg.create).should_receive('create_archive').once()
-    flexmock(module).should_receive('create_borgmatic_manifest').once()
     flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return({})
     flexmock(module.borgmatic.hooks.dispatch).should_receive(
@@ -401,7 +283,6 @@ def test_run_create_bails_if_repository_does_not_match():
     ).once().and_return(False)
     flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').never()
     flexmock(module.borgmatic.borg.create).should_receive('create_archive').never()
-    flexmock(module).should_receive('create_borgmatic_manifest').never()
     create_arguments = flexmock(
         repository=flexmock(),
         progress=flexmock(),
@@ -441,7 +322,6 @@ def test_run_create_produces_json():
     )
     parsed_json = flexmock()
     flexmock(module.borgmatic.actions.json).should_receive('parse_json').and_return(parsed_json)
-    flexmock(module).should_receive('create_borgmatic_manifest').once()
     flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return({})
     flexmock(module.borgmatic.hooks.dispatch).should_receive(

+ 6 - 0
tests/unit/config/test_normalize.py

@@ -136,6 +136,11 @@ def test_normalize_sections_with_only_scalar_raises():
             {'exclude_if_present': ['.nobackup']},
             False,
         ),
+        (
+            {'store_config_files': False},
+            {'bootstrap': {'store_config_files': False}},
+            True,
+        ),
         (
             {'source_directories': ['foo', 'bar']},
             {'source_directories': ['foo', 'bar']},
@@ -259,6 +264,7 @@ def test_normalize_applies_hard_coded_normalization_to_config(
     flexmock(module).should_receive('normalize_sections').and_return([])
 
     logs = module.normalize('test.yaml', config)
+    expected_config.setdefault('bootstrap', {})
 
     assert config == expected_config
 

+ 6 - 0
tests/unit/hooks/test_mariadb.py

@@ -78,6 +78,7 @@ def test_dump_data_sources_dumps_each_database():
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=False,
@@ -108,6 +109,7 @@ def test_dump_data_sources_dumps_with_password():
         [database],
         {},
         'test.yaml',
+        config_paths=('test.yaml',),
         borgmatic_runtime_directory='/run/borgmatic',
         source_directories=[],
         dry_run=False,
@@ -133,6 +135,7 @@ def test_dump_data_sources_dumps_all_databases_at_once():
         databases,
         {},
         'test.yaml',
+        config_paths=('test.yaml',),
         borgmatic_runtime_directory='/run/borgmatic',
         source_directories=[],
         dry_run=False,
@@ -161,6 +164,7 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=False,
@@ -472,6 +476,7 @@ def test_dump_data_sources_errors_for_missing_all_databases():
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=False,
@@ -491,6 +496,7 @@ def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=True,

+ 7 - 0
tests/unit/hooks/test_mongodb.py

@@ -46,6 +46,7 @@ def test_dump_data_sources_runs_mongodump_for_each_database():
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=False,
@@ -68,6 +69,7 @@ def test_dump_data_sources_with_dry_run_skips_mongodump():
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=True,
@@ -106,6 +108,7 @@ def test_dump_data_sources_runs_mongodump_with_hostname_and_port():
         databases,
         {},
         'test.yaml',
+        config_paths=('test.yaml',),
         borgmatic_runtime_directory='/run/borgmatic',
         source_directories=[],
         dry_run=False,
@@ -151,6 +154,7 @@ def test_dump_data_sources_runs_mongodump_with_username_and_password():
         databases,
         {},
         'test.yaml',
+        config_paths=('test.yaml',),
         borgmatic_runtime_directory='/run/borgmatic',
         source_directories=[],
         dry_run=False,
@@ -176,6 +180,7 @@ def test_dump_data_sources_runs_mongodump_with_directory_format():
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=False,
@@ -211,6 +216,7 @@ def test_dump_data_sources_runs_mongodump_with_options():
         databases,
         {},
         'test.yaml',
+        config_paths=('test.yaml',),
         borgmatic_runtime_directory='/run/borgmatic',
         source_directories=[],
         dry_run=False,
@@ -236,6 +242,7 @@ def test_dump_data_sources_runs_mongodumpall_for_all_databases():
         databases,
         {},
         'test.yaml',
+        config_paths=('test.yaml',),
         borgmatic_runtime_directory='/run/borgmatic',
         source_directories=[],
         dry_run=False,

+ 6 - 0
tests/unit/hooks/test_mysql.py

@@ -78,6 +78,7 @@ def test_dump_data_sources_dumps_each_database():
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=False,
@@ -108,6 +109,7 @@ def test_dump_data_sources_dumps_with_password():
         [database],
         {},
         'test.yaml',
+        config_paths=('test.yaml',),
         borgmatic_runtime_directory='/run/borgmatic',
         source_directories=[],
         dry_run=False,
@@ -133,6 +135,7 @@ def test_dump_data_sources_dumps_all_databases_at_once():
         databases,
         {},
         'test.yaml',
+        config_paths=('test.yaml',),
         borgmatic_runtime_directory='/run/borgmatic',
         source_directories=[],
         dry_run=False,
@@ -161,6 +164,7 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=False,
@@ -470,6 +474,7 @@ def test_dump_data_sources_errors_for_missing_all_databases():
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=False,
@@ -489,6 +494,7 @@ def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=True,

+ 12 - 0
tests/unit/hooks/test_postgresql.py

@@ -256,6 +256,7 @@ def test_dump_data_sources_runs_pg_dump_for_each_database():
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=False,
@@ -275,6 +276,7 @@ def test_dump_data_sources_raises_when_no_database_names_to_dump():
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=False,
@@ -291,6 +293,7 @@ def test_dump_data_sources_does_not_raise_when_no_database_names_to_dump():
         databases,
         {},
         'test.yaml',
+        config_paths=('test.yaml',),
         borgmatic_runtime_directory='/run/borgmatic',
         source_directories=[],
         dry_run=True,
@@ -316,6 +319,7 @@ def test_dump_data_sources_with_duplicate_dump_skips_pg_dump():
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=False,
@@ -343,6 +347,7 @@ def test_dump_data_sources_with_dry_run_skips_pg_dump():
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=True,
@@ -388,6 +393,7 @@ def test_dump_data_sources_runs_pg_dump_with_hostname_and_port():
         databases,
         {},
         'test.yaml',
+        config_paths=('test.yaml',),
         borgmatic_runtime_directory='/run/borgmatic',
         source_directories=[],
         dry_run=False,
@@ -431,6 +437,7 @@ def test_dump_data_sources_runs_pg_dump_with_username_and_password():
         databases,
         {},
         'test.yaml',
+        config_paths=('test.yaml',),
         borgmatic_runtime_directory='/run/borgmatic',
         source_directories=[],
         dry_run=False,
@@ -474,6 +481,7 @@ def test_dump_data_sources_with_username_injection_attack_gets_escaped():
         databases,
         {},
         'test.yaml',
+        config_paths=('test.yaml',),
         borgmatic_runtime_directory='/run/borgmatic',
         source_directories=[],
         dry_run=False,
@@ -513,6 +521,7 @@ def test_dump_data_sources_runs_pg_dump_with_directory_format():
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=False,
@@ -555,6 +564,7 @@ def test_dump_data_sources_runs_pg_dump_with_options():
         databases,
         {},
         'test.yaml',
+        config_paths=('test.yaml',),
         borgmatic_runtime_directory='/run/borgmatic',
         source_directories=[],
         dry_run=False,
@@ -584,6 +594,7 @@ def test_dump_data_sources_runs_pg_dumpall_for_all_databases():
         databases,
         {},
         'test.yaml',
+        config_paths=('test.yaml',),
         borgmatic_runtime_directory='/run/borgmatic',
         source_directories=[],
         dry_run=False,
@@ -625,6 +636,7 @@ def test_dump_data_sources_runs_non_default_pg_dump():
         databases,
         {},
         'test.yaml',
+        config_paths=('test.yaml',),
         borgmatic_runtime_directory='/run/borgmatic',
         source_directories=[],
         dry_run=False,

+ 6 - 0
tests/unit/hooks/test_sqlite.py

@@ -31,6 +31,7 @@ def test_dump_data_sources_logs_and_skips_if_dump_already_exists():
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=False,
@@ -61,6 +62,7 @@ def test_dump_data_sources_dumps_each_database():
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=False,
@@ -98,6 +100,7 @@ def test_dump_data_sources_with_path_injection_attack_gets_escaped():
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=False,
@@ -126,6 +129,7 @@ def test_dump_data_sources_with_non_existent_path_warns_and_dumps_database():
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=False,
@@ -156,6 +160,7 @@ def test_dump_data_sources_with_name_all_warns_and_dumps_all_databases():
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=False,
@@ -180,6 +185,7 @@ def test_dump_data_sources_does_not_dump_if_dry_run():
             databases,
             {},
             'test.yaml',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=[],
             dry_run=True,

+ 3 - 0
tests/unit/hooks/test_zfs.py

@@ -77,6 +77,7 @@ def test_dump_data_sources_snapshots_and_mounts_and_updates_source_directories()
             hook_config={},
             config={'source_directories': '/mnt/dataset', 'zfs': {}},
             log_prefix='test',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=source_directories,
             dry_run=False,
@@ -117,6 +118,7 @@ def test_dump_data_sources_uses_custom_commands():
                 'zfs': hook_config,
             },
             log_prefix='test',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=source_directories,
             dry_run=False,
@@ -141,6 +143,7 @@ def test_dump_data_sources_with_dry_run_skips_commands_and_does_not_touch_source
             hook_config={},
             config={'source_directories': '/mnt/dataset', 'zfs': {}},
             log_prefix='test',
+            config_paths=('test.yaml',),
             borgmatic_runtime_directory='/run/borgmatic',
             source_directories=source_directories,
             dry_run=True,