2
0
Эх сурвалжийг харах

Add "borgmatic check --force" flag to ignore configured check frequencies (#523).

Dan Helfman 3 жил өмнө
parent
commit
8fa90053cf

+ 2 - 1
NEWS

@@ -1,6 +1,7 @@
 1.6.2.dev0
 1.6.2.dev0
  * #523: Reduce the default consistency check frequency and support configuring the frequency
  * #523: Reduce the default consistency check frequency and support configuring the frequency
-   independently for each check. See the documentation for more information:
+   independently for each check. Also add "borgmatic check --force" flag to ignore configured
+   frequencies. See the documentation for more information:
    https://torsion.org/borgmatic/docs/how-to/deal-with-very-large-backups/#check-frequency
    https://torsion.org/borgmatic/docs/how-to/deal-with-very-large-backups/#check-frequency
  * #536: Fix generate-borgmatic-config to support more complex schema changes like the new
  * #536: Fix generate-borgmatic-config to support more complex schema changes like the new
    Healthchecks configuration options when the "--source" flag is used.
    Healthchecks configuration options when the "--source" flag is used.

+ 12 - 5
borgmatic/borg/check.py

@@ -81,8 +81,8 @@ def parse_frequency(frequency):
         time_unit += 's'
         time_unit += 's'
 
 
     if time_unit == 'months':
     if time_unit == 'months':
-        number *= 4
-        time_unit = 'weeks'
+        number *= 30
+        time_unit = 'days'
     elif time_unit == 'years':
     elif time_unit == 'years':
         number *= 365
         number *= 365
         time_unit = 'days'
         time_unit = 'days'
@@ -93,11 +93,13 @@ def parse_frequency(frequency):
         raise ValueError(f"Could not parse consistency check frequency '{frequency}'")
         raise ValueError(f"Could not parse consistency check frequency '{frequency}'")
 
 
 
 
-def filter_checks_on_frequency(location_config, consistency_config, borg_repository_id, checks):
+def filter_checks_on_frequency(
+    location_config, consistency_config, borg_repository_id, checks, force
+):
     '''
     '''
     Given a location config, a consistency config with a "checks" sequence of dicts, a Borg
     Given a location config, a consistency config with a "checks" sequence of dicts, a Borg
-    repository ID, and sequence of checks, filter down those checks based on the configured
-    "frequency" for each check as compared to its check time file.
+    repository ID, a sequence of checks, and whether to force checks to run, filter down those
+    checks based on the configured "frequency" for each check as compared to its check time file.
 
 
     In other words, a check whose check time file's timestamp is too new (based on the configured
     In other words, a check whose check time file's timestamp is too new (based on the configured
     frequency) will get cut from the returned sequence of checks. Example:
     frequency) will get cut from the returned sequence of checks. Example:
@@ -119,6 +121,9 @@ def filter_checks_on_frequency(location_config, consistency_config, borg_reposit
     '''
     '''
     filtered_checks = list(checks)
     filtered_checks = list(checks)
 
 
+    if force:
+        return tuple(filtered_checks)
+
     for check_config in consistency_config.get('checks', DEFAULT_CHECKS):
     for check_config in consistency_config.get('checks', DEFAULT_CHECKS):
         check = check_config['name']
         check = check_config['name']
         if checks and check not in checks:
         if checks and check not in checks:
@@ -240,6 +245,7 @@ def check_archives(
     progress=None,
     progress=None,
     repair=None,
     repair=None,
     only_checks=None,
     only_checks=None,
+    force=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,
@@ -269,6 +275,7 @@ def check_archives(
         consistency_config,
         consistency_config,
         borg_repository_id,
         borg_repository_id,
         parse_checks(consistency_config, only_checks),
         parse_checks(consistency_config, only_checks),
+        force,
     )
     )
     check_last = consistency_config.get('check_last', None)
     check_last = consistency_config.get('check_last', None)
     lock_wait = None
     lock_wait = None

+ 7 - 1
borgmatic/commands/arguments.py

@@ -346,7 +346,7 @@ def make_parsers():
         dest='repair',
         dest='repair',
         default=False,
         default=False,
         action='store_true',
         action='store_true',
-        help='Attempt to repair any inconsistencies found (experimental and only for interactive use)',
+        help='Attempt to repair any inconsistencies found (for interactive use)',
     )
     )
     check_group.add_argument(
     check_group.add_argument(
         '--only',
         '--only',
@@ -356,6 +356,12 @@ def make_parsers():
         action='append',
         action='append',
         help='Run a particular consistency check (repository, archives, data, or extract) instead of configured checks (subject to configured frequency, can specify flag multiple times)',
         help='Run a particular consistency check (repository, archives, data, or extract) instead of configured checks (subject to configured frequency, can specify flag multiple times)',
     )
     )
