Browse Source

Optionally change the internal database dump path via "borgmatic_source_directory" option in location configuration section (#259).

Dan Helfman 5 years ago
parent
commit
8660af745e

+ 4 - 0
NEWS

@@ -1,3 +1,7 @@
+1.4.19
+ * #259: Optionally change the internal database dump path via "borgmatic_source_directory" option
+   in location configuration section.
+
 1.4.18
 1.4.18
  * Fix "--repository" flag to accept relative paths.
  * Fix "--repository" flag to accept relative paths.
  * Fix "borgmatic umount" so it only runs Borg once instead of once per repository / configuration
  * Fix "borgmatic umount" so it only runs Borg once instead of once per repository / configuration

+ 9 - 5
borgmatic/borg/create.py

@@ -104,16 +104,19 @@ def _make_exclude_flags(location_config, exclude_filename=None):
     )
     )
 
 
 
 
-BORGMATIC_SOURCE_DIRECTORY = '~/.borgmatic'
+DEFAULT_BORGMATIC_SOURCE_DIRECTORY = '~/.borgmatic'
 
 
 
 
-def borgmatic_source_directories():
+def borgmatic_source_directories(borgmatic_source_directory):
     '''
     '''
     Return a list of borgmatic-specific source directories used for state like database backups.
     Return a list of borgmatic-specific source directories used for state like database backups.
     '''
     '''
+    if not borgmatic_source_directory:
+        borgmatic_source_directory = DEFAULT_BORGMATIC_SOURCE_DIRECTORY
+
     return (
     return (
-        [BORGMATIC_SOURCE_DIRECTORY]
-        if os.path.exists(os.path.expanduser(BORGMATIC_SOURCE_DIRECTORY))
+        [borgmatic_source_directory]
+        if os.path.exists(os.path.expanduser(borgmatic_source_directory))
         else []
         else []
     )
     )
 
 
@@ -134,7 +137,8 @@ def create_archive(
     storage config dict, create a Borg archive and return Borg's JSON output (if any).
     storage config dict, create a Borg archive and return Borg's JSON output (if any).
     '''
     '''
     sources = _expand_directories(
     sources = _expand_directories(
-        location_config['source_directories'] + borgmatic_source_directories()
+        location_config['source_directories']
+        + borgmatic_source_directories(location_config.get('borgmatic_source_directory'))
     )
     )
 
 
     pattern_file = _write_pattern_file(location_config.get('patterns'))
     pattern_file = _write_pattern_file(location_config.get('patterns'))

+ 5 - 0
borgmatic/commands/borgmatic.py

@@ -75,6 +75,7 @@ def run_configuration(config_filename, config, arguments):
                 hooks,
                 hooks,
                 config_filename,
                 config_filename,
                 dump.DATABASE_HOOK_NAMES,
                 dump.DATABASE_HOOK_NAMES,
+                location,
                 global_arguments.dry_run,
                 global_arguments.dry_run,
             )
             )
         except (OSError, CalledProcessError) as error:
         except (OSError, CalledProcessError) as error:
@@ -111,6 +112,7 @@ def run_configuration(config_filename, config, arguments):
                 hooks,
                 hooks,
                 config_filename,
                 config_filename,
                 dump.DATABASE_HOOK_NAMES,
                 dump.DATABASE_HOOK_NAMES,
