Prechádzať zdrojové kódy

Attempt to repair any inconsistencies found during a consistency check via "borgmatic check --repair" flag (#266).

Dan Helfman 5 rokov pred
rodič
commit
2ab9daaa0f

+ 2 - 0
NEWS

@@ -2,6 +2,8 @@
  * #235: Pass extra options directly to particular Borg commands, handy for Borg options that
  * #235: Pass extra options directly to particular Borg commands, handy for Borg options that
    borgmatic does not yet support natively. Use "extra_borg_options" in the storage configuration
    borgmatic does not yet support natively. Use "extra_borg_options" in the storage configuration
    section.
    section.
+ * #266: Attempt to repair any inconsistencies found during a consistency check via
+   "borgmatic check --repair" flag.
 
 
 1.4.16
 1.4.16
  * #256: Fix for "before_backup" hook not triggering an error when the command contains "borg" and
  * #256: Fix for "before_backup" hook not triggering an error when the command contains "borg" and

+ 13 - 7
borgmatic/borg/check.py

@@ -1,7 +1,7 @@
 import logging
 import logging
 
 
 from borgmatic.borg import extract
 from borgmatic.borg import extract
-from borgmatic.execute import execute_command
+from borgmatic.execute import execute_command, execute_command_without_capture
 
 
 DEFAULT_CHECKS = ('repository', 'archives')
 DEFAULT_CHECKS = ('repository', 'archives')
 DEFAULT_PREFIX = '{hostname}-'
 DEFAULT_PREFIX = '{hostname}-'
@@ -91,12 +91,13 @@ def check_archives(
     consistency_config,
     consistency_config,
     local_path='borg',
     local_path='borg',
     remote_path=None,
     remote_path=None,
+    repair=None,
     only_checks=None,
     only_checks=None,
 ):
 ):
     '''
     '''
     Given a local or remote repository path, a storage config dict, a consistency config dict,
     Given a local or remote repository path, a storage config dict, a consistency config dict,
-    local/remote commands to run, and an optional list of checks to use instead of configured
-    checks, check the contained Borg archives for consistency.
+    local/remote commands to run, whether to attempt a repair, and an optional list of checks
+    to use instead of configured checks, check the contained Borg archives for consistency.
 
 
     If there are no consistency checks to run, skip running them.
     If there are no consistency checks to run, skip running them.
     '''
     '''
@@ -106,9 +107,7 @@ def check_archives(
     extra_borg_options = storage_config.get('extra_borg_options', {}).get('check', '')
     extra_borg_options = storage_config.get('extra_borg_options', {}).get('check', '')
 
 
     if set(checks).intersection(set(DEFAULT_CHECKS + ('data',))):
     if set(checks).intersection(set(DEFAULT_CHECKS + ('data',))):
-        remote_path_flags = ('--remote-path', remote_path) if remote_path else ()
         lock_wait = storage_config.get('lock_wait', None)
         lock_wait = storage_config.get('lock_wait', None)
-        lock_wait_flags = ('--lock-wait', str(lock_wait)) if lock_wait else ()
 
 
         verbosity_flags = ()
         verbosity_flags = ()
         if logger.isEnabledFor(logging.INFO):
         if logger.isEnabledFor(logging.INFO):
@@ -120,14 +119,21 @@ def check_archives(
 
 
         full_command = (
         full_command = (
             (local_path, 'check')
             (local_path, 'check')
+            + (('--repair',) if repair else ())
             + _make_check_flags(checks, check_last, prefix)
             + _make_check_flags(checks, check_last, prefix)
-            + remote_path_flags
-            + lock_wait_flags
+            + (('--remote-path', remote_path) if remote_path else ())
+            + (('--lock-wait', str(lock_wait)) if lock_wait else ())
             + verbosity_flags
             + verbosity_flags
             + (tuple(extra_borg_options.split(' ')) if extra_borg_options else ())
             + (tuple(extra_borg_options.split(' ')) if extra_borg_options else ())
             + (repository,)
             + (repository,)
         )
         )
 
 
+        # The Borg repair option trigger an interactive prompt, which won't work when output is
+        # captured.
+        if repair:
+            execute_command_without_capture(full_command, error_on_warnings=True)
+            return
+
         execute_command(full_command, error_on_warnings=True)
         execute_command(full_command, error_on_warnings=True)
 
 
     if 'extract' in checks:
     if 'extract' in checks:

+ 7 - 0
borgmatic/commands/arguments.py

@@ -266,6 +266,13 @@ def parse_arguments(*unparsed_arguments):
         add_help=False,
         add_help=False,
     )
     )
     check_group = check_parser.add_argument_group('check arguments')
     check_group = check_parser.add_argument_group('check arguments')
+    check_group.add_argument(
+        '--repair',
+        dest='repair',
+        default=False,
+        action='store_true',
+        help='Attempt to repair any inconsistencies found (experimental and only for interactive use)',
+    )
     check_group.add_argument(
     check_group.add_argument(
         '--only',
         '--only',
         metavar='CHECK',
         metavar='CHECK',

+ 1 - 0
borgmatic/commands/borgmatic.py

@@ -230,6 +230,7 @@ def run_actions(
             consistency,
             consistency,
             local_path=local_path,
             local_path=local_path,
             remote_path=remote_path,
             remote_path=remote_path,
+            repair=arguments['check'].repair,
             only_checks=arguments['check'].only,
             only_checks=arguments['check'].only,
         )
         )
     if 'extract' in arguments:
     if 'extract' in arguments:

+ 15 - 0
tests/unit/borg/test_check.py

@@ -158,6 +158,21 @@ def test_make_check_flags_with_default_checks_and_prefix_includes_prefix_flag():
     assert flags == ('--prefix', 'foo-')
     assert flags == ('--prefix', 'foo-')
 
 
 
 
+def test_check_archives_with_repair_calls_borg_with_repair_parameter():
+    checks = ('repository',)
+    consistency_config = {'check_last': None}
+    flexmock(module).should_receive('_parse_checks').and_return(checks)
+    flexmock(module).should_receive('_make_check_flags').and_return(())
+    flexmock(module).should_receive('execute_command').never()
+    flexmock(module).should_receive('execute_command_without_capture').with_args(
+        ('borg', 'check', '--repair', 'repo'), error_on_warnings=True
+    ).once()
+
+    module.check_archives(
+        repository='repo', storage_config={}, consistency_config=consistency_config, repair=True
+    )
+
+
 @pytest.mark.parametrize(
 @pytest.mark.parametrize(
     'checks',
     'checks',
     (
     (