+    check_group.add_argument(
+        '--force',
+        default=False,
+        action='store_true',
+        help='Ignore configured check frequencies and run checks unconditionally',
+    )
     check_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
     check_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
 
 
     extract_parser = subparsers.add_parser(
     extract_parser = subparsers.add_parser(

+ 1 - 0
borgmatic/commands/borgmatic.py

@@ -403,6 +403,7 @@ def run_actions(
             progress=arguments['check'].progress,
             progress=arguments['check'].progress,
             repair=arguments['check'].repair,
             repair=arguments['check'].repair,
             only_checks=arguments['check'].only,
             only_checks=arguments['check'].only,
+            force=arguments['check'].force,
         )
         )
         command.execute_hook(
         command.execute_hook(
             hooks.get('after_check'),
             hooks.get('after_check'),

+ 4 - 1
docs/how-to/deal-with-very-large-backups.md

@@ -96,6 +96,9 @@ within `~/.borgmatic/checks`). If it hasn't been long enough, the check is
 skipped. And you still have to run `borgmatic check` (or just `borgmatic`) in
 skipped. And you still have to run `borgmatic check` (or just `borgmatic`) in
 order for checks to run, even when a `frequency` is configured!
 order for checks to run, even when a `frequency` is configured!
 
 
+If you want to temporarily ignore your configured frequencies, you can invoke
+`borgmatic check --force` to run checks unconditionally.
+
 
 
 ### Disabling checks
 ### Disabling checks
 
 
@@ -129,7 +132,7 @@ borgmatic check --only data --only extract
 
 
 This is useful for running slow consistency checks on an infrequent basis,
 This is useful for running slow consistency checks on an infrequent basis,
 separate from your regular checks. It is still subject to any configured
 separate from your regular checks. It is still subject to any configured
-check frequencies.
+check frequencies unless the `--force` flag is used.
 
 
 
 
 ## Troubleshooting
 ## Troubleshooting

+ 22 - 3
tests/unit/borg/test_check.py

@@ -83,8 +83,8 @@ def test_parse_checks_with_override_data_check_also_injects_archives():
         ('2 days', module.datetime.timedelta(days=2)),
         ('2 days', module.datetime.timedelta(days=2)),
         ('1 week', module.datetime.timedelta(weeks=1)),
         ('1 week', module.datetime.timedelta(weeks=1)),
         ('2 weeks', module.datetime.timedelta(weeks=2)),
         ('2 weeks', module.datetime.timedelta(weeks=2)),
-        ('1 month', module.datetime.timedelta(weeks=4)),
-        ('2 months', module.datetime.timedelta(weeks=8)),
+        ('1 month', module.datetime.timedelta(days=30)),
+        ('2 months', module.datetime.timedelta(days=60)),
         ('1 year', module.datetime.timedelta(days=365)),
         ('1 year', module.datetime.timedelta(days=365)),
         ('2 years', module.datetime.timedelta(days=365 * 2)),
         ('2 years', module.datetime.timedelta(days=365 * 2)),
     ),
     ),
@@ -113,12 +113,17 @@ def test_filter_checks_on_frequency_without_config_uses_default_checks():
         consistency_config={},
         consistency_config={},
         borg_repository_id='repo',
         borg_repository_id='repo',
         checks=('repository', 'archives'),
         checks=('repository', 'archives'),
+        force=False,
     ) == ('repository', 'archives')
     ) == ('repository', 'archives')
 
 
 
 
 def test_filter_checks_on_frequency_retains_unconfigured_check():
 def test_filter_checks_on_frequency_retains_unconfigured_check():
     assert module.filter_checks_on_frequency(
     assert module.filter_checks_on_frequency(
-        location_config={}, consistency_config={}, borg_repository_id='repo', checks=('data',),
+        location_config={},
+        consistency_config={},
+        borg_repository_id='repo',
+        checks=('data',),
+        force=False,
     ) == ('data',)
     ) == ('data',)
 
 
 
 
@@ -130,6 +135,7 @@ def test_filter_checks_on_frequency_retains_check_without_frequency():
         consistency_config={'checks': [{'name': 'archives'}]},
         consistency_config={'checks': [{'name': 'archives'}]},
         borg_repository_id='repo',
         borg_repository_id='repo',
         checks=('archives',),
         checks=('archives',),
+        force=False,
     ) == ('archives',)
     ) == ('archives',)
 
 
 
 
@@ -147,6 +153,7 @@ def test_filter_checks_on_frequency_retains_check_with_elapsed_frequency():
         consistency_config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
         consistency_config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
         borg_repository_id='repo',
         borg_repository_id='repo',
         checks=('archives',),
         checks=('archives',),
+        force=False,
     ) == ('archives',)
     ) == ('archives',)
 
 
 
 
@@ -162,6 +169,7 @@ def test_filter_checks_on_frequency_retains_check_with_missing_check_time_file()
         consistency_config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
         consistency_config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
         borg_repository_id='repo',
         borg_repository_id='repo',
         checks=('archives',),
         checks=('archives',),
+        force=False,
     ) == ('archives',)
     ) == ('archives',)
 
 
 
 
@@ -178,11 +186,22 @@ def test_filter_checks_on_frequency_skips_check_with_unelapsed_frequency():
             consistency_config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
             consistency_config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
             borg_repository_id='repo',
             borg_repository_id='repo',
             checks=('archives',),
             checks=('archives',),
+            force=False,
         )
         )
         == ()
         == ()
     )
     )
 
 
 
 
+def test_filter_checks_on_frequency_restains_check_with_unelapsed_frequency_and_force():
+    assert module.filter_checks_on_frequency(
+        location_config={},
+        consistency_config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
+        borg_repository_id='repo',
+        checks=('archives',),
+        force=True,
+    ) == ('archives',)
+
+
 def test_make_check_flags_with_repository_check_returns_flag():
 def test_make_check_flags_with_repository_check_returns_flag():
     flags = module.make_check_flags(('repository',))
     flags = module.make_check_flags(('repository',))
 
 

+ 3 - 1
tests/unit/commands/test_borgmatic.py

@@ -468,7 +468,9 @@ def test_run_actions_calls_hooks_for_check_action():
     flexmock(module.command).should_receive('execute_hook').twice()
     flexmock(module.command).should_receive('execute_hook').twice()
     arguments = {
     arguments = {
         'global': flexmock(monitoring_verbosity=1, dry_run=False),
         'global': flexmock(monitoring_verbosity=1, dry_run=False),
-        'check': flexmock(progress=flexmock(), repair=flexmock(), only=flexmock()),
+        'check': flexmock(
+            progress=flexmock(), repair=flexmock(), only=flexmock(), force=flexmock()
+        ),
     }
     }
 
 
     list(
     list(