+                location,
                 global_arguments.dry_run,
                 global_arguments.dry_run,
             )
             )
             command.execute_hook(
             command.execute_hook(
@@ -294,6 +296,7 @@ def run_actions(
                 hooks,
                 hooks,
                 repository,
                 repository,
                 dump.DATABASE_HOOK_NAMES,
                 dump.DATABASE_HOOK_NAMES,
+                location,
                 restore_names,
                 restore_names,
             )
             )
 
 
@@ -325,6 +328,7 @@ def run_actions(
                 restore_databases,
                 restore_databases,
                 repository,
                 repository,
                 dump.DATABASE_HOOK_NAMES,
                 dump.DATABASE_HOOK_NAMES,
+                location,
                 global_arguments.dry_run,
                 global_arguments.dry_run,
             )
             )
             dispatch.call_hooks(
             dispatch.call_hooks(
@@ -332,6 +336,7 @@ def run_actions(
                 restore_databases,
                 restore_databases,
                 repository,
                 repository,
                 dump.DATABASE_HOOK_NAMES,
                 dump.DATABASE_HOOK_NAMES,
+                location,
                 global_arguments.dry_run,
                 global_arguments.dry_run,
             )
             )
     if 'list' in arguments:
     if 'list' in arguments:

+ 8 - 0
borgmatic/config/schema.yaml

@@ -137,6 +137,14 @@ map:
                 desc: |
                 desc: |
                     Exclude files with the NODUMP flag. Defaults to false.
                     Exclude files with the NODUMP flag. Defaults to false.
                 example: true
                 example: true
+            borgmatic_source_directory:
+                type: str
+                desc: |
+                    Path for additional source files used for temporary internal state like
+                    borgmatic database dumps. Note that changing this path prevents "borgmatic
+                    restore" from finding any database dumps created before the change. Defaults
+                    to ~/.borgmatic
+                example: /tmp/borgmatic
     storage:
     storage:
         desc: |
         desc: |
             Repository storage options. See
             Repository storage options. See

+ 13 - 0
borgmatic/hooks/dump.py

@@ -2,11 +2,24 @@ import glob
 import logging
 import logging
 import os
 import os
 
 
+from borgmatic.borg.create import DEFAULT_BORGMATIC_SOURCE_DIRECTORY
+
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 DATABASE_HOOK_NAMES = ('postgresql_databases', 'mysql_databases')
 DATABASE_HOOK_NAMES = ('postgresql_databases', 'mysql_databases')
 
 
 
 
+def make_database_dump_path(borgmatic_source_directory, database_hook_name):
+    '''
+    Given a borgmatic source directory (or None) and a database hook name, construct a database dump
+    path.
+    '''
+    if not borgmatic_source_directory:
+        borgmatic_source_directory = DEFAULT_BORGMATIC_SOURCE_DIRECTORY
+
+    return os.path.join(borgmatic_source_directory, database_hook_name)
+
+
 def make_database_dump_filename(dump_path, name, hostname=None):
 def make_database_dump_filename(dump_path, name, hostname=None):
     '''
     '''
     Based on the given dump directory path, database name, and hostname, return a filename to use
     Based on the given dump directory path, database name, and hostname, return a filename to use

+ 32 - 15
borgmatic/hooks/mysql.py

@@ -4,15 +4,24 @@ import os
 from borgmatic.execute import execute_command
 from borgmatic.execute import execute_command
 from borgmatic.hooks import dump
 from borgmatic.hooks import dump
 
 
-DUMP_PATH = '~/.borgmatic/mysql_databases'
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 
 
-def dump_databases(databases, log_prefix, dry_run):
+def make_dump_path(location_config):  # pragma: no cover
+    '''
+    Make the dump path from the given location configuration and the name of this hook.
+    '''
+    return dump.make_database_dump_path(
+        location_config.get('borgmatic_source_directory'), 'mysql_databases'
+    )
+
+
+def dump_databases(databases, log_prefix, location_config, dry_run):
     '''
     '''
     Dump the given MySQL/MariaDB databases to disk. The databases are supplied as a sequence of
     Dump the given MySQL/MariaDB databases to disk. The databases are supplied as a sequence of
     dicts, one dict describing each database as per the configuration schema. Use the given log
     dicts, one dict describing each database as per the configuration schema. Use the given log
-    prefix in any log entries. If this is a dry run, then don't actually dump anything.
+    prefix in any log entries. Use the given location configuration dict to construct the
+    destination path. If this is a dry run, then don't actually dump anything.
     '''
     '''
     dry_run_label = ' (dry run; not actually dumping anything)' if dry_run else ''
     dry_run_label = ' (dry run; not actually dumping anything)' if dry_run else ''
 
 
@@ -20,7 +29,9 @@ def dump_databases(databases, log_prefix, dry_run):
 
 
     for database in databases:
     for database in databases:
         name = database['name']
         name = database['name']
-        dump_filename = dump.make_database_dump_filename(DUMP_PATH, name, database.get('hostname'))
+        dump_filename = dump.make_database_dump_filename(
+            make_dump_path(location_config), name, database.get('hostname')
+        )
         command = (
         command = (
             ('mysqldump', '--add-drop-database')
             ('mysqldump', '--add-drop-database')
             + (('--host', database['hostname']) if 'hostname' in database else ())
             + (('--host', database['hostname']) if 'hostname' in database else ())
@@ -44,37 +55,43 @@ def dump_databases(databases, log_prefix, dry_run):
             )
             )
 
 
 
 
-def remove_database_dumps(databases, log_prefix, dry_run):  # pragma: no cover
+def remove_database_dumps(databases, log_prefix, location_config, dry_run):  # pragma: no cover
     '''
     '''
     Remove the database dumps for the given databases. The databases are supplied as a sequence of
     Remove the database dumps for the given databases. The databases are supplied as a sequence of
     dicts, one dict describing each database as per the configuration schema. Use the log prefix in
     dicts, one dict describing each database as per the configuration schema. Use the log prefix in
-    any log entries. If this is a dry run, then don't actually remove anything.
+    any log entries. Use the given location configuration dict to construct the destination path. If
+    this is a dry run, then don't actually remove anything.
     '''
     '''
-    dump.remove_database_dumps(DUMP_PATH, databases, 'MySQL', log_prefix, dry_run)
+    dump.remove_database_dumps(
+        make_dump_path(location_config), databases, 'MySQL', log_prefix, dry_run
+    )
 
 
 
 
-def make_database_dump_patterns(databases, log_prefix, names):
+def make_database_dump_patterns(databases, log_prefix, location_config, names):
     '''
     '''
-    Given a sequence of configurations dicts, a prefix to log with, and a sequence of database
-    names to match, return the corresponding glob patterns to match the database dumps in an
-    archive. An empty sequence of names indicates that the patterns should match all dumps.
+    Given a sequence of configurations dicts, a prefix to log with, a location configuration dict,
+    and a sequence of database names to match, return the corresponding glob patterns to match the
+    database dumps in an archive. An empty sequence of names indicates that the patterns should
+    match all dumps.
     '''
     '''
     return [
     return [
-        dump.make_database_dump_filename(DUMP_PATH, name, hostname='*') for name in (names or ['*'])
+        dump.make_database_dump_filename(make_dump_path(location_config), name, hostname='*')
+        for name in (names or ['*'])
     ]
     ]
 
 
 
 
-def restore_database_dumps(databases, log_prefix, dry_run):
+def restore_database_dumps(databases, log_prefix, location_config, dry_run):
     '''
     '''
     Restore the given MySQL/MariaDB databases from disk. The databases are supplied as a sequence of
     Restore the given MySQL/MariaDB databases from disk. The databases are supplied as a sequence of
     dicts, one dict describing each database as per the configuration schema. Use the given log
     dicts, one dict describing each database as per the configuration schema. Use the given log
-    prefix in any log entries. If this is a dry run, then don't actually restore anything.
+    prefix in any log entries. Use the given location configuration dict to construct the
+    destination path. If this is a dry run, then don't actually restore anything.
     '''
     '''
     dry_run_label = ' (dry run; not actually restoring anything)' if dry_run else ''
     dry_run_label = ' (dry run; not actually restoring anything)' if dry_run else ''
 
 
     for database in databases:
     for database in databases:
         dump_filename = dump.make_database_dump_filename(
         dump_filename = dump.make_database_dump_filename(
-            DUMP_PATH, database['name'], database.get('hostname')
+            make_dump_path(location_config), database['name'], database.get('hostname')
         )
         )
         restore_command = (
         restore_command = (
             ('mysql', '--batch')
             ('mysql', '--batch')

+ 32 - 15
borgmatic/hooks/postgresql.py

@@ -4,15 +4,24 @@ import os
 from borgmatic.execute import execute_command
 from borgmatic.execute import execute_command
 from borgmatic.hooks import dump
 from borgmatic.hooks import dump
 
 
-DUMP_PATH = '~/.borgmatic/postgresql_databases'
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 
 
-def dump_databases(databases, log_prefix, dry_run):
+def make_dump_path(location_config):  # pragma: no cover
+    '''
+    Make the dump path from the given location configuration and the name of this hook.
+    '''
+    return dump.make_database_dump_path(
+        location_config.get('borgmatic_source_directory'), 'postgresql_databases'
+    )
+
+
+def dump_databases(databases, log_prefix, location_config, dry_run):
     '''
     '''
     Dump the given PostgreSQL databases to disk. The databases are supplied as a sequence of dicts,
     Dump the given PostgreSQL databases to disk. The databases are supplied as a sequence of dicts,
     one dict describing each database as per the configuration schema. Use the given log prefix in
     one dict describing each database as per the configuration schema. Use the given log prefix in
-    any log entries. If this is a dry run, then don't actually dump anything.
+    any log entries. Use the given location configuration dict to construct the destination path. If
+    this is a dry run, then don't actually dump anything.
     '''
     '''
     dry_run_label = ' (dry run; not actually dumping anything)' if dry_run else ''
     dry_run_label = ' (dry run; not actually dumping anything)' if dry_run else ''
 
 
@@ -20,7 +29,9 @@ def dump_databases(databases, log_prefix, dry_run):
 
 
     for database in databases:
     for database in databases:
         name = database['name']
         name = database['name']
-        dump_filename = dump.make_database_dump_filename(DUMP_PATH, name, database.get('hostname'))
+        dump_filename = dump.make_database_dump_filename(
+            make_dump_path(location_config), name, database.get('hostname')
+        )
         all_databases = bool(name == 'all')
         all_databases = bool(name == 'all')
         command = (
         command = (
             ('pg_dumpall' if all_databases else 'pg_dump', '--no-password', '--clean')
             ('pg_dumpall' if all_databases else 'pg_dump', '--no-password', '--clean')
@@ -44,37 +55,43 @@ def dump_databases(databases, log_prefix, dry_run):
             execute_command(command, extra_environment=extra_environment)
             execute_command(command, extra_environment=extra_environment)
 
 
 
 
-def remove_database_dumps(databases, log_prefix, dry_run):  # pragma: no cover
+def remove_database_dumps(databases, log_prefix, location_config, dry_run):  # pragma: no cover
     '''
     '''
     Remove the database dumps for the given databases. The databases are supplied as a sequence of
     Remove the database dumps for the given databases. The databases are supplied as a sequence of
     dicts, one dict describing each database as per the configuration schema. Use the log prefix in
     dicts, one dict describing each database as per the configuration schema. Use the log prefix in
-    any log entries. If this is a dry run, then don't actually remove anything.
+    any log entries. Use the given location configuration dict to construct the destination path. If
+    this is a dry run, then don't actually remove anything.
     '''
     '''
-    dump.remove_database_dumps(DUMP_PATH, databases, 'PostgreSQL', log_prefix, dry_run)
+    dump.remove_database_dumps(
+        make_dump_path(location_config), databases, 'PostgreSQL', log_prefix, dry_run
+    )
 
 
 
 
-def make_database_dump_patterns(databases, log_prefix, names):
+def make_database_dump_patterns(databases, log_prefix, location_config, names):
     '''
     '''
-    Given a sequence of configurations dicts, a prefix to log with, and a sequence of database
-    names to match, return the corresponding glob patterns to match the database dumps in an
-    archive. An empty sequence of names indicates that the patterns should match all dumps.
+    Given a sequence of configurations dicts, a prefix to log with, a location configuration dict,
+    and a sequence of database names to match, return the corresponding glob patterns to match the
+    database dumps in an archive. An empty sequence of names indicates that the patterns should
+    match all dumps.
     '''
     '''
     return [
     return [
-        dump.make_database_dump_filename(DUMP_PATH, name, hostname='*') for name in (names or ['*'])
+        dump.make_database_dump_filename(make_dump_path(location_config), name, hostname='*')
+        for name in (names or ['*'])
     ]
     ]
 
 
 
 
-def restore_database_dumps(databases, log_prefix, dry_run):
+def restore_database_dumps(databases, log_prefix, location_config, dry_run):
     '''
     '''
     Restore the given PostgreSQL databases from disk. The databases are supplied as a sequence of
     Restore the given PostgreSQL databases from disk. The databases are supplied as a sequence of
     dicts, one dict describing each database as per the configuration schema. Use the given log
     dicts, one dict describing each database as per the configuration schema. Use the given log
-    prefix in any log entries. If this is a dry run, then don't actually restore anything.
+    prefix in any log entries. Use the given location configuration dict to construct the
+    destination path. If this is a dry run, then don't actually restore anything.
     '''
     '''
     dry_run_label = ' (dry run; not actually restoring anything)' if dry_run else ''
     dry_run_label = ' (dry run; not actually restoring anything)' if dry_run else ''
 
 
     for database in databases:
     for database in databases:
         dump_filename = dump.make_database_dump_filename(
         dump_filename = dump.make_database_dump_filename(
-            DUMP_PATH, database['name'], database.get('hostname')
+            make_dump_path(location_config), database['name'], database.get('hostname')
         )
         )
         restore_command = (
         restore_command = (
             ('pg_restore', '--no-password', '--clean', '--if-exists', '--exit-on-error')
             ('pg_restore', '--no-password', '--clean', '--if-exists', '--exit-on-error')

+ 6 - 2
docs/how-to/backup-your-databases.md

@@ -23,8 +23,12 @@ hooks:
 ```
 ```
 
 
 Prior to each backup, borgmatic dumps each configured database to a file
 Prior to each backup, borgmatic dumps each configured database to a file
-(located in `~/.borgmatic/`) and includes it in the backup. After the backup
-completes, borgmatic removes the database dump files to recover disk space.
+and includes it in the backup. After the backup completes, borgmatic removes
+the database dump files to recover disk space.
+
+borgmatic creates these temporary dump files in `~/.borgmatic` by default. To
+customize this path, set the `borgmatic_source_directory` option in the
+`location` section of borgmatic's configuration.
 
 
 Here's a more involved example that connects to remote databases:
 Here's a more involved example that connects to remote databases:
 
 

+ 1 - 1
setup.py

@@ -1,6 +1,6 @@
 from setuptools import find_packages, setup
 from setuptools import find_packages, setup
 
 
-VERSION = '1.4.18'
+VERSION = '1.4.19'
 
 
 
 
 setup(
 setup(

+ 9 - 2
tests/unit/borg/test_create.py

@@ -184,14 +184,21 @@ def test_borgmatic_source_directories_set_when_directory_exists():
     flexmock(module.os.path).should_receive('exists').and_return(True)
     flexmock(module.os.path).should_receive('exists').and_return(True)
     flexmock(module.os.path).should_receive('expanduser')
     flexmock(module.os.path).should_receive('expanduser')
 
 
-    assert module.borgmatic_source_directories() == [module.BORGMATIC_SOURCE_DIRECTORY]
+    assert module.borgmatic_source_directories('/tmp') == ['/tmp']
 
 
 
 
 def test_borgmatic_source_directories_empty_when_directory_does_not_exist():
 def test_borgmatic_source_directories_empty_when_directory_does_not_exist():
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.os.path).should_receive('expanduser')
     flexmock(module.os.path).should_receive('expanduser')
 
 
-    assert module.borgmatic_source_directories() == []
+    assert module.borgmatic_source_directories('/tmp') == []
+
+
+def test_borgmatic_source_directories_defaults_when_directory_not_given():
+    flexmock(module.os.path).should_receive('exists').and_return(True)
+    flexmock(module.os.path).should_receive('expanduser')
+
+    assert module.borgmatic_source_directories(None) == [module.DEFAULT_BORGMATIC_SOURCE_DIRECTORY]
 
 
 
 
 DEFAULT_ARCHIVE_NAME = '{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}'
 DEFAULT_ARCHIVE_NAME = '{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}'

+ 8 - 0
tests/unit/hooks/test_dump.py

@@ -4,6 +4,14 @@ from flexmock import flexmock
 from borgmatic.hooks import dump as module
 from borgmatic.hooks import dump as module
 
 
 
 
+def test_make_database_dump_path_joins_arguments():
+    assert module.make_database_dump_path('/tmp', 'super_databases') == '/tmp/super_databases'
+
+
+def test_make_database_dump_path_defaults_without_source_directory():
+    assert module.make_database_dump_path(None, 'super_databases') == '~/.borgmatic/super_databases'
+
+
 def test_make_database_dump_filename_uses_name_and_hostname():
 def test_make_database_dump_filename_uses_name_and_hostname():
     flexmock(module.os.path).should_receive('expanduser').and_return('databases')
     flexmock(module.os.path).should_receive('expanduser').and_return('databases')
 
 

+ 23 - 12
tests/unit/hooks/test_mysql.py

@@ -8,6 +8,7 @@ from borgmatic.hooks import mysql as module
 def test_dump_databases_runs_mysqldump_for_each_database():
 def test_dump_databases_runs_mysqldump_for_each_database():
     databases = [{'name': 'foo'}, {'name': 'bar'}]
     databases = [{'name': 'foo'}, {'name': 'bar'}]
     output_file = flexmock()
     output_file = flexmock()
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/localhost/foo'
         'databases/localhost/foo'
     ).and_return('databases/localhost/bar')
     ).and_return('databases/localhost/bar')
@@ -21,23 +22,25 @@ def test_dump_databases_runs_mysqldump_for_each_database():
             extra_environment=None,
             extra_environment=None,
         ).once()
         ).once()
 
 
-    module.dump_databases(databases, 'test.yaml', dry_run=False)
+    module.dump_databases(databases, 'test.yaml', {}, dry_run=False)
 
 
 
 
 def test_dump_databases_with_dry_run_skips_mysqldump():
 def test_dump_databases_with_dry_run_skips_mysqldump():
     databases = [{'name': 'foo'}, {'name': 'bar'}]
     databases = [{'name': 'foo'}, {'name': 'bar'}]
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/localhost/foo'
         'databases/localhost/foo'
     ).and_return('databases/localhost/bar')
     ).and_return('databases/localhost/bar')
     flexmock(module.os).should_receive('makedirs').never()
     flexmock(module.os).should_receive('makedirs').never()
     flexmock(module).should_receive('execute_command').never()
     flexmock(module).should_receive('execute_command').never()
 
 
-    module.dump_databases(databases, 'test.yaml', dry_run=True)
+    module.dump_databases(databases, 'test.yaml', {}, dry_run=True)
 
 
 
 
 def test_dump_databases_runs_mysqldump_with_hostname_and_port():
 def test_dump_databases_runs_mysqldump_with_hostname_and_port():
     databases = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
     databases = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
     output_file = flexmock()
     output_file = flexmock()
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/database.example.org/foo'
         'databases/database.example.org/foo'
     )
     )
