Selaa lähdekoodia

Add "borgmatic list --successful" flag to only list successful (non-checkpoint) archives (#86).

Dan Helfman 5 vuotta sitten
vanhempi
sitoutus
7b3b28616d

+ 2 - 1
NEWS

@@ -1,4 +1,5 @@
-1.3.24.dev0
+1.3.24
+ * #86: Add "borgmatic list --successful" flag to only list successful (non-checkpoint) archives.
  * Add a suggestion form to all documentation pages, so users can submit ideas for improving the
    documentation.
  * Update documentation link to community Arch Linux borgmatic package.

+ 9 - 1
borgmatic/borg/list.py

@@ -6,6 +6,10 @@ from borgmatic.execute import execute_command
 logger = logging.getLogger(__name__)
 
 
+# A hack to convince Borg to exclude archives ending in ".checkpoint".
+BORG_EXCLUDE_CHECKPOINTS_GLOB = '*[!.][!c][!h][!e][!c][!k][!p][!o][!i][!n][!t]'
+
+
 def list_archives(repository, storage_config, list_arguments, local_path='borg', remote_path=None):
     '''
     Given a local or remote repository path, a storage config dict, and the arguments to the list
@@ -13,6 +17,8 @@ def list_archives(repository, storage_config, list_arguments, local_path='borg',
     if an archive name is given, listing the files in that archive.
     '''
     lock_wait = storage_config.get('lock_wait', None)
+    if list_arguments.successful:
+        list_arguments.glob_archives = BORG_EXCLUDE_CHECKPOINTS_GLOB
 
     full_command = (
         (local_path, 'list')
@@ -28,7 +34,9 @@ def list_archives(repository, storage_config, list_arguments, local_path='borg',
         )
         + make_flags('remote-path', remote_path)
         + make_flags('lock-wait', lock_wait)
-        + make_flags_from_arguments(list_arguments, excludes=('repository', 'archive'))
+        + make_flags_from_arguments(
+            list_arguments, excludes=('repository', 'archive', 'successful')
+        )
         + (
             '::'.join((repository, list_arguments.archive))
             if list_arguments.archive

+ 9 - 0
borgmatic/commands/arguments.py

@@ -316,6 +316,12 @@ def parse_arguments(*unparsed_arguments):
     list_group.add_argument(
         '-a', '--glob-archives', metavar='GLOB', help='Only list archive names matching this glob'
     )
+    list_group.add_argument(
+        '--successful',
+        default=False,
+        action='store_true',
+        help='Only list archive names of successful (non-checkpoint) backups',
+    )
     list_group.add_argument(
         '--sort-by', metavar='KEYS', help='Comma-separated list of sorting keys'
     )
@@ -388,6 +394,9 @@ def parse_arguments(*unparsed_arguments):
     if 'init' in arguments and arguments['global'].dry_run:
         raise ValueError('The init action cannot be used with the --dry-run option')
 
+    if 'list' in arguments and arguments['list'].glob_archives and arguments['list'].successful:
+        raise ValueError('The --glob-archives and --successful options cannot be used together')
+
     if (
         'list' in arguments
         and 'info' in arguments

+ 18 - 1
docs/how-to/inspect-your-backups.md

@@ -32,7 +32,7 @@ borgmatic --stats
 
 ## Existing backups
 
-Borgmatic provides convenient actions for Borg's
+borgmatic provides convenient actions for Borg's
 [list](https://borgbackup.readthedocs.io/en/stable/usage/list.html) and
 [info](https://borgbackup.readthedocs.io/en/stable/usage/info.html)
 functionality:
@@ -46,6 +46,7 @@ borgmatic info
 (No borgmatic `list` or `info` actions? Try the old-style `--list` or
 `--info`. Or upgrade borgmatic!)
 
+
 ## Logging
 
 By default, borgmatic logs to a local syslog-compatible daemon if one is
@@ -135,6 +136,22 @@ Note that when you specify the `--json` flag, Borg's other non-JSON output is
 suppressed so as not to interfere with the captured JSON. Also note that JSON
 output only shows up at the console, and not in syslog.
 
+### Successful backups
+
+`borgmatic list` includes support for a `--successful` flag that only lists
+successful (non-checkpoint) backups. Combined with a built-in Borg flag like
+`--last`, you can list the last successful backup for use in your monitoring
+scripts. Here's an example combined with `--json`:
+
+```bash
+borgmatic list --successful --last 1 --json
+```
+
+Note that this particular combination will only work if you've got a single
+backup "series" in your repository. If you're instead backing up, say, from
+multiple different hosts into a single repository, then you'll need to get
+fancier with your archive listing. See `borg list --help` for more flags.
+
 
 ## Related documentation
 

+ 1 - 1
setup.py

@@ -1,6 +1,6 @@
 from setuptools import find_packages, setup
 
-VERSION = '1.3.24.dev0'
+VERSION = '1.3.24'
 
 
 setup(

+ 9 - 0
tests/integration/commands/test_arguments.py

@@ -230,6 +230,15 @@ def test_parse_arguments_disallows_init_and_dry_run():
         )
 
 
+def test_parse_arguments_disallows_glob_archives_with_successful():
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
+
+    with pytest.raises(ValueError):
+        module.parse_arguments(
+            '--config', 'myconfig', 'list', '--glob-archives', '*glob*', '--successful'
+        )
+
+
 def test_parse_arguments_disallows_repository_without_extract_or_list():
     flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
 

+ 39 - 12
tests/unit/borg/test_list.py

@@ -14,7 +14,9 @@ def test_list_archives_calls_borg_with_parameters():
     )
 
     module.list_archives(
-        repository='repo', storage_config={}, list_arguments=flexmock(archive=None, json=False)
+        repository='repo',
+        storage_config={},
+        list_arguments=flexmock(archive=None, json=False, successful=False),
     )
 
 
@@ -25,7 +27,9 @@ def test_list_archives_with_log_info_calls_borg_with_info_parameter():
     insert_logging_mock(logging.INFO)
 
     module.list_archives(
-        repository='repo', storage_config={}, list_arguments=flexmock(archive=None, json=False)
+        repository='repo',
+        storage_config={},
+        list_arguments=flexmock(archive=None, json=False, successful=False),
     )
 
 
@@ -36,7 +40,9 @@ def test_list_archives_with_log_info_and_json_suppresses_most_borg_output():
     insert_logging_mock(logging.INFO)
 
     module.list_archives(
-        repository='repo', storage_config={}, list_arguments=flexmock(archive=None, json=True)
+        repository='repo',
+        storage_config={},
+        list_arguments=flexmock(archive=None, json=True, successful=False),
     )
 
 
@@ -47,7 +53,9 @@ def test_list_archives_with_log_debug_calls_borg_with_debug_parameter():
     insert_logging_mock(logging.DEBUG)
 
     module.list_archives(
-        repository='repo', storage_config={}, list_arguments=flexmock(archive=None, json=False)
+        repository='repo',
+        storage_config={},
+        list_arguments=flexmock(archive=None, json=False, successful=False),
     )
 
 
@@ -58,7 +66,9 @@ def test_list_archives_with_log_debug_and_json_suppresses_most_borg_output():
     insert_logging_mock(logging.DEBUG)
 
     module.list_archives(
-        repository='repo', storage_config={}, list_arguments=flexmock(archive=None, json=True)
+        repository='repo',
+        storage_config={},
+        list_arguments=flexmock(archive=None, json=True, successful=False),
     )
 
 
@@ -71,7 +81,7 @@ def test_list_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
     module.list_archives(
         repository='repo',
         storage_config=storage_config,
-        list_arguments=flexmock(archive=None, json=False),
+        list_arguments=flexmock(archive=None, json=False, successful=False),
     )
 
 
@@ -84,7 +94,7 @@ def test_list_archives_with_archive_calls_borg_with_archive_parameter():
     module.list_archives(
         repository='repo',
         storage_config=storage_config,
-        list_arguments=flexmock(archive='archive', json=False),
+        list_arguments=flexmock(archive='archive', json=False, successful=False),
     )
 
 
@@ -96,7 +106,7 @@ def test_list_archives_with_local_path_calls_borg_via_local_path():
     module.list_archives(
         repository='repo',
         storage_config={},
-        list_arguments=flexmock(archive=None, json=False),
+        list_arguments=flexmock(archive=None, json=False, successful=False),
         local_path='borg1',
     )
 
@@ -109,7 +119,7 @@ def test_list_archives_with_remote_path_calls_borg_with_remote_path_parameters()
     module.list_archives(
         repository='repo',
         storage_config={},
-        list_arguments=flexmock(archive=None, json=False),
+        list_arguments=flexmock(archive=None, json=False, successful=False),
         remote_path='borg1',
     )
 
@@ -122,7 +132,7 @@ def test_list_archives_with_short_calls_borg_with_short_parameter():
     module.list_archives(
         repository='repo',
         storage_config={},
-        list_arguments=flexmock(archive=None, json=False, short=True),
+        list_arguments=flexmock(archive=None, json=False, successful=False, short=True),
     )
 
 
@@ -149,7 +159,22 @@ def test_list_archives_passes_through_arguments_to_borg(argument_name):
     module.list_archives(
         repository='repo',
         storage_config={},
-        list_arguments=flexmock(archive=None, json=False, **{argument_name: 'value'}),
+        list_arguments=flexmock(
+            archive=None, json=False, successful=False, **{argument_name: 'value'}
+        ),
+    )
+
+
+def test_list_archives_with_successful_calls_borg_to_exclude_checkpoints():
+    flexmock(module).should_receive('execute_command').with_args(
+        ('borg', 'list', '--glob-archives', module.BORG_EXCLUDE_CHECKPOINTS_GLOB, 'repo'),
+        output_log_level=logging.WARNING,
+    ).and_return('[]')
+
+    module.list_archives(
+        repository='repo',
+        storage_config={},
+        list_arguments=flexmock(archive=None, json=False, successful=True),
     )
 
 
@@ -159,7 +184,9 @@ def test_list_archives_with_json_calls_borg_with_json_parameter():
     ).and_return('[]')
 
     json_output = module.list_archives(
-        repository='repo', storage_config={}, list_arguments=flexmock(archive=None, json=True)
+        repository='repo',
+        storage_config={},
+        list_arguments=flexmock(archive=None, json=True, successful=False),
     )
 
     assert json_output == '[]'