ソースを参照

Stream SQLite databases directly to Borg instead of dumping to an intermediate file (#807).

Dan Helfman 1 年間 前
コミット
5f3dc1cfb0
3 ファイル変更15 行追加11 行削除
  1. 1 0
      NEWS
  2. 7 4
      borgmatic/hooks/sqlite.py
  3. 7 7
      tests/unit/hooks/test_sqlite.py

+ 1 - 0
NEWS

@@ -6,6 +6,7 @@
  * #800: Add configured repository labels to the JSON output for all actions.
  * #800: Add configured repository labels to the JSON output for all actions.
  * #802: The "check --force" flag now runs checks even if "check" is in "skip_actions".
  * #802: The "check --force" flag now runs checks even if "check" is in "skip_actions".
  * #804: Validate the configured action names in the "skip_actions" option.
  * #804: Validate the configured action names in the "skip_actions" option.
+ * #807: Stream SQLite databases directly to Borg instead of dumping to an intermediate file.
  * When logging commands that borgmatic executes, log the environment variables that
  * When logging commands that borgmatic executes, log the environment variables that
    borgmatic sets for those commands. (But don't log their values, since they often contain
    borgmatic sets for those commands. (But don't log their values, since they often contain
    passwords.)
    passwords.)

+ 7 - 4
borgmatic/hooks/sqlite.py

@@ -18,10 +18,12 @@ def make_dump_path(config):  # pragma: no cover
 
 
 def dump_data_sources(databases, config, log_prefix, dry_run):
 def dump_data_sources(databases, config, log_prefix, dry_run):
     '''
     '''
-    Dump the given SQLite3 databases to a file. The databases are supplied as a sequence of
+    Dump the given SQLite3 databases to a named pipe. The databases are supplied as a sequence of
     configuration dicts, as per the configuration schema. Use the given configuration dict to
     configuration dicts, as per the configuration schema. Use the given configuration dict to
-    construct the destination path and the given log prefix in any log entries. If this is a dry
-    run, then don't actually dump anything.
+    construct the destination path and the given log prefix in any log entries.
+
+    Return a sequence of subprocess.Popen instances for the dump processes ready to spew to a named
+    pipe. But if this is a dry run, then don't actually dump anything and return an empty sequence.
     '''
     '''
     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 ''
     processes = []
     processes = []
@@ -40,6 +42,7 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
 
 
         dump_path = make_dump_path(config)
         dump_path = make_dump_path(config)
         dump_filename = dump.make_data_source_dump_filename(dump_path, database['name'])
         dump_filename = dump.make_data_source_dump_filename(dump_path, database['name'])
+
         if os.path.exists(dump_filename):
         if os.path.exists(dump_filename):
             logger.warning(
             logger.warning(
                 f'{log_prefix}: Skipping duplicate dump of SQLite database at {database_path} to {dump_filename}'
                 f'{log_prefix}: Skipping duplicate dump of SQLite database at {database_path} to {dump_filename}'
@@ -59,7 +62,7 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
         if dry_run:
         if dry_run:
             continue
             continue
 
 
-        dump.create_parent_directory_for_dump(dump_filename)
+        dump.create_named_pipe_for_dump(dump_filename)
         processes.append(execute_command(command, shell=True, run_to_completion=False))
         processes.append(execute_command(command, shell=True, run_to_completion=False))
 
 
     return processes
     return processes

+ 7 - 7
tests/unit/hooks/test_sqlite.py

@@ -13,7 +13,7 @@ def test_dump_data_sources_logs_and_skips_if_dump_already_exists():
         '/path/to/dump/database'
         '/path/to/dump/database'
     )
     )
     flexmock(module.os.path).should_receive('exists').and_return(True)
     flexmock(module.os.path).should_receive('exists').and_return(True)
-    flexmock(module.dump).should_receive('create_parent_directory_for_dump').never()
+    flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
     flexmock(module).should_receive('execute_command').never()
     flexmock(module).should_receive('execute_command').never()
 
 
     assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == []
     assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == []
@@ -31,7 +31,7 @@ def test_dump_data_sources_dumps_each_database():
         '/path/to/dump/database'
         '/path/to/dump/database'
     )
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.os.path).should_receive('exists').and_return(False)
-    flexmock(module.dump).should_receive('create_parent_directory_for_dump')
+    flexmock(module.dump).should_receive('create_named_pipe_for_dump')
     flexmock(module).should_receive('execute_command').and_return(processes[0]).and_return(
     flexmock(module).should_receive('execute_command').and_return(processes[0]).and_return(
         processes[1]
         processes[1]
     )
     )
@@ -39,7 +39,7 @@ def test_dump_data_sources_dumps_each_database():
     assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
     assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
 
 
 
 
-def test_dumping_database_with_non_existent_path_warns_and_dumps_database():
+def test_dump_data_sources_with_non_existent_path_warns_and_dumps_database():
     databases = [
     databases = [
         {'path': '/path/to/database1', 'name': 'database1'},
         {'path': '/path/to/database1', 'name': 'database1'},
     ]
     ]
@@ -51,13 +51,13 @@ def test_dumping_database_with_non_existent_path_warns_and_dumps_database():
         '/path/to/dump/database'
         '/path/to/dump/database'
     )
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.os.path).should_receive('exists').and_return(False)
-    flexmock(module.dump).should_receive('create_parent_directory_for_dump')
+    flexmock(module.dump).should_receive('create_named_pipe_for_dump')
     flexmock(module).should_receive('execute_command').and_return(processes[0])
     flexmock(module).should_receive('execute_command').and_return(processes[0])
 
 
     assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
     assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
 
 
 
 
-def test_dumping_database_with_name_all_warns_and_dumps_all_databases():
+def test_dump_data_sources_with_name_all_warns_and_dumps_all_databases():
     databases = [
     databases = [
         {'path': '/path/to/database1', 'name': 'all'},
         {'path': '/path/to/database1', 'name': 'all'},
     ]
     ]
@@ -71,7 +71,7 @@ def test_dumping_database_with_name_all_warns_and_dumps_all_databases():
         '/path/to/dump/database'
         '/path/to/dump/database'
     )
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.os.path).should_receive('exists').and_return(False)
-    flexmock(module.dump).should_receive('create_parent_directory_for_dump')
+    flexmock(module.dump).should_receive('create_named_pipe_for_dump')
     flexmock(module).should_receive('execute_command').and_return(processes[0])
     flexmock(module).should_receive('execute_command').and_return(processes[0])
 
 
     assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
     assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
@@ -85,7 +85,7 @@ def test_dump_data_sources_does_not_dump_if_dry_run():
         '/path/to/dump/database'
         '/path/to/dump/database'
     )
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.os.path).should_receive('exists').and_return(False)
-    flexmock(module.dump).should_receive('create_parent_directory_for_dump').never()
+    flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
     flexmock(module).should_receive('execute_command').never()
     flexmock(module).should_receive('execute_command').never()
 
 
     assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=True) == []
     assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=True) == []