@@ -61,12 +64,13 @@ def test_dump_databases_runs_mysqldump_with_hostname_and_port():
         extra_environment=None,
         extra_environment=None,
     ).once()
     ).once()
 
 
-    module.dump_databases(databases, 'test.yaml', dry_run=False)
+    module.dump_databases(databases, 'test.yaml', {}, dry_run=False)
 
 
 
 
 def test_dump_databases_runs_mysqldump_with_username_and_password():
 def test_dump_databases_runs_mysqldump_with_username_and_password():
     databases = [{'name': 'foo', 'username': 'root', 'password': 'trustsome1'}]
     databases = [{'name': 'foo', 'username': 'root', 'password': 'trustsome1'}]
     output_file = flexmock()
     output_file = flexmock()
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/localhost/foo'
         'databases/localhost/foo'
     )
     )
@@ -79,12 +83,13 @@ def test_dump_databases_runs_mysqldump_with_username_and_password():
         extra_environment={'MYSQL_PWD': 'trustsome1'},
         extra_environment={'MYSQL_PWD': 'trustsome1'},
     ).once()
     ).once()
 
 
-    module.dump_databases(databases, 'test.yaml', dry_run=False)
+    module.dump_databases(databases, 'test.yaml', {}, dry_run=False)
 
 
 
 
 def test_dump_databases_runs_mysqldump_with_options():
 def test_dump_databases_runs_mysqldump_with_options():
     databases = [{'name': 'foo', 'options': '--stuff=such'}]
     databases = [{'name': 'foo', 'options': '--stuff=such'}]
     output_file = flexmock()
     output_file = flexmock()
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/localhost/foo'
         'databases/localhost/foo'
     )
     )
