mirror of
https://github.com/borgmatic-collective/borgmatic.git
synced 2026-07-23 18:33:02 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b3b28616d | ||
|
|
f3910f49ca | ||
|
|
59e1cac92c | ||
|
|
b1f0287fdb | ||
|
|
99c35d4077 | ||
|
|
07b9ff61f2 | ||
|
|
f573c1810a |
@@ -1,3 +1,9 @@
|
||||
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.
|
||||
|
||||
1.3.23
|
||||
* #174: More detailed error alerting via runtime context available in "on_error" hook.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
)
|
||||
@@ -323,7 +329,7 @@ def parse_arguments(*unparsed_arguments):
|
||||
'--first', metavar='N', help='List first N archives after other filters are applied'
|
||||
)
|
||||
list_group.add_argument(
|
||||
'--last', metavar='N', help='List first N archives after other filters are applied'
|
||||
'--last', metavar='N', help='List last N archives after other filters are applied'
|
||||
)
|
||||
list_group.add_argument(
|
||||
'-e', '--exclude', metavar='PATTERN', help='Exclude paths matching the pattern'
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#suggestion-form textarea {
|
||||
font-family: sans-serif;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#suggestion-form label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#suggestion-form input[type=email] {
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#suggestion-form .form-error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<h2>Improve this documentation</h2>
|
||||
|
||||
<p>Have an idea on how to make this documentation even better? Send your
|
||||
feedback below! (But if you need help installing or using borgmatic, please
|
||||
use our <a href="https://torsion.org/borgmatic/#issues">issue tracker</a>
|
||||
instead.)</p>
|
||||
|
||||
<form id="suggestion-form">
|
||||
<div><label for="suggestion">Suggestion</label></div>
|
||||
<textarea id="suggestion" rows="8" cols="60" name="suggestion"></textarea>
|
||||
<div data-sk-error="suggestion" class="form-error"></div>
|
||||
<input id="_page" type="hidden" name="_page">
|
||||
<input id="_subject" type="hidden" name="_subject" value="borgmatic documentation suggestion">
|
||||
<br />
|
||||
<label for="email">Email address</label>
|
||||
<div><input id="email" type="email" name="email" placeholder="Only required if you want a response!"></div>
|
||||
<div data-sk-error="email" class="form-error"></div>
|
||||
<br />
|
||||
<div><button type="submit">Send</button></div>
|
||||
<br />
|
||||
</form>
|
||||
|
||||
<script>
|
||||
document.getElementById('_page').value = window.location.href;
|
||||
window.sk=window.sk||function(){(sk.q=sk.q||[]).push(arguments)};
|
||||
|
||||
sk('form', 'init', {
|
||||
id: '1d536680ab96',
|
||||
element: '#suggestion-form'
|
||||
});
|
||||
</script>
|
||||
|
||||
<script defer src="https://js.statickit.com/statickit.js"></script>
|
||||
@@ -11,6 +11,7 @@
|
||||
{% include 'components/minilink.css' %}
|
||||
{% include 'components/toc.css' %}
|
||||
{% include 'components/info-blocks.css' %}
|
||||
{% include 'components/suggestion-form.css' %}
|
||||
{% include 'prism-theme.css' %}
|
||||
{% include 'asciinema.css' %}
|
||||
{% endset %}
|
||||
|
||||
@@ -8,5 +8,7 @@ headerClass: elv-header-default
|
||||
<main class="elv-layout{% if layoutClass %} {{ layoutClass }}{% endif %}">
|
||||
<article>
|
||||
{{ content | safe }}
|
||||
|
||||
{% include 'components/suggestion-form.html' %}
|
||||
</article>
|
||||
</main>
|
||||
|
||||
@@ -14,7 +14,7 @@ repositories.
|
||||
|
||||
If you find yourself in this situation, you have some options. First, you can
|
||||
run borgmatic's pruning, creating, or checking actions separately. For
|
||||
instance, the the following optional flags are available:
|
||||
instance, the the following optional actions are available:
|
||||
|
||||
```bash
|
||||
borgmatic prune
|
||||
@@ -25,7 +25,7 @@ borgmatic check
|
||||
(No borgmatic `prune`, `create`, or `check` actions? Try the old-style
|
||||
`--prune`, `--create`, or `--check`. Or upgrade borgmatic!)
|
||||
|
||||
You can run with only one of these flags provided, or you can mix and match
|
||||
You can run with only one of these actions provided, or you can mix and match
|
||||
any number of them in a single borgmatic run. This supports approaches like
|
||||
making backups with `create` on a frequent schedule, while only running
|
||||
expensive consistency checks with `check` on a much less frequent basis from
|
||||
|
||||
@@ -32,7 +32,7 @@ borgmatic --stats
|
||||
|
||||
## Existing backups
|
||||
|
||||
Borgmatic provides convenient flags 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
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ borgmatic:
|
||||
* [Debian](https://tracker.debian.org/pkg/borgmatic)
|
||||
* [Ubuntu](https://launchpad.net/ubuntu/+source/borgmatic)
|
||||
* [Fedora](https://bodhi.fedoraproject.org/updates/?search=borgmatic)
|
||||
* [Arch Linux](https://aur.archlinux.org/packages/borgmatic/)
|
||||
* [Arch Linux](https://www.archlinux.org/packages/community/any/borgmatic/)
|
||||
* [OpenBSD](http://ports.su/sysutils/borgmatic)
|
||||
* [openSUSE](https://software.opensuse.org/package/borgmatic)
|
||||
* [stand-alone binary](https://github.com/cmarquardt/borgmatic-binary)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
VERSION = '1.3.23'
|
||||
VERSION = '1.3.24'
|
||||
|
||||
|
||||
setup(
|
||||
|
||||
@@ -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'])
|
||||
|
||||
|
||||
@@ -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 == '[]'
|
||||
|
||||
Reference in New Issue
Block a user