Browse Source

Add support for a database backup label instead of host:port (#1116)

Florian Apolloner 2 months ago
parent
commit
c084f10fe2

+ 1 - 0
NEWS

@@ -1,4 +1,5 @@
 2.0.8.dev0
 2.0.8.dev0
+ * #1116: Add support for database backup labels.
  * #1114: Document systemd configuration changes for the ZFS filesystem hook.
  * #1114: Document systemd configuration changes for the ZFS filesystem hook.
  * #1118: Fix a bug in which Borg hangs during database backup when different filesystems are in
  * #1118: Fix a bug in which Borg hangs during database backup when different filesystems are in
    use.
    use.

+ 25 - 0
borgmatic/config/schema.yaml

@@ -1323,6 +1323,11 @@ properties:
                         implicitly enables read_special (see above) to support
                         implicitly enables read_special (see above) to support
                         dump and restore streaming.
                         dump and restore streaming.
                     example: users
                     example: users
+                label:
+                    type: string
+                    description: |
+                        Label to identify the database dump in the backup.
+                    example: my_backup_label
                 hostname:
                 hostname:
                     type: string
                     type: string
                     description: |
                     description: |
@@ -1524,6 +1529,11 @@ properties:
                         database hook implicitly enables read_special (see
                         database hook implicitly enables read_special (see
                         above) to support dump and restore streaming.
                         above) to support dump and restore streaming.
                     example: users
                     example: users
+                label:
+                    type: string
+                    description: |
+                        Label to identify the database dump in the backup.
+                    example: my_backup_label
                 hostname:
                 hostname:
                     type: string
                     type: string
                     description: |
                     description: |
@@ -1689,6 +1699,11 @@ properties:
                         database hook implicitly enables read_special (see
                         database hook implicitly enables read_special (see
                         above) to support dump and restore streaming.
                         above) to support dump and restore streaming.
                     example: users
                     example: users
+                label:
+                    type: string
+                    description: |
+                        Label to identify the database dump in the backup.
+                    example: my_backup_label
                 hostname:
                 hostname:
                     type: string
                     type: string
                     description: |
                     description: |
@@ -1862,6 +1877,11 @@ properties:
                         read_special (see above) to support dump and restore
                         read_special (see above) to support dump and restore
                         streaming.
                         streaming.
                     example: /var/lib/sqlite/users.db
                     example: /var/lib/sqlite/users.db
+                label:
+                    type: string
+                    description: |
+                        Label to identify the database dump in the backup.
+                    example: my_backup_label
                 restore_path:
                 restore_path:
                     type: string
                     type: string
                     description: |
                     description: |
@@ -1910,6 +1930,11 @@ properties:
                         database hook implicitly enables read_special (see
                         database hook implicitly enables read_special (see
                         above) to support dump and restore streaming.
                         above) to support dump and restore streaming.
                     example: users
                     example: users
+                label:
+                    type: string
+                    description: |
+                        Label to identify the database dump in the backup.
+                    example: my_backup_label
                 hostname:
                 hostname:
                     type: string
                     type: string
                     description: |
                     description: |

+ 5 - 5
borgmatic/hooks/data_source/dump.py

@@ -19,7 +19,7 @@ def make_data_source_dump_path(borgmatic_runtime_directory, data_source_hook_nam
     return os.path.join(borgmatic_runtime_directory, data_source_hook_name)
     return os.path.join(borgmatic_runtime_directory, data_source_hook_name)
 
 
 
 
-def make_data_source_dump_filename(dump_path, name, hostname=None, port=None):
+def make_data_source_dump_filename(dump_path, name, hostname=None, port=None, label=None):
     '''
     '''
     Based on the given dump directory path, data source name, hostname, and port, return a filename
     Based on the given dump directory path, data source name, hostname, and port, return a filename
     to use for the data source dump. The hostname defaults to localhost.
     to use for the data source dump. The hostname defaults to localhost.
@@ -29,12 +29,12 @@ def make_data_source_dump_filename(dump_path, name, hostname=None, port=None):
     if os.path.sep in name:
     if os.path.sep in name:
         raise ValueError(f'Invalid data source name {name}')
         raise ValueError(f'Invalid data source name {name}')
 
 
-    return os.path.join(
-        dump_path,
-        (hostname or 'localhost') + ('' if port is None else f':{port}'),
-        name,
+    identifier = (
+        label if label else (hostname or 'localhost') + ('' if port is None else f':{port}')
     )
     )
 
 
+    return os.path.join(dump_path, identifier, name)
+
 
 
 def write_data_source_dumps_metadata(borgmatic_runtime_directory, hook_name, dumps_metadata):
 def write_data_source_dumps_metadata(borgmatic_runtime_directory, hook_name, dumps_metadata):
     '''
     '''

+ 4 - 3
borgmatic/hooks/data_source/mariadb.py

@@ -178,6 +178,7 @@ def execute_dump_command(
         database['name'],
         database['name'],
         database.get('hostname'),
         database.get('hostname'),
         database.get('port'),
         database.get('port'),
+        database.get('label'),
     )
     )
 
 
     if os.path.exists(dump_filename):
     if os.path.exists(dump_filename):
@@ -384,16 +385,16 @@ def make_data_source_dump_patterns(
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
 
 
     return (
     return (
-        dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, hostname='*'),
+        dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, label='*'),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
             make_dump_path(borgmatic_runtime_directory),
             make_dump_path(borgmatic_runtime_directory),
             name,
             name,
-            hostname='*',
+            label='*',
         ),
         ),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
             make_dump_path(borgmatic_source_directory),
             make_dump_path(borgmatic_source_directory),
             name,
             name,