@@ -97,12 +102,13 @@ def test_dump_databases_runs_mysqldump_with_options():
         extra_environment=None,
         extra_environment=None,
     ).once()
     ).once()
 
 
-    module.dump_databases(databases, 'test.yaml', dry_run=False)
+    module.dump_databases(databases, 'test.yaml', {}, dry_run=False)
 
 
 
 
 def test_dump_databases_runs_mysqldump_for_all_databases():
 def test_dump_databases_runs_mysqldump_for_all_databases():
     databases = [{'name': 'all'}]
     databases = [{'name': 'all'}]
     output_file = flexmock()
     output_file = flexmock()
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/localhost/all'
         'databases/localhost/all'
     )
     )
@@ -115,30 +121,33 @@ def test_dump_databases_runs_mysqldump_for_all_databases():
         extra_environment=None,
         extra_environment=None,
     ).once()
     ).once()
 
 
-    module.dump_databases(databases, 'test.yaml', dry_run=False)
+    module.dump_databases(databases, 'test.yaml', {}, dry_run=False)
 
 
 
 
 def test_make_database_dump_patterns_converts_names_to_glob_paths():
 def test_make_database_dump_patterns_converts_names_to_glob_paths():
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/*/foo'
         'databases/*/foo'
     ).and_return('databases/*/bar')
     ).and_return('databases/*/bar')
 
 
