Browse Source

Actually pass the current configuration to credential hooks.

Dan Helfman 3 months ago
parent
commit
b283e379d0

+ 1 - 1
borgmatic/borg/environment.py

@@ -41,7 +41,7 @@ def make_environment(config):
         value = config.get(option_name)
 
         if option_name in CREDENTIAL_OPTIONS and value is not None:
-            value = borgmatic.hooks.credential.parse.resolve_credential(value)
+            value = borgmatic.hooks.credential.parse.resolve_credential(value, config)
 
         if value is not None:
             environment[environment_variable_name] = str(value)

+ 87 - 7
borgmatic/hooks/credential/parse.py

@@ -7,17 +7,97 @@ import borgmatic.hooks.dispatch
 IS_A_HOOK = False
 
 
+class Hash_adapter:
+    '''
+    A Hash_adapter instance wraps an unhashable object and pretends it's hashable. This is intended
+    for passing to a @functools.cache-decorated function to prevent it from complaining that an
+    argument is unhashable. It should only be used for arguments that you don't want to actually
+    impact the cache hashing, because Hash_adapter doesn't actually hash the object's contents.
+
+    Example usage:
+
+        @functools.cache
+        def func(a, b):
+            print(a, b.actual_value)
+            return a
+
+        func(5, Hash_adapter({1: 2, 3: 4}))  # Calls func(), prints, and returns.
+        func(5, Hash_adapter({1: 2, 3: 4}))  # Hits the cache and just returns the value.
+        func(5, Hash_adapter({5: 6, 7: 8}))  # Also uses cache, since the Hash_adapter is ignored.
+
+    In the above function, the "b" value is one that has been wrapped with Hash_adappter, and
+    therefore "b.actual_value" is necessary to access the original value.
+    '''
+
+    def __init__(self, actual_value):
+        self.actual_value = actual_value
+
+    def __eq__(self, other):
+        return True
+
+    def __hash__(self):
+        return 0
+
+
+UNHASHABLE_TYPES = (dict, list, set)
+
+
+def cache_ignoring_unhashable_arguments(function):
+    '''
+    A function decorator that caches calls to the decorated function but ignores any unhashable
+    arguments when performing cache lookups. This is intended to be a drop-in replacement for
+    functools.cache.
+
+    Example usage:
+
+        @cache_ignoring_unhashable_arguments
+        def func(a, b):
+            print(a, b)
+            return a
+
+        func(5, {1: 2, 3: 4})  # Calls func(), prints, and returns.
+        func(5, {1: 2, 3: 4})  # Hits the cache and just returns the value.
+        func(5, {5: 6, 7: 8})  # Also uses cache, since the unhashable value (the dict) is ignored.
+    '''
+
+    @functools.cache
+    def cached_function(*args, **kwargs):
+        return function(
+            *(arg.actual_value if isinstance(arg, Hash_adapter) else arg for arg in args),
+            **{
+                key: value.actual_value if isinstance(value, Hash_adapter) else value
+                for (key, value) in kwargs.items()
+            },
+        )
+
+    @functools.wraps(function)
+    def wrapper_function(*args, **kwargs):
+        return cached_function(
+            *(Hash_adapter(arg) if isinstance(arg, UNHASHABLE_TYPES) else arg for arg in args),
+            **{
+                key: Hash_adapter(value) if isinstance(value, UNHASHABLE_TYPES) else value
+                for (key, value) in kwargs.items()
+            },
+        )
+
+    wrapper_function.cache_clear = cached_function.cache_clear
+
+    return wrapper_function
+
+
 CREDENTIAL_PATTERN = re.compile(r'\{credential( +(?P<hook_and_parameters>.*))?\}')
 
 
-@functools.cache
-def resolve_credential(value):
+@cache_ignoring_unhashable_arguments
+def resolve_credential(value, config):
     '''
-    Given a configuration value containing a string like "{credential hookname credentialname}", resolve it by
-    calling the relevant hook to get the actual credential value. If the given value does not
-    actually contain a credential tag, then return it unchanged.
+    Given a configuration value containing a string like "{credential hookname credentialname}" and
+    a configuration dict, resolve the credential by calling the relevant hook to get the actual
+    credential value. If the given value does not actually contain a credential tag, then return it
+    unchanged.
 
-    Cache the value so repeated calls to this function don't need to load the credential repeatedly.
+    Cache the value (ignoring the config for purposes of caching), so repeated calls to this
+    function don't need to load the credential repeatedly.
 
     Raise ValueError if the config could not be parsed or the credential could not be loaded.
     '''
@@ -40,5 +120,5 @@ def resolve_credential(value):
         raise ValueError(f'Cannot load credential with invalid syntax "{value}"')
 
     return borgmatic.hooks.dispatch.call_hook(
-        'load_credential', {}, hook_name, tuple(credential_parameters)
+        'load_credential', config, hook_name, tuple(credential_parameters)
     )

+ 30 - 12
borgmatic/hooks/data_source/mariadb.py

@@ -26,11 +26,11 @@ def make_dump_path(base_directory):  # pragma: no cover
 SYSTEM_DATABASE_NAMES = ('information_schema', 'mysql', 'performance_schema', 'sys')
 
 
-def database_names_to_dump(database, extra_environment, dry_run):
+def database_names_to_dump(database, config, extra_environment, dry_run):
     '''
-    Given a requested database config, return the corresponding sequence of database names to dump.
-    In the case of "all", query for the names of databases on the configured host and return them,
-    excluding any system databases that will cause problems during restore.
+    Given a requested database config and a configuration dict, return the corresponding sequence of
+    database names to dump. In the case of "all", query for the names of databases on the configured
+    host and return them, excluding any system databases that will cause problems during restore.
     '''
     if database['name'] != 'all':
         return (database['name'],)
@@ -47,7 +47,10 @@ def database_names_to_dump(database, extra_environment, dry_run):
         + (('--port', str(database['port'])) if 'port' in database else ())
         + (('--protocol', 'tcp') if 'hostname' in database or 'port' in database else ())
         + (
-            ('--user', borgmatic.hooks.credential.parse.resolve_credential(database['username']))
+            (
+                '--user',
+                borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
+            )
             if 'username' in database
             else ()
         )