-            hostname='*',
+            label='*',
         ),
         ),
     )
     )
 
 

+ 5 - 3
borgmatic/hooks/data_source/mongodb.py

@@ -71,6 +71,7 @@ def dump_data_sources(
             name,
             name,
             database.get('hostname'),
             database.get('hostname'),
             database.get('port'),
             database.get('port'),
+            database.get('label'),
         )
         )
         dump_format = database.get('format', 'archive')
         dump_format = database.get('format', 'archive')
 
 
@@ -200,16 +201,16 @@ def make_data_source_dump_patterns(
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
 
 
     return (
     return (
-        dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, hostname='*'),
+        dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, label='*'),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
             make_dump_path(borgmatic_runtime_directory),
             make_dump_path(borgmatic_runtime_directory),
             name,
             name,
-            hostname='*',
+            label='*',
         ),
         ),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
             make_dump_path(borgmatic_source_directory),
             make_dump_path(borgmatic_source_directory),
             name,
             name,
-            hostname='*',
+            label='*',
         ),
         ),
     )
     )
 
 
@@ -238,6 +239,7 @@ def restore_data_source_dump(
         make_dump_path(borgmatic_runtime_directory),
         make_dump_path(borgmatic_runtime_directory),
         data_source['name'],
         data_source['name'],
         data_source.get('hostname'),
         data_source.get('hostname'),
+        data_source.get('label'),
     )
     )
     restore_command = build_restore_command(
     restore_command = build_restore_command(
         extract_process,
         extract_process,

+ 4 - 3
borgmatic/hooks/data_source/mysql.py

@@ -104,6 +104,7 @@ def execute_dump_command(
         database['name'],
         database['name'],
         database.get('hostname'),
         database.get('hostname'),
         database.get('port'),
         database.get('port'),
+        database.get('label'),
     )
     )
 
 
     if os.path.exists(dump_filename):
     if os.path.exists(dump_filename):
@@ -315,16 +316,16 @@ def make_data_source_dump_patterns(
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
 
 
     return (
     return (
-        dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, hostname='*'),
+        dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, label='*'),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
             make_dump_path(borgmatic_runtime_directory),
             make_dump_path(borgmatic_runtime_directory),
             name,
             name,
-            hostname='*',
+            label='*',
         ),
         ),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
             make_dump_path(borgmatic_source_directory),
             make_dump_path(borgmatic_source_directory),
             name,
             name,
-            hostname='*',
+            label='*',
         ),
         ),
     )
     )
 
 