-    assert module.make_database_dump_patterns(flexmock(), flexmock(), ('foo', 'bar')) == [
+    assert module.make_database_dump_patterns(flexmock(), flexmock(), {}, ('foo', 'bar')) == [
         'databases/*/foo',
         'databases/*/foo',
         'databases/*/bar',
         'databases/*/bar',
     ]
     ]
 
 
 
 
 def test_make_database_dump_patterns_treats_empty_names_as_matching_all_databases():
 def test_make_database_dump_patterns_treats_empty_names_as_matching_all_databases():
+    flexmock(module).should_receive('make_dump_path').and_return('/dump/path')
     flexmock(module.dump).should_receive('make_database_dump_filename').with_args(
     flexmock(module.dump).should_receive('make_database_dump_filename').with_args(
-        module.DUMP_PATH, '*', '*'
+        '/dump/path', '*', '*'
     ).and_return('databases/*/*')
     ).and_return('databases/*/*')
 
 
-    assert module.make_database_dump_patterns(flexmock(), flexmock(), ()) == ['databases/*/*']
+    assert module.make_database_dump_patterns(flexmock(), flexmock(), {}, ()) == ['databases/*/*']
 
 
 
 
 def test_restore_database_dumps_restores_each_database():
 def test_restore_database_dumps_restores_each_database():
     databases = [{'name': 'foo'}, {'name': 'bar'}]
     databases = [{'name': 'foo'}, {'name': 'bar'}]
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/localhost/foo'
         'databases/localhost/foo'
     ).and_return('databases/localhost/bar')
     ).and_return('databases/localhost/bar')