@@ -67,7 +70,7 @@ def database_names_to_dump(database, extra_environment, dry_run):
 
 
 def execute_dump_command(
-    database, dump_path, database_names, extra_environment, dry_run, dry_run_label
+    database, config, dump_path, database_names, extra_environment, dry_run, dry_run_label
 ):
     '''
     Kick off a dump for the given MariaDB database (provided as a configuration dict) to a named
@@ -102,7 +105,10 @@ def execute_dump_command(
         + (('--port', str(database['port'])) if 'port' in database else ())
         + (('--protocol', 'tcp') if 'hostname' in database or 'port' in database else ())
         + (
-            ('--user', borgmatic.hooks.credential.parse.resolve_credential(database['username']))
+            (
+                '--user',
+                borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
+            )
             if 'username' in database
             else ()
         )
@@ -162,7 +168,11 @@ def dump_data_sources(
     for database in databases:
         dump_path = make_dump_path(borgmatic_runtime_directory)
         extra_environment = (
-            {'MYSQL_PWD': borgmatic.hooks.credential.parse.resolve_credential(database['password'])}
+            {
+                'MYSQL_PWD': borgmatic.hooks.credential.parse.resolve_credential(
+                    database['password'], config
+                )
+            }
             if 'password' in database
             else None
         )
@@ -181,6 +191,7 @@ def dump_data_sources(
                 processes.append(
                     execute_dump_command(
                         renamed_database,
+                        config,
                         dump_path,
                         (dump_name,),
                         extra_environment,
@@ -192,6 +203,7 @@ def dump_data_sources(
             processes.append(
                 execute_dump_command(
                     database,
+                    config,
                     dump_path,
                     dump_database_names,
                     extra_environment,
@@ -265,12 +277,18 @@ def restore_data_source_dump(
         connection_params['port'] or data_source.get('restore_port', data_source.get('port', ''))
     )
     username = borgmatic.hooks.credential.parse.resolve_credential(
-        connection_params['username']
-        or data_source.get('restore_username', data_source.get('username'))
+        (
+            connection_params['username']
+            or data_source.get('restore_username', data_source.get('username'))
+        ),
+        config,
     )
     password = borgmatic.hooks.credential.parse.resolve_credential(
-        connection_params['password']
-        or data_source.get('restore_password', data_source.get('password'))
+        (
+            connection_params['password']
+            or data_source.get('restore_password', data_source.get('password'))
+        ),
+        config,
     )
 
     mariadb_restore_command = tuple(

+ 20 - 8
borgmatic/hooks/data_source/mongodb.py

@@ -69,7 +69,7 @@ def dump_data_sources(
         if dry_run:
             continue
 
-        command = build_dump_command(database, dump_filename, dump_format)
+        command = build_dump_command(database, config, dump_filename, dump_format)
 
         if dump_format == 'directory':
             dump.create_parent_directory_for_dump(dump_filename)
@@ -88,7 +88,7 @@ def dump_data_sources(
     return processes
 
 
-def build_dump_command(database, dump_filename, dump_format):
+def build_dump_command(database, config, dump_filename, dump_format):
     '''
     Return the mongodump command from a single database configuration.
     '''
@@ -103,7 +103,9 @@ def build_dump_command(database, dump_filename, dump_format):
             (
                 '--username',
                 shlex.quote(
-                    borgmatic.hooks.credential.parse.resolve_credential(database['username'])
+                    borgmatic.hooks.credential.parse.resolve_credential(
+                        database['username'], config
+                    )
                 ),
             )
             if 'username' in database
@@ -113,7 +115,9 @@ def build_dump_command(database, dump_filename, dump_format):
             (
                 '--password',
                 shlex.quote(
-                    borgmatic.hooks.credential.parse.resolve_credential(database['password'])
+                    borgmatic.hooks.credential.parse.resolve_credential(
+                        database['password'], config
+                    )
                 ),
             )
             if 'password' in database
@@ -192,7 +196,7 @@ def restore_data_source_dump(
         data_source.get('hostname'),
     )
     restore_command = build_restore_command(
-        extract_process, data_source, dump_filename, connection_params
+        extract_process, data_source, config, dump_filename, connection_params
     )
 
     logger.debug(f"Restoring MongoDB database {data_source['name']}{dry_run_label}")
@@ -209,7 +213,7 @@ def restore_data_source_dump(
     )
 
 
-def build_restore_command(extract_process, database, dump_filename, connection_params):
+def build_restore_command(extract_process, database, config, dump_filename, connection_params):
     '''
     Return the mongorestore command from a single database configuration.
     '''
@@ -218,10 +222,18 @@ def build_restore_command(extract_process, database, dump_filename, connection_p
     )
     port = str(connection_params['port'] or database.get('restore_port', database.get('port', '')))
     username = borgmatic.hooks.credential.parse.resolve_credential(
-        connection_params['username'] or database.get('restore_username', database.get('username'))
+        (
+            connection_params['username']
+            or database.get('restore_username', database.get('username'))
+        ),
+        config,
     )
     password = borgmatic.hooks.credential.parse.resolve_credential(
-        connection_params['password'] or database.get('restore_password', database.get('password'))
+        (
+            connection_params['password']
+            or database.get('restore_password', database.get('password'))
+        ),
+        config,
     )
 
     command = ['mongorestore']

+ 31 - 13
borgmatic/hooks/data_source/mysql.py

@@ -26,11 +26,11 @@ def make_dump_path(base_directory):  # pragma: no cover
 SYSTEM_DATABASE_NAMES = ('information_schema', 'mysql', 'performance_schema', 'sys')
 
 
-def database_names_to_dump(database, extra_environment, dry_run):
+def database_names_to_dump(database, config, extra_environment, dry_run):
     '''
-    Given a requested database config, return the corresponding sequence of database names to dump.
-    In the case of "all", query for the names of databases on the configured host and return them,
-    excluding any system databases that will cause problems during restore.
+    Given a requested database config and a configuration dict, return the corresponding sequence of
+    database names to dump. In the case of "all", query for the names of databases on the configured
+    host and return them, excluding any system databases that will cause problems during restore.
     '''
     if database['name'] != 'all':
         return (database['name'],)
@@ -47,7 +47,10 @@ def database_names_to_dump(database, extra_environment, dry_run):
         + (('--port', str(database['port'])) if 'port' in database else ())
         + (('--protocol', 'tcp') if 'hostname' in database or 'port' in database else ())
         + (
-            ('--user', borgmatic.hooks.credential.parse.resolve_credential(database['username']))
+            (
+                '--user',
+                borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
+            )
             if 'username' in database
             else ()
         )
@@ -67,7 +70,7 @@ def database_names_to_dump(database, extra_environment, dry_run):
 
 
 def execute_dump_command(
-    database, dump_path, database_names, extra_environment, dry_run, dry_run_label
+    database, config, dump_path, database_names, extra_environment, dry_run, dry_run_label
 ):
     '''
     Kick off a dump for the given MySQL/MariaDB database (provided as a configuration dict) to a