+ 5 - 3
borgmatic/hooks/data_source/postgresql.py

@@ -186,6 +186,7 @@ def dump_data_sources(
                 database_name,
                 database_name,
                 database.get('hostname'),
                 database.get('hostname'),
                 database.get('port'),
                 database.get('port'),
+                database.get('label'),
             )
             )
 
 
             if os.path.exists(dump_filename):
             if os.path.exists(dump_filename):
@@ -302,16 +303,16 @@ def make_data_source_dump_patterns(
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
 
 
     return (
     return (
-        dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, hostname='*'),
+        dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, label='*'),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
             make_dump_path(borgmatic_runtime_directory),
             make_dump_path(borgmatic_runtime_directory),
             name,
             name,
-            hostname='*',
+            label='*',
         ),
         ),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
             make_dump_path(borgmatic_source_directory),
             make_dump_path(borgmatic_source_directory),
             name,
             name,
-            hostname='*',
+            label='*',
         ),
         ),
     )
     )
 
 
@@ -359,6 +360,7 @@ def restore_data_source_dump(
         make_dump_path(borgmatic_runtime_directory),
         make_dump_path(borgmatic_runtime_directory),
         data_source['name'],
         data_source['name'],
         data_source.get('hostname'),
         data_source.get('hostname'),
+        data_source.get('label'),
     )
     )
     psql_command = tuple(
     psql_command = tuple(
         shlex.quote(part) for part in shlex.split(data_source.get('psql_command') or 'psql')
         shlex.quote(part) for part in shlex.split(data_source.get('psql_command') or 'psql')

+ 6 - 4
borgmatic/hooks/data_source/sqlite.py

@@ -71,7 +71,9 @@ def dump_data_sources(
             )
             )
 
 
         dump_path = make_dump_path(borgmatic_runtime_directory)
         dump_path = make_dump_path(borgmatic_runtime_directory)
-        dump_filename = dump.make_data_source_dump_filename(dump_path, database['name'])
+        dump_filename = dump.make_data_source_dump_filename(
+            dump_path, database['name'], label=database.get('label')
+        )
 
 
         if os.path.exists(dump_filename):
         if os.path.exists(dump_filename):
             logger.warning(
             logger.warning(
@@ -143,16 +145,16 @@ def make_data_source_dump_patterns(
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
 
 
     return (
     return (
-        dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, hostname='*'),
+        dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, label='*'),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
             make_dump_path(borgmatic_runtime_directory),
             make_dump_path(borgmatic_runtime_directory),
             name,
             name,
-            hostname='*',
+            label='*',
         ),
         ),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
             make_dump_path(borgmatic_source_directory),
             make_dump_path(borgmatic_source_directory),
             name,
             name,
-            hostname='*',
+            label='*',
         ),
         ),
     )
     )
 
 

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

@@ -72,8 +72,10 @@ Here's a more involved example that connects to remote databases:
 ```yaml
 ```yaml
 postgresql_databases:
 postgresql_databases:
     - name: users
     - name: users
+      label: database_server1
       hostname: database1.example.org
       hostname: database1.example.org
     - name: orders
     - name: orders
+      label: database_server2
       hostname: database2.example.org
       hostname: database2.example.org
       port: 5433
       port: 5433
       username: postgres
       username: postgres

+ 7 - 0
tests/unit/hooks/data_source/test_dump.py

@@ -25,6 +25,13 @@ def test_make_data_source_dump_filename_uses_name_and_hostname_and_port():
     )
     )
 
 
 
 
+def test_make_data_source_dump_filename_users_label():
+    assert (
+        module.make_data_source_dump_filename('databases', 'test', 'hostname', 1234, 'custom_label')
+        == 'databases/custom_label/test'
+    )
+
+
 def test_make_data_source_dump_filename_without_hostname_defaults_to_localhost():
 def test_make_data_source_dump_filename_without_hostname_defaults_to_localhost():
     assert module.make_data_source_dump_filename('databases', 'test') == 'databases/localhost/test'
     assert module.make_data_source_dump_filename('databases', 'test') == 'databases/localhost/test'