@@ -153,11 +162,12 @@ def test_restore_database_dumps_restores_each_database():
             ('mysql', '--batch'), input_file=input_file, extra_environment=None
             ('mysql', '--batch'), input_file=input_file, extra_environment=None
         ).once()
         ).once()
 
 
-    module.restore_database_dumps(databases, 'test.yaml', dry_run=False)
+    module.restore_database_dumps(databases, 'test.yaml', {}, dry_run=False)
 
 
 
 
 def test_restore_database_dumps_runs_mysql_with_hostname_and_port():
 def test_restore_database_dumps_runs_mysql_with_hostname_and_port():
     databases = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
     databases = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/localhost/foo'
         'databases/localhost/foo'
     )
     )
@@ -182,11 +192,12 @@ def test_restore_database_dumps_runs_mysql_with_hostname_and_port():
         extra_environment=None,
         extra_environment=None,
     ).once()
     ).once()
 
 
-    module.restore_database_dumps(databases, 'test.yaml', dry_run=False)
+    module.restore_database_dumps(databases, 'test.yaml', {}, dry_run=False)
 
 
 
 
 def test_restore_database_dumps_runs_mysql_with_username_and_password():
 def test_restore_database_dumps_runs_mysql_with_username_and_password():
     databases = [{'name': 'foo', 'username': 'root', 'password': 'trustsome1'}]
     databases = [{'name': 'foo', 'username': 'root', 'password': 'trustsome1'}]
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/localhost/foo'
         'databases/localhost/foo'
     )
     )
