Browse Source

In the SQLite database hook, run SQLite such that it exits upon encountering an error instead of, you know, not doing that.

Dan Helfman 3 weeks ago
parent
commit
14a8055e71
3 changed files with 12 additions and 1 deletions
  1. 2 0
      NEWS
  2. 2 1
      borgmatic/hooks/data_source/sqlite.py
  3. 8 0
      tests/unit/hooks/data_source/test_sqlite.py

+ 2 - 0
NEWS

@@ -1,4 +1,6 @@
 2.0.12.dev0
 2.0.12.dev0
+ * In the SQLite database hook, run SQLite such that it exits upon encountering an error instead of,
+   you know, not doing that.
  * Add documentation on repositories, including SSH, Rclone, S3, and B2:
  * Add documentation on repositories, including SSH, Rclone, S3, and B2:
    https://torsion.org/borgmatic/reference/configuration/repositories/
    https://torsion.org/borgmatic/reference/configuration/repositories/
  * Improve documentation search results for individual configuration options.
  * Improve documentation search results for individual configuration options.

+ 2 - 1
borgmatic/hooks/data_source/sqlite.py

@@ -86,6 +86,7 @@ def dump_data_sources(
         )
         )
         command = (
         command = (
             *sqlite_command,
             *sqlite_command,
+            '-bail',
             shlex.quote(database_path),
             shlex.quote(database_path),
             '.dump',
             '.dump',
             '>',
             '>',
@@ -196,7 +197,7 @@ def restore_data_source_dump(
         shlex.quote(part)
         shlex.quote(part)
         for part in shlex.split(data_source.get('sqlite_restore_command') or 'sqlite3')
         for part in shlex.split(data_source.get('sqlite_restore_command') or 'sqlite3')
     )
     )
-    restore_command = (*sqlite_restore_command, shlex.quote(database_path))
+    restore_command = (*sqlite_restore_command, '-bail', shlex.quote(database_path))
     # Don't give Borg local path so as to error on warnings, as "borg extract" only gives a warning
     # Don't give Borg local path so as to error on warnings, as "borg extract" only gives a warning
     # if the restore paths don't exist in the archive.
     # if the restore paths don't exist in the archive.
     execute_command_with_processes(
     execute_command_with_processes(

+ 8 - 0
tests/unit/hooks/data_source/test_sqlite.py

@@ -114,6 +114,7 @@ def test_dump_data_sources_with_path_injection_attack_gets_escaped():
     flexmock(module).should_receive('execute_command').with_args(
     flexmock(module).should_receive('execute_command').with_args(
         (
         (
             'sqlite3',
             'sqlite3',
+            '-bail',
             "'/path/to/database1; naughty-command'",
             "'/path/to/database1; naughty-command'",
             '.dump',
             '.dump',
             '>',
             '>',
@@ -170,6 +171,7 @@ def test_dump_data_sources_runs_non_default_sqlite_with_path_injection_attack_ge
         (
         (
             'custom_sqlite',  # custom sqlite command
             'custom_sqlite',  # custom sqlite command
             "'*'",  # Should get shell escaped to prevent injection attacks.
             "'*'",  # Should get shell escaped to prevent injection attacks.
+            '-bail',
             "'/path/to/database1; naughty-command'",
             "'/path/to/database1; naughty-command'",
             '.dump',
             '.dump',
             '>',
             '>',
@@ -325,6 +327,7 @@ def test_restore_data_source_dump_restores_database():
     flexmock(module).should_receive('execute_command_with_processes').with_args(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
         (
             'sqlite3',
             'sqlite3',
+            '-bail',
             '/path/to/database',
             '/path/to/database',
         ),
         ),
         processes=[extract_process],
         processes=[extract_process],
@@ -360,6 +363,7 @@ def test_restore_data_source_dump_runs_non_default_sqlite_restores_database():
         (
         (
             'custom_sqlite',
             'custom_sqlite',
             "'*'",  # Should get shell escaped to prevent injection attacks.
             "'*'",  # Should get shell escaped to prevent injection attacks.
+            '-bail',
             '/path/to/database',
             '/path/to/database',
         ),
         ),
         processes=[extract_process],
         processes=[extract_process],
@@ -393,6 +397,7 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
     flexmock(module).should_receive('execute_command_with_processes').with_args(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
         (
             'sqlite3',
             'sqlite3',
+            '-bail',
             'cli/path/to/database',
             'cli/path/to/database',
         ),
         ),
         processes=[extract_process],
         processes=[extract_process],
@@ -426,6 +431,7 @@ def test_restore_data_source_dump_runs_non_default_sqlite_with_connection_params
     flexmock(module).should_receive('execute_command_with_processes').with_args(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
         (
             'custom_sqlite',
             'custom_sqlite',
+            '-bail',
             'cli/path/to/database',
             'cli/path/to/database',
         ),
         ),
         processes=[extract_process],
         processes=[extract_process],
@@ -462,6 +468,7 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
     flexmock(module).should_receive('execute_command_with_processes').with_args(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
         (
             'sqlite3',
             'sqlite3',
+            '-bail',
             'config/path/to/database',
             'config/path/to/database',
         ),
         ),
         processes=[extract_process],
         processes=[extract_process],
@@ -496,6 +503,7 @@ def test_restore_data_source_dump_runs_non_default_sqlite_without_connection_par
     flexmock(module).should_receive('execute_command_with_processes').with_args(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
         (
             'custom_sqlite',
             'custom_sqlite',
+            '-bail',
             'config/path/to/database',
             'config/path/to/database',
         ),
         ),
         processes=[extract_process],
         processes=[extract_process],