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

This commit is contained in:
Dan Helfman 2019-10-13 15:58:11 -07:00
parent f3910f49ca
commit 7b3b28616d
7 changed files with 87 additions and 16 deletions

3
NEWS
View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

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

View file

@ -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'])

View file

@ -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 == '[]'