@@ -202,4 +213,4 @@ def test_restore_database_dumps_runs_mysql_with_username_and_password():
         extra_environment={'MYSQL_PWD': 'trustsome1'},
         extra_environment={'MYSQL_PWD': 'trustsome1'},
     ).once()
     ).once()
 
 
-    module.restore_database_dumps(databases, 'test.yaml', dry_run=False)
+    module.restore_database_dumps(databases, 'test.yaml', {}, dry_run=False)

+ 25 - 13
tests/unit/hooks/test_postgresql.py

@@ -5,6 +5,7 @@ from borgmatic.hooks import postgresql as module
 
 
 def test_dump_databases_runs_pg_dump_for_each_database():
 def test_dump_databases_runs_pg_dump_for_each_database():
     databases = [{'name': 'foo'}, {'name': 'bar'}]
     databases = [{'name': 'foo'}, {'name': 'bar'}]
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/localhost/foo'
         'databases/localhost/foo'
     ).and_return('databases/localhost/bar')
     ).and_return('databases/localhost/bar')
@@ -25,22 +26,24 @@ def test_dump_databases_runs_pg_dump_for_each_database():
             extra_environment=None,
             extra_environment=None,
         ).once()
         ).once()
 
 
-    module.dump_databases(databases, 'test.yaml', dry_run=False)
+    module.dump_databases(databases, 'test.yaml', {}, dry_run=False)
 
 
 
 
 def test_dump_databases_with_dry_run_skips_pg_dump():
 def test_dump_databases_with_dry_run_skips_pg_dump():
     databases = [{'name': 'foo'}, {'name': 'bar'}]
     databases = [{'name': 'foo'}, {'name': 'bar'}]
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/localhost/foo'
         'databases/localhost/foo'
     ).and_return('databases/localhost/bar')
     ).and_return('databases/localhost/bar')
     flexmock(module.os).should_receive('makedirs').never()
     flexmock(module.os).should_receive('makedirs').never()
     flexmock(module).should_receive('execute_command').never()
     flexmock(module).should_receive('execute_command').never()
 
 
-    module.dump_databases(databases, 'test.yaml', dry_run=True)
+    module.dump_databases(databases, 'test.yaml', {}, dry_run=True)
 
 
 
 
 def test_dump_databases_runs_pg_dump_with_hostname_and_port():
 def test_dump_databases_runs_pg_dump_with_hostname_and_port():
     databases = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
     databases = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/database.example.org/foo'
         'databases/database.example.org/foo'
     )
     )
@@ -64,11 +67,12 @@ def test_dump_databases_runs_pg_dump_with_hostname_and_port():
         extra_environment=None,
         extra_environment=None,
     ).once()
     ).once()
 
 
-    module.dump_databases(databases, 'test.yaml', dry_run=False)
+    module.dump_databases(databases, 'test.yaml', {}, dry_run=False)
 
 
 
 
 def test_dump_databases_runs_pg_dump_with_username_and_password():
 def test_dump_databases_runs_pg_dump_with_username_and_password():
     databases = [{'name': 'foo', 'username': 'postgres', 'password': 'trustsome1'}]
     databases = [{'name': 'foo', 'username': 'postgres', 'password': 'trustsome1'}]
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/localhost/foo'
         'databases/localhost/foo'
     )
     )
@@ -90,11 +94,12 @@ def test_dump_databases_runs_pg_dump_with_username_and_password():
         extra_environment={'PGPASSWORD': 'trustsome1'},
         extra_environment={'PGPASSWORD': 'trustsome1'},
     ).once()
     ).once()
 
 
-    module.dump_databases(databases, 'test.yaml', dry_run=False)
+    module.dump_databases(databases, 'test.yaml', {}, dry_run=False)
 
 
 
 
 def test_dump_databases_runs_pg_dump_with_format():
 def test_dump_databases_runs_pg_dump_with_format():
     databases = [{'name': 'foo', 'format': 'tar'}]
     databases = [{'name': 'foo', 'format': 'tar'}]
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/localhost/foo'
         'databases/localhost/foo'
     )
     )
@@ -114,11 +119,12 @@ def test_dump_databases_runs_pg_dump_with_format():
         extra_environment=None,
         extra_environment=None,
     ).once()
     ).once()
 
 
-    module.dump_databases(databases, 'test.yaml', dry_run=False)
+    module.dump_databases(databases, 'test.yaml', {}, dry_run=False)
 
 
 
 
 def test_dump_databases_runs_pg_dump_with_options():
 def test_dump_databases_runs_pg_dump_with_options():
     databases = [{'name': 'foo', 'options': '--stuff=such'}]
     databases = [{'name': 'foo', 'options': '--stuff=such'}]
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/localhost/foo'
         'databases/localhost/foo'
     )
     )