@@ -101,7 +104,10 @@ def execute_dump_command(
         + (('--port', str(database['port'])) if 'port' in database else ())
         + (('--protocol', 'tcp') if 'hostname' in database or 'port' in database else ())
         + (
-            ('--user', borgmatic.hooks.credential.parse.resolve_credential(database['username']))
+            (
+                '--user',
+                borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
+            )
             if 'username' in database
             else ()
         )
@@ -161,11 +167,15 @@ def dump_data_sources(
     for database in databases:
         dump_path = make_dump_path(borgmatic_runtime_directory)
         extra_environment = (
-            {'MYSQL_PWD': borgmatic.hooks.credential.parse.resolve_credential(database['password'])}
+            {
+                'MYSQL_PWD': borgmatic.hooks.credential.parse.resolve_credential(
+                    database['password'], config
+                )
+            }
             if 'password' in database
             else None
         )
-        dump_database_names = database_names_to_dump(database, extra_environment, dry_run)
+        dump_database_names = database_names_to_dump(database, config, extra_environment, dry_run)
 
         if not dump_database_names:
             if dry_run:
@@ -180,6 +190,7 @@ def dump_data_sources(
                 processes.append(
                     execute_dump_command(
                         renamed_database,
+                        config,
                         dump_path,
                         (dump_name,),
                         extra_environment,
@@ -191,6 +202,7 @@ def dump_data_sources(
             processes.append(
                 execute_dump_command(
                     database,
+                    config,
                     dump_path,
                     dump_database_names,
                     extra_environment,
@@ -264,12 +276,18 @@ def restore_data_source_dump(
         connection_params['port'] or data_source.get('restore_port', data_source.get('port', ''))
     )
     username = borgmatic.hooks.credential.parse.resolve_credential(
-        connection_params['username']
-        or data_source.get('restore_username', data_source.get('username'))
+        (
+            connection_params['username']
+            or data_source.get('restore_username', data_source.get('username'))
+        ),
+        config,
     )
     password = borgmatic.hooks.credential.parse.resolve_credential(
-        connection_params['password']
-        or data_source.get('restore_password', data_source.get('password'))
+        (
+            connection_params['password']
+            or data_source.get('restore_password', data_source.get('password'))
+        ),
+        config,
     )
 
     mysql_restore_command = tuple(

+ 22 - 16
borgmatic/hooks/data_source/postgresql.py

@@ -25,7 +25,7 @@ def make_dump_path(base_directory):  # pragma: no cover
     return dump.make_data_source_dump_path(base_directory, 'postgresql_databases')
 
 
-def make_extra_environment(database, restore_connection_params=None):
+def make_extra_environment(database, config, restore_connection_params=None):
     '''
     Make the extra_environment dict from the given database configuration. If restore connection
     params are given, this is for a restore operation.
@@ -35,12 +35,15 @@ def make_extra_environment(database, restore_connection_params=None):
     try:
         if restore_connection_params:
             extra['PGPASSWORD'] = borgmatic.hooks.credential.parse.resolve_credential(
-                restore_connection_params.get('password')
-                or database.get('restore_password', database['password'])
+                (
+                    restore_connection_params.get('password')
+                    or database.get('restore_password', database['password'])
+                ),
+                config,
             )
         else:
             extra['PGPASSWORD'] = borgmatic.hooks.credential.parse.resolve_credential(
-                database['password']
+                database['password'], config
             )
     except (AttributeError, KeyError):
         pass
@@ -62,12 +65,12 @@ def make_extra_environment(database, restore_connection_params=None):
 EXCLUDED_DATABASE_NAMES = ('template0', 'template1')
 
 
-def database_names_to_dump(database, extra_environment, dry_run):
+def database_names_to_dump(database, config, extra_environment, dry_run):
     '''
-    Given a requested database config, return the corresponding sequence of database names to dump.
-    In the case of "all" when a database format is given, query for the names of databases on the
-    configured host and return them. For "all" without a database format, just return a sequence
-    containing "all".
+    Given a requested database config and a configuration dict, return the corresponding sequence of
+    database names to dump. In the case of "all" when a database format is given, query for the
+    names of databases on the configured host and return them. For "all" without a database format,
+    just return a sequence containing "all".
     '''
     requested_name = database['name']
 
@@ -89,7 +92,7 @@ def database_names_to_dump(database, extra_environment, dry_run):
         + (
             (
                 '--username',
-                borgmatic.hooks.credential.parse.resolve_credential(database['username']),
+                borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
             )
             if 'username' in database
             else ()
@@ -146,9 +149,9 @@ def dump_data_sources(
     logger.info(f'Dumping PostgreSQL databases{dry_run_label}')
 
     for database in databases:
-        extra_environment = make_extra_environment(database)
+        extra_environment = make_extra_environment(database, config)
         dump_path = make_dump_path(borgmatic_runtime_directory)
-        dump_database_names = database_names_to_dump(database, extra_environment, dry_run)
+        dump_database_names = database_names_to_dump(database, config, extra_environment, dry_run)
 
         if not dump_database_names:
             if dry_run:
@@ -189,7 +192,7 @@ def dump_data_sources(
                         '--username',
                         shlex.quote(
                             borgmatic.hooks.credential.parse.resolve_credential(
-                                database['username']
+                                database['username'], config
                             )
                         ),
                     )
@@ -309,8 +312,11 @@ def restore_data_source_dump(
         connection_params['port'] or data_source.get('restore_port', data_source.get('port', ''))
     )
     username = borgmatic.hooks.credential.parse.resolve_credential(
-        connection_params['username']
-        or data_source.get('restore_username', data_source.get('username'))
+        (
+            connection_params['username']
+            or data_source.get('restore_username', data_source.get('username'))
+        ),
+        config,
     )
 
     all_databases = bool(data_source['name'] == 'all')
@@ -364,7 +370,7 @@ def restore_data_source_dump(
     )
 
     extra_environment = make_extra_environment(
-        data_source, restore_connection_params=connection_params
+        data_source, config, restore_connection_params=connection_params
     )
 
     logger.debug(f"Restoring PostgreSQL database {data_source['name']}{dry_run_label}")

+ 3 - 3
borgmatic/hooks/monitoring/ntfy.py

@@ -51,13 +51,13 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
 
         try:
             username = borgmatic.hooks.credential.parse.resolve_credential(
-                hook_config.get('username')
+                hook_config.get('username'), config
             )
             password = borgmatic.hooks.credential.parse.resolve_credential(
-                hook_config.get('password')
+                hook_config.get('password'), config
             )
             access_token = borgmatic.hooks.credential.parse.resolve_credential(
-                hook_config.get('access_token')
+                hook_config.get('access_token'), config
             )
         except ValueError as error:
             logger.warning(f'Ntfy credential error: {error}')

+ 1 - 1
borgmatic/hooks/monitoring/pagerduty.py

@@ -42,7 +42,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
 
     try:
         integration_key = borgmatic.hooks.credential.parse.resolve_credential(
-            hook_config.get('integration_key')
+            hook_config.get('integration_key'), config
         )
     except ValueError as error:
         logger.warning(f'PagerDuty credential error: {error}')

+ 4 - 2
borgmatic/hooks/monitoring/pushover.py

@@ -35,8 +35,10 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
     state_config = hook_config.get(state.name.lower(), {})
 
     try:
-        token = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('token'))
-        user = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('user'))
+        token = borgmatic.hooks.credential.parse.resolve_credential(
+            hook_config.get('token'), config
+        )
+        user = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('user'), config)
     except ValueError as error:
         logger.warning(f'Pushover credential error: {error}')
         return

+ 9 - 3
borgmatic/hooks/monitoring/zabbix.py

@@ -37,9 +37,15 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
     )
 
     try:
-        username = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('username'))
-        password = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('password'))
-        api_key = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('api_key'))
+        username = borgmatic.hooks.credential.parse.resolve_credential(
+            hook_config.get('username'), config
+        )
+        password = borgmatic.hooks.credential.parse.resolve_credential(
+            hook_config.get('password'), config
+        )
+        api_key = borgmatic.hooks.credential.parse.resolve_credential(
+            hook_config.get('api_key'), config
+        )
     except ValueError as error:
         logger.warning(f'Zabbix credential error: {error}')
         return

+ 5 - 4
tests/unit/borg/test_environment.py

@@ -26,7 +26,7 @@ def test_make_environment_with_passphrase_should_set_environment():
     flexmock(module.os.environ).should_receive('get').and_return(None)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
 
     environment = module.make_environment({'encryption_passphrase': 'pass'})
 
@@ -34,16 +34,17 @@ def test_make_environment_with_passphrase_should_set_environment():
 
 
 def test_make_environment_with_credential_tag_passphrase_should_load_it_and_set_environment():
+    config = {'encryption_passphrase': '{credential systemd pass}'}
     flexmock(module.borgmatic.borg.passcommand).should_receive(
         'get_passphrase_from_passcommand'
     ).and_return(None)
     flexmock(module.os).should_receive('pipe').never()
     flexmock(module.os.environ).should_receive('get').and_return(None)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
-        'resolve_credential'
-    ).with_args('{credential systemd pass}').and_return('pass')
+        'resolve_credential',
+    ).with_args('{credential systemd pass}', config).and_return('pass')
 
-    environment = module.make_environment({'encryption_passphrase': '{credential systemd pass}'})
+    environment = module.make_environment(config)
 
     assert environment.get('BORG_PASSPHRASE') == 'pass'
 

+ 38 - 7
tests/unit/hooks/credential/test_parse.py

@@ -4,18 +4,49 @@ from flexmock import flexmock
 from borgmatic.hooks.credential import parse as module
 
 
+def test_hash_adapter_is_always_equal():
+    assert module.Hash_adapter({1: 2}) == module.Hash_adapter({3: 4})
+
+
+def test_hash_adapter_alwaysh_hashes_the_same():
+    assert hash(module.Hash_adapter({1: 2})) == hash(module.Hash_adapter({3: 4}))
+
+
+def test_cache_ignoring_unhashable_arguments_caches_arguments_after_first_call():
+    hashable = 3
+    unhashable = {1, 2}
+    calls = 0
+
+    @module.cache_ignoring_unhashable_arguments
+    def function(first, second, third):
+        nonlocal calls
+        calls += 1
+
+        assert first == hashable
+        assert second == unhashable
+        assert third == unhashable
+
+        return first
+
+    assert function(hashable, unhashable, third=unhashable) == hashable
+    assert calls == 1
+
+    assert function(hashable, unhashable, third=unhashable) == hashable
+    assert calls == 1
+
+
 def test_resolve_credential_passes_through_string_without_credential():
     module.resolve_credential.cache_clear()
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').never()
 
-    assert module.resolve_credential('{no credentials here}') == '{no credentials here}'
+    assert module.resolve_credential('{no credentials here}', config={}) == '{no credentials here}'
 
 
 def test_resolve_credential_passes_through_none():
     module.resolve_credential.cache_clear()
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').never()
 
-    assert module.resolve_credential(None) is None
+    assert module.resolve_credential(None, config={}) is None
 
 
 @pytest.mark.parametrize('invalid_value', ('{credential}', '{credential }', '{credential systemd}'))
@@ -24,7 +55,7 @@ def test_resolve_credential_with_invalid_credential_raises(invalid_value):
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').never()
 
     with pytest.raises(ValueError):
-        module.resolve_credential(invalid_value)
+        module.resolve_credential(invalid_value, config={})
 
 
 def test_resolve_credential_with_valid_credential_loads_credential():
@@ -36,7 +67,7 @@ def test_resolve_credential_with_valid_credential_loads_credential():
         ('mycredential',),
     ).and_return('result').once()
 
-    assert module.resolve_credential('{credential systemd mycredential}') == 'result'
+    assert module.resolve_credential('{credential systemd mycredential}', config={}) == 'result'
 
 
 def test_resolve_credential_with_valid_credential_and_quoted_parameters_loads_credential():
@@ -48,7 +79,7 @@ def test_resolve_credential_with_valid_credential_and_quoted_parameters_loads_cr
         ('my credential',),
     ).and_return('result').once()
 
-    assert module.resolve_credential('{credential systemd "my credential"}') == 'result'
+    assert module.resolve_credential('{credential systemd "my credential"}', config={}) == 'result'
 
 
 def test_resolve_credential_caches_credential_after_first_call():
@@ -60,5 +91,5 @@ def test_resolve_credential_caches_credential_after_first_call():
         ('mycredential',),
     ).and_return('result').once()
 
-    assert module.resolve_credential('{credential systemd mycredential}') == 'result'
-    assert module.resolve_credential('{credential systemd mycredential}') == 'result'
+    assert module.resolve_credential('{credential systemd mycredential}', config={}) == 'result'
+    assert module.resolve_credential('{credential systemd mycredential}', config={}) == 'result'

+ 39 - 27
tests/unit/hooks/data_source/test_mariadb.py

@@ -9,7 +9,7 @@ from borgmatic.hooks.data_source import mariadb as module
 def test_database_names_to_dump_passes_through_name():
     extra_environment = flexmock()
 
-    names = module.database_names_to_dump({'name': 'foo'}, extra_environment, dry_run=False)
+    names = module.database_names_to_dump({'name': 'foo'}, {}, extra_environment, dry_run=False)
 
     assert names == ('foo',)
 
@@ -18,7 +18,7 @@ def test_database_names_to_dump_bails_for_dry_run():
     extra_environment = flexmock()
     flexmock(module).should_receive('execute_command_and_capture_output').never()
 
-    names = module.database_names_to_dump({'name': 'all'}, extra_environment, dry_run=True)
+    names = module.database_names_to_dump({'name': 'all'}, {}, extra_environment, dry_run=True)
 
     assert names == ()
 
@@ -27,13 +27,13 @@ def test_database_names_to_dump_queries_mariadb_for_database_names():
     extra_environment = flexmock()
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_and_capture_output').with_args(
         ('mariadb', '--skip-column-names', '--batch', '--execute', 'show schemas'),
         extra_environment=extra_environment,
     ).and_return('foo\nbar\nmysql\n').once()
 
-    names = module.database_names_to_dump({'name': 'all'}, extra_environment, dry_run=False)
+    names = module.database_names_to_dump({'name': 'all'}, {}, extra_environment, dry_run=False)
 
     assert names == ('foo', 'bar')
 
@@ -55,7 +55,7 @@ def test_dump_data_sources_dumps_each_database():
     flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
         ('bar',)
     )
@@ -63,6 +63,7 @@ def test_dump_data_sources_dumps_each_database():
     for name, process in zip(('foo', 'bar'), processes):
         flexmock(module).should_receive('execute_dump_command').with_args(
             database={'name': name},
+            config={},
             dump_path=object,
             database_names=(name,),
             extra_environment=object,
@@ -89,13 +90,14 @@ def test_dump_data_sources_dumps_with_password():
     flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
         ('bar',)
     )
 
     flexmock(module).should_receive('execute_dump_command').with_args(
         database=database,
+        config={},
         dump_path=object,
         database_names=('foo',),
         extra_environment={'MYSQL_PWD': 'trustsome1'},
@@ -119,10 +121,11 @@ def test_dump_data_sources_dumps_all_databases_at_once():
     flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
     flexmock(module).should_receive('execute_dump_command').with_args(
         database={'name': 'all'},
+        config={},
         dump_path=object,
         database_names=('foo', 'bar'),
         extra_environment=object,
@@ -146,12 +149,13 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
     flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
 
     for name, process in zip(('foo', 'bar'), processes):
         flexmock(module).should_receive('execute_dump_command').with_args(
             database={'name': name, 'format': 'sql'},
+            config={},
             dump_path=object,
             database_names=(name,),
             extra_environment=object,
@@ -186,7 +190,7 @@ def test_database_names_to_dump_runs_mariadb_with_list_options():
         extra_environment=None,
     ).and_return(('foo\nbar')).once()
 
-    assert module.database_names_to_dump(database, None, '') == ('foo', 'bar')
+    assert module.database_names_to_dump(database, {}, None, '') == ('foo', 'bar')
 
 
 def test_database_names_to_dump_runs_non_default_mariadb_with_list_options():
@@ -207,7 +211,7 @@ def test_database_names_to_dump_runs_non_default_mariadb_with_list_options():
         ),
     ).and_return(('foo\nbar')).once()
 
-    assert module.database_names_to_dump(database, None, '') == ('foo', 'bar')
+    assert module.database_names_to_dump(database, {}, None, '') == ('foo', 'bar')
 
 
 def test_execute_dump_command_runs_mariadb_dump():
@@ -216,7 +220,7 @@ def test_execute_dump_command_runs_mariadb_dump():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -235,6 +239,7 @@ def test_execute_dump_command_runs_mariadb_dump():
     assert (
         module.execute_dump_command(
             database={'name': 'foo'},
+            config={},
             dump_path=flexmock(),
             database_names=('foo',),
             extra_environment=None,
@@ -251,7 +256,7 @@ def test_execute_dump_command_runs_mariadb_dump_without_add_drop_database():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -269,6 +274,7 @@ def test_execute_dump_command_runs_mariadb_dump_without_add_drop_database():
     assert (
         module.execute_dump_command(
             database={'name': 'foo', 'add_drop_database': False},
+            config={},
             dump_path=flexmock(),
             database_names=('foo',),
             extra_environment=None,
@@ -285,7 +291,7 @@ def test_execute_dump_command_runs_mariadb_dump_with_hostname_and_port():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -310,6 +316,7 @@ def test_execute_dump_command_runs_mariadb_dump_with_hostname_and_port():
     assert (
         module.execute_dump_command(
             database={'name': 'foo', 'hostname': 'database.example.org', 'port': 5433},
+            config={},
             dump_path=flexmock(),
             database_names=('foo',),
             extra_environment=None,
@@ -326,7 +333,7 @@ def test_execute_dump_command_runs_mariadb_dump_with_username_and_password():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -347,6 +354,7 @@ def test_execute_dump_command_runs_mariadb_dump_with_username_and_password():
     assert (
         module.execute_dump_command(
             database={'name': 'foo', 'username': 'root', 'password': 'trustsome1'},
+            config={},
             dump_path=flexmock(),
             database_names=('foo',),
             extra_environment={'MYSQL_PWD': 'trustsome1'},
@@ -363,7 +371,7 @@ def test_execute_dump_command_runs_mariadb_dump_with_options():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -383,6 +391,7 @@ def test_execute_dump_command_runs_mariadb_dump_with_options():
     assert (
         module.execute_dump_command(
             database={'name': 'foo', 'options': '--stuff=such'},
+            config={},
             dump_path=flexmock(),
             database_names=('foo',),
             extra_environment=None,
@@ -399,7 +408,7 @@ def test_execute_dump_command_runs_non_default_mariadb_dump_with_options():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -423,6 +432,7 @@ def test_execute_dump_command_runs_non_default_mariadb_dump_with_options():
                 'mariadb_dump_command': 'custom_mariadb_dump',
                 'options': '--stuff=such',
             },  # Custom MariaDB dump command specified
+            config={},
             dump_path=flexmock(),
             database_names=('foo',),
             extra_environment=None,
@@ -442,6 +452,7 @@ def test_execute_dump_command_with_duplicate_dump_skips_mariadb_dump():
     assert (
         module.execute_dump_command(
             database={'name': 'foo'},
+            config={},
             dump_path=flexmock(),
             database_names=('foo',),
             extra_environment=None,
@@ -457,7 +468,7 @@ def test_execute_dump_command_with_dry_run_skips_mariadb_dump():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').never()
@@ -465,6 +476,7 @@ def test_execute_dump_command_with_dry_run_skips_mariadb_dump():
     assert (
         module.execute_dump_command(
             database={'name': 'foo'},
+            config={},
             dump_path=flexmock(),
             database_names=('foo',),
             extra_environment=None,
@@ -480,7 +492,7 @@ def test_dump_data_sources_errors_for_missing_all_databases():
     flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
         'databases/localhost/all'
     )
@@ -502,7 +514,7 @@ def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run
     flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
         'databases/localhost/all'
     )
@@ -527,7 +539,7 @@ def test_restore_data_source_dump_runs_mariadb_to_restore():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('mariadb', '--batch'),
         processes=[extract_process],
@@ -558,7 +570,7 @@ def test_restore_data_source_dump_runs_mariadb_with_options():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('mariadb', '--batch', '--harder'),
         processes=[extract_process],
@@ -591,7 +603,7 @@ def test_restore_data_source_dump_runs_non_default_mariadb_with_options():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('custom_mariadb', '--batch', '--harder'),
         processes=[extract_process],
@@ -622,7 +634,7 @@ def test_restore_data_source_dump_runs_mariadb_with_hostname_and_port():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
             'mariadb',
@@ -662,7 +674,7 @@ def test_restore_data_source_dump_runs_mariadb_with_username_and_password():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('mariadb', '--batch', '--user', 'root'),
         processes=[extract_process],
@@ -703,7 +715,7 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
             'mariadb',
@@ -757,7 +769,7 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
             'mariadb',
@@ -798,7 +810,7 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').never()
 
     module.restore_data_source_dump(

+ 13 - 13
tests/unit/hooks/data_source/test_mongodb.py

@@ -126,7 +126,7 @@ def test_dump_data_sources_runs_mongodump_with_username_and_password():
     )
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -247,9 +247,9 @@ def test_build_dump_command_with_username_injection_attack_gets_escaped():
     database = {'name': 'test', 'username': 'bob; naughty-command'}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
 
-    command = module.build_dump_command(database, dump_filename='test', dump_format='archive')
+    command = module.build_dump_command(database, {}, dump_filename='test', dump_format='archive')
 
     assert "'bob; naughty-command'" in command
 
@@ -262,7 +262,7 @@ def test_restore_data_source_dump_runs_mongorestore():
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ['mongorestore', '--archive', '--drop'],
         processes=[extract_process],
@@ -296,7 +296,7 @@ def test_restore_data_source_dump_runs_mongorestore_with_hostname_and_port():
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         [
             'mongorestore',
@@ -344,7 +344,7 @@ def test_restore_data_source_dump_runs_mongorestore_with_username_and_password()
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         [
             'mongorestore',
@@ -398,7 +398,7 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         [
             'mongorestore',
@@ -456,7 +456,7 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         [
             'mongorestore',
@@ -502,7 +502,7 @@ def test_restore_data_source_dump_runs_mongorestore_with_options():
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ['mongorestore', '--archive', '--drop', '--harder'],
         processes=[extract_process],
@@ -534,7 +534,7 @@ def test_restore_databases_dump_runs_mongorestore_with_schemas():
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         [
             'mongorestore',
@@ -574,7 +574,7 @@ def test_restore_data_source_dump_runs_psql_for_all_database_dump():
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ['mongorestore', '--archive'],
         processes=[extract_process],
@@ -605,7 +605,7 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').never()
 
     module.restore_data_source_dump(
@@ -631,7 +631,7 @@ def test_restore_data_source_dump_without_extract_process_restores_from_disk():
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ['mongorestore', '--dir', '/dump/path', '--drop'],
         processes=[],

+ 37 - 25
tests/unit/hooks/data_source/test_mysql.py

@@ -9,7 +9,7 @@ from borgmatic.hooks.data_source import mysql as module
 def test_database_names_to_dump_passes_through_name():
     extra_environment = flexmock()
 
-    names = module.database_names_to_dump({'name': 'foo'}, extra_environment, dry_run=False)
+    names = module.database_names_to_dump({'name': 'foo'}, {}, extra_environment, dry_run=False)
 
     assert names == ('foo',)
 
@@ -18,10 +18,10 @@ def test_database_names_to_dump_bails_for_dry_run():
     extra_environment = flexmock()
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_and_capture_output').never()
 
-    names = module.database_names_to_dump({'name': 'all'}, extra_environment, dry_run=True)
+    names = module.database_names_to_dump({'name': 'all'}, {}, extra_environment, dry_run=True)
 
     assert names == ()
 
@@ -30,13 +30,13 @@ def test_database_names_to_dump_queries_mysql_for_database_names():
     extra_environment = flexmock()
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_and_capture_output').with_args(
         ('mysql', '--skip-column-names', '--batch', '--execute', 'show schemas'),
         extra_environment=extra_environment,
     ).and_return('foo\nbar\nmysql\n').once()
 
-    names = module.database_names_to_dump({'name': 'all'}, extra_environment, dry_run=False)
+    names = module.database_names_to_dump({'name': 'all'}, {}, extra_environment, dry_run=False)
 
     assert names == ('foo', 'bar')
 
@@ -63,6 +63,7 @@ def test_dump_data_sources_dumps_each_database():
     for name, process in zip(('foo', 'bar'), processes):
         flexmock(module).should_receive('execute_dump_command').with_args(
             database={'name': name},
+            config={},
             dump_path=object,
             database_names=(name,),
             extra_environment=object,
@@ -89,13 +90,14 @@ def test_dump_data_sources_dumps_with_password():
     flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
         ('bar',)
     )
 
     flexmock(module).should_receive('execute_dump_command').with_args(
         database=database,
+        config={},
         dump_path=object,
         database_names=('foo',),
         extra_environment={'MYSQL_PWD': 'trustsome1'},
@@ -120,6 +122,7 @@ def test_dump_data_sources_dumps_all_databases_at_once():
     flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
     flexmock(module).should_receive('execute_dump_command').with_args(
         database={'name': 'all'},
+        config={},
         dump_path=object,
         database_names=('foo', 'bar'),
         extra_environment=object,
@@ -146,6 +149,7 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
     for name, process in zip(('foo', 'bar'), processes):
         flexmock(module).should_receive('execute_dump_command').with_args(
             database={'name': name, 'format': 'sql'},
+            config={},
             dump_path=object,
             database_names=(name,),
             extra_environment=object,
@@ -180,7 +184,7 @@ def test_database_names_to_dump_runs_mysql_with_list_options():
         extra_environment=None,
     ).and_return(('foo\nbar')).once()
 
-    assert module.database_names_to_dump(database, None, '') == ('foo', 'bar')
+    assert module.database_names_to_dump(database, {}, None, '') == ('foo', 'bar')
 
 
 def test_database_names_to_dump_runs_non_default_mysql_with_list_options():
@@ -201,7 +205,7 @@ def test_database_names_to_dump_runs_non_default_mysql_with_list_options():
         ),
     ).and_return(('foo\nbar')).once()
 
-    assert module.database_names_to_dump(database, None, '') == ('foo', 'bar')
+    assert module.database_names_to_dump(database, {}, None, '') == ('foo', 'bar')
 
 
 def test_execute_dump_command_runs_mysqldump():
@@ -210,7 +214,7 @@ def test_execute_dump_command_runs_mysqldump():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -229,6 +233,7 @@ def test_execute_dump_command_runs_mysqldump():
     assert (
         module.execute_dump_command(
             database={'name': 'foo'},
+            config={},
             dump_path=flexmock(),
             database_names=('foo',),
             extra_environment=None,
@@ -245,7 +250,7 @@ def test_execute_dump_command_runs_mysqldump_without_add_drop_database():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -263,6 +268,7 @@ def test_execute_dump_command_runs_mysqldump_without_add_drop_database():
     assert (
         module.execute_dump_command(
             database={'name': 'foo', 'add_drop_database': False},
+            config={},
             dump_path=flexmock(),
             database_names=('foo',),
             extra_environment=None,
@@ -279,7 +285,7 @@ def test_execute_dump_command_runs_mysqldump_with_hostname_and_port():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -304,6 +310,7 @@ def test_execute_dump_command_runs_mysqldump_with_hostname_and_port():
     assert (
         module.execute_dump_command(
             database={'name': 'foo', 'hostname': 'database.example.org', 'port': 5433},
+            config={},
             dump_path=flexmock(),
             database_names=('foo',),
             extra_environment=None,
@@ -320,7 +327,7 @@ def test_execute_dump_command_runs_mysqldump_with_username_and_password():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -341,6 +348,7 @@ def test_execute_dump_command_runs_mysqldump_with_username_and_password():
     assert (
         module.execute_dump_command(
             database={'name': 'foo', 'username': 'root', 'password': 'trustsome1'},
+            config={},
             dump_path=flexmock(),
             database_names=('foo',),
             extra_environment={'MYSQL_PWD': 'trustsome1'},
@@ -357,7 +365,7 @@ def test_execute_dump_command_runs_mysqldump_with_options():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -377,6 +385,7 @@ def test_execute_dump_command_runs_mysqldump_with_options():
     assert (
         module.execute_dump_command(
             database={'name': 'foo', 'options': '--stuff=such'},
+            config={},
             dump_path=flexmock(),
             database_names=('foo',),
             extra_environment=None,
@@ -393,7 +402,7 @@ def test_execute_dump_command_runs_non_default_mysqldump():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -415,6 +424,7 @@ def test_execute_dump_command_runs_non_default_mysqldump():
                 'name': 'foo',
                 'mysql_dump_command': 'custom_mysqldump',
             },  # Custom MySQL dump command specified
+            config={},
             dump_path=flexmock(),
             database_names=('foo',),
             extra_environment=None,
@@ -434,6 +444,7 @@ def test_execute_dump_command_with_duplicate_dump_skips_mysqldump():
     assert (
         module.execute_dump_command(
             database={'name': 'foo'},
+            config={},
             dump_path=flexmock(),
             database_names=('foo',),
             extra_environment=None,
@@ -449,7 +460,7 @@ def test_execute_dump_command_with_dry_run_skips_mysqldump():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').never()
@@ -457,6 +468,7 @@ def test_execute_dump_command_with_dry_run_skips_mysqldump():
     assert (
         module.execute_dump_command(
             database={'name': 'foo'},
+            config={},
             dump_path=flexmock(),
             database_names=('foo',),
             extra_environment=None,
@@ -472,7 +484,7 @@ def test_dump_data_sources_errors_for_missing_all_databases():
     flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
         'databases/localhost/all'
     )
@@ -494,7 +506,7 @@ def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run
     flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
         'databases/localhost/all'
     )
@@ -519,7 +531,7 @@ def test_restore_data_source_dump_runs_mysql_to_restore():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('mysql', '--batch'),
         processes=[extract_process],
@@ -550,7 +562,7 @@ def test_restore_data_source_dump_runs_mysql_with_options():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('mysql', '--batch', '--harder'),
         processes=[extract_process],
@@ -581,7 +593,7 @@ def test_restore_data_source_dump_runs_non_default_mysql_with_options():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('custom_mysql', '--batch', '--harder'),
         processes=[extract_process],
@@ -612,7 +624,7 @@ def test_restore_data_source_dump_runs_mysql_with_hostname_and_port():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
             'mysql',
@@ -652,7 +664,7 @@ def test_restore_data_source_dump_runs_mysql_with_username_and_password():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('mysql', '--batch', '--user', 'root'),
         processes=[extract_process],
@@ -693,7 +705,7 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
             'mysql',
@@ -747,7 +759,7 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
             'mysql',
@@ -788,7 +800,7 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_with_processes').never()
 
     module.restore_data_source_dump(

+ 44 - 44
tests/unit/hooks/data_source/test_postgresql.py

@@ -26,9 +26,9 @@ def test_make_extra_environment_maps_options_to_environment():
     }
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
 
-    extra_env = module.make_extra_environment(database)
+    extra_env = module.make_extra_environment(database, {})
 
     assert extra_env == expected
 
@@ -37,10 +37,10 @@ def test_make_extra_environment_with_cli_password_sets_correct_password():
     database = {'name': 'foo', 'restore_password': 'trustsome1', 'password': 'anotherpassword'}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
 
     extra = module.make_extra_environment(
-        database, restore_connection_params={'password': 'clipassword'}
+        database, {}, restore_connection_params={'password': 'clipassword'}
     )
 
     assert extra['PGPASSWORD'] == 'clipassword'
@@ -50,7 +50,7 @@ def test_make_extra_environment_without_cli_password_or_configured_password_does
     database = {'name': 'foo'}
 
     extra = module.make_extra_environment(
-        database, restore_connection_params={'username': 'someone'}
+        database, {}, restore_connection_params={'username': 'someone'}
     )
 
     assert 'PGPASSWORD' not in extra
@@ -59,7 +59,7 @@ def test_make_extra_environment_without_cli_password_or_configured_password_does
 def test_make_extra_environment_without_ssl_mode_does_not_set_ssl_mode():
     database = {'name': 'foo'}
 
-    extra = module.make_extra_environment(database)
+    extra = module.make_extra_environment(database, {})
 
     assert 'PGSSLMODE' not in extra
 
@@ -67,41 +67,41 @@ def test_make_extra_environment_without_ssl_mode_does_not_set_ssl_mode():
 def test_database_names_to_dump_passes_through_individual_database_name():
     database = {'name': 'foo'}
 
-    assert module.database_names_to_dump(database, flexmock(), dry_run=False) == ('foo',)
+    assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('foo',)
 
 
 def test_database_names_to_dump_passes_through_individual_database_name_with_format():
     database = {'name': 'foo', 'format': 'custom'}
 
-    assert module.database_names_to_dump(database, flexmock(), dry_run=False) == ('foo',)
+    assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('foo',)
 
 
 def test_database_names_to_dump_passes_through_all_without_format():
     database = {'name': 'all'}
 
-    assert module.database_names_to_dump(database, flexmock(), dry_run=False) == ('all',)
+    assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('all',)
 
 
 def test_database_names_to_dump_with_all_and_format_and_dry_run_bails():
     database = {'name': 'all', 'format': 'custom'}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_and_capture_output').never()
 
-    assert module.database_names_to_dump(database, flexmock(), dry_run=True) == ()
+    assert module.database_names_to_dump(database, {}, flexmock(), dry_run=True) == ()
 
 
 def test_database_names_to_dump_with_all_and_format_lists_databases():
     database = {'name': 'all', 'format': 'custom'}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_and_capture_output').and_return(
         'foo,test,\nbar,test,"stuff and such"'
     )
 
-    assert module.database_names_to_dump(database, flexmock(), dry_run=False) == (
+    assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == (
         'foo',
         'bar',
     )
@@ -111,7 +111,7 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_hostnam
     database = {'name': 'all', 'format': 'custom', 'hostname': 'localhost', 'port': 1234}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_and_capture_output').with_args(
         (
             'psql',
@@ -128,7 +128,7 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_hostnam
         extra_environment=object,
     ).and_return('foo,test,\nbar,test,"stuff and such"')
 
-    assert module.database_names_to_dump(database, flexmock(), dry_run=False) == (
+    assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == (
         'foo',
         'bar',
     )
@@ -138,7 +138,7 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_usernam
     database = {'name': 'all', 'format': 'custom', 'username': 'postgres'}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_and_capture_output').with_args(
         (
             'psql',
@@ -153,7 +153,7 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_usernam
         extra_environment=object,
     ).and_return('foo,test,\nbar,test,"stuff and such"')
 
-    assert module.database_names_to_dump(database, flexmock(), dry_run=False) == (
+    assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == (
         'foo',
         'bar',
     )
@@ -163,13 +163,13 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_options
     database = {'name': 'all', 'format': 'custom', 'list_options': '--harder'}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_and_capture_output').with_args(
         ('psql', '--list', '--no-password', '--no-psqlrc', '--csv', '--tuples-only', '--harder'),
         extra_environment=object,
     ).and_return('foo,test,\nbar,test,"stuff and such"')
 
-    assert module.database_names_to_dump(database, flexmock(), dry_run=False) == (
+    assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == (
         'foo',
         'bar',
     )
@@ -179,12 +179,12 @@ def test_database_names_to_dump_with_all_and_format_excludes_particular_database
     database = {'name': 'all', 'format': 'custom'}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_and_capture_output').and_return(
         'foo,test,\ntemplate0,test,blah'
     )
 
-    assert module.database_names_to_dump(database, flexmock(), dry_run=False) == ('foo',)
+    assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('foo',)
 
 
 def test_database_names_to_dump_with_all_and_psql_command_uses_custom_command():
@@ -195,7 +195,7 @@ def test_database_names_to_dump_with_all_and_psql_command_uses_custom_command():
     }
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('execute_command_and_capture_output').with_args(
         (
             'docker',
@@ -213,7 +213,7 @@ def test_database_names_to_dump_with_all_and_psql_command_uses_custom_command():
         extra_environment=object,
     ).and_return('foo,text').once()
 
-    assert module.database_names_to_dump(database, flexmock(), dry_run=False) == ('foo',)
+    assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('foo',)
 
 
 def test_use_streaming_true_for_any_non_directory_format_databases():
@@ -248,7 +248,7 @@ def test_dump_data_sources_runs_pg_dump_for_each_database():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     for name, process in zip(('foo', 'bar'), processes):
@@ -355,7 +355,7 @@ def test_dump_data_sources_with_dry_run_skips_pg_dump():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
     flexmock(module).should_receive('execute_command').never()
 
@@ -384,7 +384,7 @@ def test_dump_data_sources_runs_pg_dump_with_hostname_and_port():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -432,7 +432,7 @@ def test_dump_data_sources_runs_pg_dump_with_username_and_password():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -478,7 +478,7 @@ def test_dump_data_sources_with_username_injection_attack_gets_escaped():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -521,7 +521,7 @@ def test_dump_data_sources_runs_pg_dump_with_directory_format():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_parent_directory_for_dump')
     flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
 
@@ -566,7 +566,7 @@ def test_dump_data_sources_runs_pg_dump_with_options():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -609,7 +609,7 @@ def test_dump_data_sources_runs_pg_dumpall_for_all_databases():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -641,7 +641,7 @@ def test_dump_data_sources_runs_non_default_pg_dump():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -679,7 +679,7 @@ def test_restore_data_source_dump_runs_pg_restore():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
@@ -736,7 +736,7 @@ def test_restore_data_source_dump_runs_pg_restore_with_hostname_and_port():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
@@ -801,7 +801,7 @@ def test_restore_data_source_dump_runs_pg_restore_with_username_and_password():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('make_extra_environment').and_return(
         {'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'}
     )
@@ -875,7 +875,7 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('make_extra_environment').and_return(
         {'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'}
     )
@@ -957,7 +957,7 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('make_extra_environment').and_return(
         {'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'}
     )
@@ -1033,7 +1033,7 @@ def test_restore_data_source_dump_runs_pg_restore_with_options():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
@@ -1090,7 +1090,7 @@ def test_restore_data_source_dump_runs_psql_for_all_database_dump():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
@@ -1132,7 +1132,7 @@ def test_restore_data_source_dump_runs_psql_for_plain_database_dump():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
@@ -1186,7 +1186,7 @@ def test_restore_data_source_dump_runs_non_default_pg_restore_and_psql():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
@@ -1250,7 +1250,7 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
@@ -1277,7 +1277,7 @@ def test_restore_data_source_dump_without_extract_process_restores_from_disk():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
@@ -1332,7 +1332,7 @@ def test_restore_data_source_dump_with_schemas_restores_schemas():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')

+ 14 - 14
tests/unit/hooks/monitoring/test_ntfy.py

@@ -38,7 +38,7 @@ def test_ping_monitor_minimal_config_hits_hosted_ntfy_on_fail():
     hook_config = {'topic': topic}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
@@ -62,7 +62,7 @@ def test_ping_monitor_with_access_token_hits_hosted_ntfy_on_fail():
     }
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
@@ -88,7 +88,7 @@ def test_ping_monitor_with_username_password_and_access_token_ignores_username_p
     }
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
@@ -114,7 +114,7 @@ def test_ping_monitor_with_username_password_hits_hosted_ntfy_on_fail():
     }
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
@@ -135,7 +135,7 @@ def test_ping_monitor_with_password_but_no_username_warns():
     hook_config = {'topic': topic, 'password': 'fakepassword'}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
@@ -157,7 +157,7 @@ def test_ping_monitor_with_username_but_no_password_warns():
     hook_config = {'topic': topic, 'username': 'testuser'}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
@@ -179,7 +179,7 @@ def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_start():
     hook_config = {'topic': topic}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').never()
 
     module.ping_monitor(
@@ -196,7 +196,7 @@ def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_finish():
     hook_config = {'topic': topic}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').never()
 
     module.ping_monitor(
@@ -213,7 +213,7 @@ def test_ping_monitor_minimal_config_hits_selfhosted_ntfy_on_fail():
     hook_config = {'topic': topic, 'server': custom_base_url}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{custom_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
@@ -234,7 +234,7 @@ def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_fail_dry_run():
     hook_config = {'topic': topic}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').never()
 
     module.ping_monitor(
@@ -251,7 +251,7 @@ def test_ping_monitor_custom_message_hits_hosted_ntfy_on_fail():
     hook_config = {'topic': topic, 'fail': custom_message_config}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}', headers=custom_message_headers, auth=None
     ).and_return(flexmock(ok=True)).once()
@@ -270,7 +270,7 @@ def test_ping_monitor_custom_state_hits_hosted_ntfy_on_start():
     hook_config = {'topic': topic, 'states': ['start', 'fail']}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.START),
@@ -291,7 +291,7 @@ def test_ping_monitor_with_connection_error_logs_warning():
     hook_config = {'topic': topic}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
@@ -331,7 +331,7 @@ def test_ping_monitor_with_other_error_logs_warning():
     hook_config = {'topic': topic}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     response = flexmock(ok=False)
     response.should_receive('raise_for_status').and_raise(
         module.requests.exceptions.RequestException

+ 6 - 6
tests/unit/hooks/monitoring/test_pagerduty.py

@@ -6,7 +6,7 @@ from borgmatic.hooks.monitoring import pagerduty as module
 def test_ping_monitor_ignores_start_state():
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').never()
 
     module.ping_monitor(
@@ -22,7 +22,7 @@ def test_ping_monitor_ignores_start_state():
 def test_ping_monitor_ignores_finish_state():
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').never()
 
     module.ping_monitor(
@@ -38,7 +38,7 @@ def test_ping_monitor_ignores_finish_state():
 def test_ping_monitor_calls_api_for_fail_state():
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').and_return(flexmock(ok=True))
 
     module.ping_monitor(
@@ -54,7 +54,7 @@ def test_ping_monitor_calls_api_for_fail_state():
 def test_ping_monitor_dry_run_does_not_call_api():
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').never()
 
     module.ping_monitor(
@@ -70,7 +70,7 @@ def test_ping_monitor_dry_run_does_not_call_api():
 def test_ping_monitor_with_connection_error_logs_warning():
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').and_raise(
         module.requests.exceptions.ConnectionError
     )
@@ -107,7 +107,7 @@ def test_ping_monitor_with_other_error_logs_warning():
     response = flexmock(ok=False)
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     response.should_receive('raise_for_status').and_raise(
         module.requests.exceptions.RequestException
     )

+ 14 - 14
tests/unit/hooks/monitoring/test_pushover.py

@@ -13,7 +13,7 @@ def test_ping_monitor_config_with_minimum_config_fail_state_backup_successfully_
     hook_config = {'token': 'ksdjfwoweijfvwoeifvjmwghagy92', 'user': '983hfe0of902lkjfa2amanfgui'}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
@@ -43,7 +43,7 @@ def test_ping_monitor_config_with_minimum_config_start_state_backup_not_send_to_
     hook_config = {'token': 'ksdjfwoweijfvwoeifvjmwghagy92', 'user': '983hfe0of902lkjfa2amanfgui'}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').never()
 
@@ -71,7 +71,7 @@ def test_ping_monitor_start_state_backup_default_message_successfully_send_to_pu
     }
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
@@ -107,7 +107,7 @@ def test_ping_monitor_start_state_backup_custom_message_successfully_send_to_pus
     }
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
@@ -142,7 +142,7 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_emergency
     }
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
@@ -180,7 +180,7 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_emergency
     }
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
@@ -218,7 +218,7 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_emergency
     }
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
@@ -259,7 +259,7 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_high_decl
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').never()
 
@@ -314,7 +314,7 @@ def test_ping_monitor_start_state_backup_based_on_documentation_advanced_example
     }
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
@@ -380,7 +380,7 @@ def test_ping_monitor_fail_state_backup_based_on_documentation_advanced_example_
     }
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
@@ -451,7 +451,7 @@ def test_ping_monitor_finish_state_backup_based_on_documentation_advanced_exampl
     }
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
@@ -487,7 +487,7 @@ def test_ping_monitor_config_with_minimum_config_fail_state_backup_successfully_
     hook_config = {'token': 'ksdjfwoweijfvwoeifvjmwghagy92', 'user': '983hfe0of902lkjfa2amanfgui'}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').and_return(flexmock(ok=True)).never()
 
@@ -511,7 +511,7 @@ def test_ping_monitor_config_incorrect_state_exit_early():
     }
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').and_return(flexmock(ok=True)).never()
 
@@ -537,7 +537,7 @@ def test_ping_monitor_push_post_error_bails():
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     push_response = flexmock(ok=False)
     push_response.should_receive('raise_for_status').and_raise(
         module.requests.ConnectionError

+ 18 - 18
tests/unit/hooks/monitoring/test_zabbix.py

@@ -77,7 +77,7 @@ def test_ping_monitor_config_with_api_key_only_bails():
     hook_config = {'api_key': API_KEY}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -97,7 +97,7 @@ def test_ping_monitor_config_with_host_only_bails():
     hook_config = {'host': HOST}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -117,7 +117,7 @@ def test_ping_monitor_config_with_key_only_bails():
     hook_config = {'key': KEY}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -137,7 +137,7 @@ def test_ping_monitor_config_with_server_only_bails():
     hook_config = {'server': SERVER}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -156,7 +156,7 @@ def test_ping_monitor_config_user_password_no_zabbix_data_bails():
     hook_config = {'server': SERVER, 'username': USERNAME, 'password': PASSWORD}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -175,7 +175,7 @@ def test_ping_monitor_config_api_key_no_zabbix_data_bails():
     hook_config = {'server': SERVER, 'api_key': API_KEY}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -195,7 +195,7 @@ def test_ping_monitor_config_itemid_no_auth_data_bails():
     hook_config = {'server': SERVER, 'itemid': ITEMID}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -215,7 +215,7 @@ def test_ping_monitor_config_host_and_key_no_auth_data_bails():
     hook_config = {'server': SERVER, 'host': HOST, 'key': KEY}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -235,7 +235,7 @@ def test_ping_monitor_config_host_and_key_with_api_key_auth_data_successful():
     hook_config = {'server': SERVER, 'host': HOST, 'key': KEY, 'api_key': API_KEY}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{SERVER}',
         headers=AUTH_HEADERS_API_KEY,
@@ -257,7 +257,7 @@ def test_ping_monitor_config_host_and_missing_key_bails():
     hook_config = {'server': SERVER, 'host': HOST, 'api_key': API_KEY}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -275,7 +275,7 @@ def test_ping_monitor_config_key_and_missing_host_bails():
     hook_config = {'server': SERVER, 'key': KEY, 'api_key': API_KEY}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -302,7 +302,7 @@ def test_ping_monitor_config_host_and_key_with_username_password_auth_data_succe
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     auth_response = flexmock(ok=True)
     auth_response.should_receive('json').and_return(
         {'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}
@@ -343,7 +343,7 @@ def test_ping_monitor_config_host_and_key_with_username_password_auth_data_and_a
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     auth_response = flexmock(ok=False)
     auth_response.should_receive('json').and_return(
         {'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}
@@ -384,7 +384,7 @@ def test_ping_monitor_config_host_and_key_with_username_and_missing_password_bai
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -408,7 +408,7 @@ def test_ping_monitor_config_host_and_key_with_password_and_missing_username_bai
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -428,7 +428,7 @@ def test_ping_monitor_config_itemid_with_api_key_auth_data_successful():
     hook_config = {'server': SERVER, 'itemid': ITEMID, 'api_key': API_KEY}
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{SERVER}',
         headers=AUTH_HEADERS_API_KEY,
@@ -453,7 +453,7 @@ def test_ping_monitor_config_itemid_with_username_password_auth_data_successful(
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     auth_response = flexmock(ok=True)
     auth_response.should_receive('json').and_return(
         {'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}
@@ -488,7 +488,7 @@ def test_ping_monitor_config_itemid_with_username_password_auth_data_and_push_po
 
     flexmock(module.borgmatic.hooks.credential.parse).should_receive(
         'resolve_credential'
-    ).replace_with(lambda value: value)
+    ).replace_with(lambda value, config: value)
     auth_response = flexmock(ok=True)
     auth_response.should_receive('json').and_return(
         {'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}