@@ -139,11 +145,12 @@ def test_dump_databases_runs_pg_dump_with_options():
         extra_environment=None,
         extra_environment=None,
     ).once()
     ).once()
 
 
-    module.dump_databases(databases, 'test.yaml', dry_run=False)
+    module.dump_databases(databases, 'test.yaml', {}, dry_run=False)
 
 
 
 
 def test_dump_databases_runs_pg_dumpall_for_all_databases():
 def test_dump_databases_runs_pg_dumpall_for_all_databases():
     databases = [{'name': 'all'}]
     databases = [{'name': 'all'}]
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/localhost/all'
         'databases/localhost/all'
     )
     )
@@ -154,30 +161,33 @@ def test_dump_databases_runs_pg_dumpall_for_all_databases():
         extra_environment=None,
         extra_environment=None,
     ).once()
     ).once()
 
 
-    module.dump_databases(databases, 'test.yaml', dry_run=False)
+    module.dump_databases(databases, 'test.yaml', {}, dry_run=False)
 
 
 
 
 def test_make_database_dump_patterns_converts_names_to_glob_paths():
 def test_make_database_dump_patterns_converts_names_to_glob_paths():
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/*/foo'
         'databases/*/foo'
     ).and_return('databases/*/bar')
     ).and_return('databases/*/bar')
 
 
-    assert module.make_database_dump_patterns(flexmock(), flexmock(), ('foo', 'bar')) == [
+    assert module.make_database_dump_patterns(flexmock(), flexmock(), {}, ('foo', 'bar')) == [
         'databases/*/foo',
         'databases/*/foo',
         'databases/*/bar',
         'databases/*/bar',
     ]
     ]
 
 
 
 
 def test_make_database_dump_patterns_treats_empty_names_as_matching_all_databases():
 def test_make_database_dump_patterns_treats_empty_names_as_matching_all_databases():
+    flexmock(module).should_receive('make_dump_path').and_return('/dump/path')
     flexmock(module.dump).should_receive('make_database_dump_filename').with_args(
     flexmock(module.dump).should_receive('make_database_dump_filename').with_args(
-        module.DUMP_PATH, '*', '*'
+        '/dump/path', '*', '*'
     ).and_return('databases/*/*')
     ).and_return('databases/*/*')
 
 
-    assert module.make_database_dump_patterns(flexmock(), flexmock(), ()) == ['databases/*/*']
+    assert module.make_database_dump_patterns(flexmock(), flexmock(), {}, ()) == ['databases/*/*']
 
 
 
 
 def test_restore_database_dumps_restores_each_database():
 def test_restore_database_dumps_restores_each_database():
     databases = [{'name': 'foo'}, {'name': 'bar'}]
     databases = [{'name': 'foo'}, {'name': 'bar'}]
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/localhost/foo'
         'databases/localhost/foo'
     ).and_return('databases/localhost/bar')
     ).and_return('databases/localhost/bar')
@@ -201,11 +211,12 @@ def test_restore_database_dumps_restores_each_database():
             extra_environment=None,
             extra_environment=None,
         ).once()
         ).once()
 
 
-    module.restore_database_dumps(databases, 'test.yaml', dry_run=False)
+    module.restore_database_dumps(databases, 'test.yaml', {}, dry_run=False)
 
 
 
 
 def test_restore_database_dumps_runs_pg_restore_with_hostname_and_port():
 def test_restore_database_dumps_runs_pg_restore_with_hostname_and_port():
     databases = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
     databases = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/localhost/foo'
         'databases/localhost/foo'
     )
     )
@@ -244,11 +255,12 @@ def test_restore_database_dumps_runs_pg_restore_with_hostname_and_port():
         extra_environment=None,
         extra_environment=None,
     ).once()
     ).once()
 
 
-    module.restore_database_dumps(databases, 'test.yaml', dry_run=False)
+    module.restore_database_dumps(databases, 'test.yaml', {}, dry_run=False)
 
 
 
 
 def test_restore_database_dumps_runs_pg_restore_with_username_and_password():
 def test_restore_database_dumps_runs_pg_restore_with_username_and_password():
     databases = [{'name': 'foo', 'username': 'postgres', 'password': 'trustsome1'}]
     databases = [{'name': 'foo', 'username': 'postgres', 'password': 'trustsome1'}]
+    flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
         'databases/localhost/foo'
         'databases/localhost/foo'
     )
     )
@@ -283,4 +295,4 @@ def test_restore_database_dumps_runs_pg_restore_with_username_and_password():
         extra_environment={'PGPASSWORD': 'trustsome1'},
         extra_environment={'PGPASSWORD': 'trustsome1'},
     ).once()
     ).once()
 
 
-    module.restore_database_dumps(databases, 'test.yaml', dry_run=False)
+    module.restore_database_dumps(databases, 'test.yaml', {}, dry_run=False)