mirror of
https://github.com/borgmatic-collective/borgmatic.git
synced 2026-07-22 18:13:02 +02:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afeaa86cbf | ||
|
|
7e7c3c273e | ||
|
|
704db9c9d7 | ||
|
|
b6ab326bb4 | ||
|
|
aaf5812a3f | ||
|
|
47a6691886 | ||
|
|
596d59ef60 |
@@ -1,4 +1,6 @@
|
||||
2.1.6.dev0
|
||||
* #1256: Fix a race condition in which borgmatic sometimes swallows Borg error output without
|
||||
logging it.
|
||||
* #1300: Fix the "source_directories_must_exist" option to support source directories relative to a
|
||||
"working_directory".
|
||||
* #1301: Expand the "patterns_from" and "exclude_from" options to support paths containing tildes
|
||||
@@ -8,6 +10,11 @@
|
||||
* #1303: For the MariaDB hook, include only a subset of system data when dumping the "mysql" system
|
||||
database (or "all" databases), so the dump is actually restorable. See the documentation for more
|
||||
information: https://torsion.org/borgmatic/reference/configuration/data-sources/mariadb/
|
||||
* #1308: Update the Apprise monitoring hook's "url" option to support loading credentials with the
|
||||
"{credential ...}" syntax. See the documentation for more information:
|
||||
https://torsion.org/borgmatic/reference/configuration/credentials/
|
||||
* #1309: Add a "--quick-stats" flag and corresponding "quick_statistics" option for showing only
|
||||
abbreviated statistics for the "create" action. Borg 1.4.5+ only.
|
||||
* Update the KeePassXC credential hook to support KeePassXC's secret service integration. See the
|
||||
documentation for more information:
|
||||
https://torsion.org/borgmatic/reference/configuration/credentials/keepassxc/
|
||||
|
||||
@@ -6,7 +6,7 @@ attrs==26.1.0 # via jsonschema, referencing, -r binary_requirements.
|
||||
certifi==2026.4.22 # via apprise, requests, -r binary_requirements.in
|
||||
charset-normalizer==3.4.7 # via requests, -r binary_requirements.in
|
||||
click==8.3.3 # via apprise, -r binary_requirements.in
|
||||
idna==3.13 # via requests, -r binary_requirements.in
|
||||
idna==3.14 # via requests, -r binary_requirements.in
|
||||
jsonschema==4.26.0 # via borgmatic, -r binary_requirements.in
|
||||
jsonschema-specifications==2025.9.1 # via jsonschema, -r binary_requirements.in
|
||||
markdown==3.10.2 # via apprise, -r binary_requirements.in
|
||||
@@ -14,8 +14,8 @@ oauthlib==3.3.1 # via requests-oauthlib, -r binary_requirements.in
|
||||
packaging==26.2 # via borgmatic, -r binary_requirements.in
|
||||
pyyaml==6.0.3 # via apprise, -r binary_requirements.in
|
||||
referencing==0.37.0 # via jsonschema, jsonschema-specifications, -r binary_requirements.in
|
||||
requests==2.33.1 # via apprise, borgmatic, requests-oauthlib, -r binary_requirements.in
|
||||
requests==2.34.0 # via apprise, borgmatic, requests-oauthlib, -r binary_requirements.in
|
||||
requests-oauthlib==2.0.0 # via apprise, -r binary_requirements.in
|
||||
rpds-py==0.30.0 # via jsonschema, referencing, -r binary_requirements.in
|
||||
ruamel-yaml==0.19.1 # via borgmatic, -r binary_requirements.in
|
||||
urllib3==2.6.3 # via requests, -r binary_requirements.in
|
||||
urllib3==2.7.0 # via requests, -r binary_requirements.in
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import argparse
|
||||
import datetime
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
import borgmatic.borg.extract
|
||||
import borgmatic.borg.info
|
||||
import borgmatic.borg.repo_list
|
||||
import borgmatic.config.paths
|
||||
|
||||
@@ -100,6 +104,39 @@ def run_bootstrap(bootstrap_arguments, global_arguments, local_borg_version):
|
||||
Raise CalledProcessError or OSError if Borg could not be run.
|
||||
'''
|
||||
config = make_bootstrap_config(bootstrap_arguments)
|
||||
|
||||
if bootstrap_arguments.archive == 'latest':
|
||||
archives_data = json.loads(
|
||||
borgmatic.borg.info.display_archives_info(
|
||||
bootstrap_arguments.repository,
|
||||
config,
|
||||
local_borg_version,
|
||||
info_arguments=argparse.Namespace(archive=None, prefix=None, json=True),
|
||||
global_arguments=global_arguments,
|
||||
local_path=bootstrap_arguments.local_path,
|
||||
remote_path=bootstrap_arguments.remote_path,
|
||||
)
|
||||
)['archives']
|
||||
|
||||
get_repo_archive_format = lambda archive_data: archive_data['command_line'][-1]
|
||||
get_archive_start = lambda archive_data: datetime.datetime.fromisoformat(
|
||||
archive_data['start']
|
||||
)
|
||||
|
||||
latest_archives = {
|
||||
repo_archive_format: max(archives_data, key=get_archive_start)['name']
|
||||
for repo_archive_format, archives_data in itertools.groupby(
|
||||
sorted(archives_data, key=get_repo_archive_format),
|
||||
key=get_repo_archive_format,
|
||||
)
|
||||
if not repo_archive_format.endswith('checkpoint')
|
||||
}
|
||||
|
||||
if len(latest_archives) > 1:
|
||||
raise ValueError(
|
||||
f'The repository appears to have multiple "latest" archives, each with a different archive name format: {", ".join(sorted(latest_archives.values()))}. Please select one with --archive.'
|
||||
)
|
||||
|
||||
archive_name = borgmatic.borg.repo_list.resolve_archive_name(
|
||||
bootstrap_arguments.repository,
|
||||
bootstrap_arguments.archive,
|
||||
|
||||
@@ -374,7 +374,9 @@ def create_archive(
|
||||
|
||||
if json:
|
||||
output_log_level = None
|
||||
elif config.get('list_details') or (config.get('statistics') and not dry_run):
|
||||
elif config.get('list_details') or (
|
||||
(config.get('statistics') or config.get('quick_statistics')) and not dry_run
|
||||
):
|
||||
output_log_level = logging.ANSWER
|
||||
else:
|
||||
output_log_level = logging.INFO
|
||||
@@ -386,6 +388,11 @@ def create_archive(
|
||||
create_flags += (
|
||||
(('--info',) if logger.getEffectiveLevel() == logging.INFO and not json else ())
|
||||
+ (('--stats',) if config.get('statistics') and not json and not dry_run else ())
|
||||
+ (
|
||||
('--quick-stats',)
|
||||
if config.get('quick_statistics') and not json and not dry_run
|
||||
else ()
|
||||
)
|
||||
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) and not json else ())
|
||||
+ (('--progress',) if config.get('progress') else ())
|
||||
+ (('--json',) if json else ())
|
||||
|
||||
@@ -896,6 +896,13 @@ def make_parsers(schema, unparsed_arguments): # noqa: PLR0915
|
||||
action='store_true',
|
||||
help='Display statistics of archive',
|
||||
)
|
||||
create_group.add_argument(
|
||||
'--quick-stats',
|
||||
dest='quick_statistics',
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Display statistics of archive, skipping repository-wide "All archives" and chunk index statistics [Borg 1.4.5+ only]',
|
||||
)
|
||||
create_group.add_argument(
|
||||
'--list',
|
||||
'--files',
|
||||
|
||||
@@ -1091,6 +1091,15 @@ properties:
|
||||
Corresponds to the "--stats" flag on those actions. Defaults to
|
||||
false.
|
||||
example: true
|
||||
quick_statistics:
|
||||
type: boolean
|
||||
description: |
|
||||
Display statistics for an archive when running supported actions,
|
||||
skipping the repository-wide "All archives" and chunk index
|
||||
statistics to save some time. Corresponds to the "--quick-stats"
|
||||
flag on those actions. Defaults to false. (This option is supported
|
||||
for Borg 1.4.5+ only.)
|
||||
example: true
|
||||
list_details:
|
||||
type: boolean
|
||||
description: |
|
||||
@@ -2825,7 +2834,9 @@ properties:
|
||||
properties:
|
||||
url:
|
||||
type: string
|
||||
description: URL of this Apprise service.
|
||||
description: |
|
||||
URL of this Apprise service. Supports the
|
||||
"{credential ...}" syntax.
|
||||
example: "gotify://hostname/token"
|
||||
label:
|
||||
type: string
|
||||
|
||||
+31
-7
@@ -372,13 +372,21 @@ def log_buffer_lines(
|
||||
yield log_record.getMessage()
|
||||
|
||||
|
||||
def raise_for_process_errors(buffer_readers, process_metadatas, borg_local_path, borg_exit_codes):
|
||||
def raise_for_process_errors(
|
||||
buffer_readers,
|
||||
process_metadatas,
|
||||
output_log_level,
|
||||
borg_local_path,
|
||||
borg_exit_codes,
|
||||
capture_stderr=False,
|
||||
):
|
||||
'''
|
||||
Given a dict from buffer object to Buffer_reader, a dict from subprocess.Popen() instance to
|
||||
Process_metadata instance, Borg's local path, a sequence of exit code configuration dicts, check
|
||||
the given processes for error or warning exit codes. If found, vent or kill any running
|
||||
processes. In the case of an error exit code, raise. In the case of warning, return
|
||||
Exit_status.WARNING. Otherwise, return None.
|
||||
Process_metadata instance, a requested output log level for stdout, Borg's local path, a
|
||||
sequence of exit code configuration dicts, and whether to capture stderr, check the given
|
||||
processes for error or warning exit codes. If found, vent or kill any running processes and
|
||||
drain any remaining buffer lines. In the case of an error exit code, raise. In the case of
|
||||
warning, return Exit_status.WARNING. Otherwise, return None.
|
||||
'''
|
||||
result_status = None
|
||||
|
||||
@@ -404,6 +412,13 @@ def raise_for_process_errors(buffer_readers, process_metadatas, borg_local_path,
|
||||
result_status = Exit_status.WARNING
|
||||
continue
|
||||
|
||||
# Drain and log remaining buffer lines, so no output gets lost. But swallow captured output,
|
||||
# since we're raising below instead of yielding captured lines.
|
||||
tuple(
|
||||
log_remaining_buffer_lines(
|
||||
buffer_readers, process_metadatas, output_log_level, borg_local_path, capture_stderr
|
||||
)
|
||||
)
|
||||
last_lines = process_metadatas[process].last_lines
|
||||
|
||||
# If an error occurs, include its output in the raised exception so that we don't
|
||||
@@ -429,6 +444,9 @@ def log_remaining_buffer_lines(
|
||||
whether to capture stderr, drain and log any remaining output lines from the buffers until
|
||||
they're empty. Additionally, for any log records with a log level the same as the output log
|
||||
level, yield those log messages for capture.
|
||||
|
||||
The main difference between this function and log_buffer_lines() is that this one doesn't just
|
||||
do one "turn of the crank" of logging buffer output; it completely drains any remaining output.
|
||||
'''
|
||||
for output_buffer, reader in buffer_readers.items():
|
||||
if not reader.process:
|
||||
@@ -446,6 +464,7 @@ def log_remaining_buffer_lines(
|
||||
borg_local_path=borg_local_path,
|
||||
command=reader.process.args,
|
||||
),
|
||||
last_lines=process_metadatas[reader.process].last_lines,
|
||||
)
|
||||
|
||||
if (
|
||||
@@ -494,7 +513,7 @@ def log_outputs(
|
||||
for buffer in output_buffers_for_process(process, exclude_stdouts)
|
||||
}
|
||||
|
||||
# Log output lines for each process until they all exit.
|
||||
# Log output lines for each process until they all exit or one errors.
|
||||
while True:
|
||||
yield from log_buffer_lines(
|
||||
buffer_readers, process_metadatas, output_log_level, borg_local_path, capture_stderr
|
||||
@@ -502,7 +521,12 @@ def log_outputs(
|
||||
|
||||
if (
|
||||
raise_for_process_errors(
|
||||
buffer_readers, process_metadatas, borg_local_path, borg_exit_codes
|
||||
buffer_readers,
|
||||
process_metadatas,
|
||||
output_log_level,
|
||||
borg_local_path,
|
||||
borg_exit_codes,
|
||||
capture_stderr,
|
||||
)
|
||||
== Exit_status.WARNING
|
||||
):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import operator
|
||||
|
||||
import borgmatic.hooks.credential.parse
|
||||
import borgmatic.hooks.monitoring.logs
|
||||
import borgmatic.hooks.monitoring.monitor
|
||||
|
||||
@@ -76,7 +77,12 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||
logger.info(f'Pinging Apprise services: {labels_string}{dry_run_string}')
|
||||
|
||||
apprise_object = apprise.Apprise()
|
||||
apprise_object.add(list(map(operator.itemgetter('url'), hook_config.get('services'))))
|
||||
apprise_object.add(
|
||||
[
|
||||
borgmatic.hooks.credential.parse.resolve_credential(service['url'], config)
|
||||
for service in hook_config.get('services')
|
||||
]
|
||||
)
|
||||
|
||||
if dry_run:
|
||||
return
|
||||
|
||||
@@ -5,9 +5,9 @@ eleventyNavigation:
|
||||
parent: 🚨 Monitoring
|
||||
---
|
||||
<span class="minilink minilink-addedin">New in version 1.8.4</span>
|
||||
[Apprise](https://github.com/caronc/apprise/wiki) is a local notification library
|
||||
[Apprise](https://appriseit.com/) is a local notification library
|
||||
that "allows you to send a notification to almost all of the most popular
|
||||
[notification services](https://github.com/caronc/apprise/wiki) available to
|
||||
[notification services](https://appriseit.com/services/) available to
|
||||
us today such as: Telegram, Discord, Slack, Amazon SNS, Gotify, etc."
|
||||
|
||||
Depending on how you installed borgmatic, it may not have come with Apprise.
|
||||
@@ -22,7 +22,7 @@ sudo uv tool install borgmatic[Apprise]
|
||||
Omit `sudo` if borgmatic is installed as a non-root user.
|
||||
|
||||
Once Apprise is installed, configure borgmatic to notify one or more [Apprise
|
||||
services](https://github.com/caronc/apprise/wiki). For example:
|
||||
services](https://appriseit.com/services/). For example:
|
||||
|
||||
```yaml
|
||||
apprise:
|
||||
|
||||
+2
-1
@@ -5,13 +5,14 @@ authors = [
|
||||
{ name="Dan Helfman", email="witten@torsion.org" },
|
||||
]
|
||||
description = "Simple, configuration-driven backup software for servers and workstations"
|
||||
license = "GPL-3.0-or-later"
|
||||
license-files = ["LICENSE"]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: Console",
|
||||
"Intended Audience :: System Administrators",
|
||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||
"Programming Language :: Python",
|
||||
"Topic :: Security :: Cryptography",
|
||||
"Topic :: System :: Archiving :: Backup",
|
||||
|
||||
@@ -6,9 +6,9 @@ certifi==2026.4.22 # via apprise, requests, -r test_requirements.in
|
||||
charset-normalizer==3.4.7 # via requests, -r test_requirements.in
|
||||
click>=8.1.8
|
||||
codespell==2.4.2 # via -r test_requirements.in
|
||||
coverage==7.13.5 # via pytest-cov, -r test_requirements.in
|
||||
coverage==7.14.0 # via pytest-cov, -r test_requirements.in
|
||||
flexmock==0.13.0 # via -r test_requirements.in
|
||||
idna==3.13 # via requests, -r test_requirements.in
|
||||
idna==3.14 # via requests, -r test_requirements.in
|
||||
iniconfig==2.3.0 # via pytest, -r test_requirements.in
|
||||
jsonschema==4.26.0 # via -r test_requirements.in
|
||||
jsonschema-specifications==2025.9.1 # via jsonschema, -r test_requirements.in
|
||||
@@ -22,9 +22,9 @@ pytest-cov==7.1.0 # via -r test_requirements.in
|
||||
pytest-timeout==2.4.0 # via -r test_requirements.in
|
||||
pyyaml>5.0.0
|
||||
referencing==0.37.0 # via jsonschema, jsonschema-specifications, -r test_requirements.in
|
||||
requests==2.33.1 # via apprise, requests-oauthlib, -r test_requirements.in
|
||||
requests==2.34.0 # via apprise, requests-oauthlib, -r test_requirements.in
|
||||
requests-oauthlib==2.0.0 # via apprise, -r test_requirements.in
|
||||
rpds-py==0.30.0 # via jsonschema, referencing, -r test_requirements.in
|
||||
ruamel-yaml>0.15.0
|
||||
typing-extensions==4.15.0 # via -r test_requirements.in
|
||||
urllib3==2.6.3 # via requests, -r test_requirements.in
|
||||
urllib3==2.7.0 # via requests, -r test_requirements.in
|
||||
|
||||
@@ -557,6 +557,43 @@ def test_log_outputs_with_unfinished_process_re_polls():
|
||||
)
|
||||
|
||||
|
||||
def test_log_outputs_includes_error_output_when_output_spans_multiple_chunks():
|
||||
flexmock(module.logger).should_receive('log')
|
||||
flexmock(module).should_receive('interpret_exit_code').and_return(module.Exit_status.ERROR)
|
||||
flexmock(module).should_receive('command_for_process').and_return('python')
|
||||
|
||||
process = subprocess.Popen(
|
||||
[
|
||||
sys.executable,
|
||||
'-c',
|
||||
(
|
||||
'import os, sys; '
|
||||
f'os.write(sys.stdout.fileno(), b"x" * {module.READ_CHUNK_SIZE + 10}); '
|
||||
'os.write(sys.stdout.fileno(), b"\\nERROR: critical failure"); '
|
||||
'os.close(1); '
|
||||
'os._exit(2)'
|
||||
),
|
||||
],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
flexmock(module).should_receive('output_buffers_for_process').and_return((process.stdout,))
|
||||
|
||||
with pytest.raises(subprocess.CalledProcessError) as error:
|
||||
tuple(
|
||||
module.log_outputs(
|
||||
(process,),
|
||||
exclude_stdouts=(),
|
||||
output_log_level=logging.INFO,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
)
|
||||
)
|
||||
|
||||
assert error.value.output
|
||||
assert 'ERROR: critical failure' in error.value.output
|
||||
|
||||
|
||||
def test_read_lines_uses_system_locale_when_decoding_output():
|
||||
flexmock(module.locale).should_receive('getpreferredencoding').and_return('ISO-8859-1')
|
||||
process = subprocess.Popen(['echo', b'\xc4pple'], stdout=subprocess.PIPE)
|
||||
|
||||
@@ -1477,6 +1477,44 @@ def test_create_archive_with_stats_and_dry_run_calls_borg_without_stats():
|
||||
'source_directories': ['foo', 'bar'],
|
||||
'repositories': ['repo'],
|
||||
'exclude_patterns': None,
|
||||
'statistics': True,
|
||||
},
|
||||
patterns=[Pattern('foo'), Pattern('bar')],
|
||||
local_borg_version='1.2.3',
|
||||
global_arguments=flexmock(),
|
||||
borgmatic_runtime_directory='/borgmatic/run',
|
||||
)
|
||||
|
||||
|
||||
def test_create_archive_with_quick_stats_and_dry_run_calls_borg_without_quick_stats():
|
||||
# --dry-run and --quick-stats are mutually exclusive, see:
|
||||
# https://borgbackup.readthedocs.io/en/stable/usage/create.html#description
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
flexmock(module).should_receive('make_base_create_command').and_return(
|
||||
(('borg', 'create', '--dry-run'), REPO_ARCHIVE, flexmock()),
|
||||
)
|
||||
flexmock(module.environment).should_receive('make_environment')
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'create', '--dry-run', '--info', *REPO_ARCHIVE),
|
||||
output_log_level=logging.INFO,
|
||||
output_file=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
working_directory=None,
|
||||
environment=None,
|
||||
)
|
||||
insert_logging_mock(logging.INFO)
|
||||
|
||||
module.create_archive(
|
||||
dry_run=True,
|
||||
repository_path='repo',
|
||||
config={
|
||||
'source_directories': ['foo', 'bar'],
|
||||
'repositories': ['repo'],
|
||||
'exclude_patterns': None,
|
||||
'quick_statistics': True,
|
||||
},
|
||||
patterns=[Pattern('foo'), Pattern('bar')],
|
||||
local_borg_version='1.2.3',
|
||||
@@ -1806,6 +1844,42 @@ def test_create_archive_with_stats_and_json_calls_borg_without_stats_flag():
|
||||
'source_directories': ['foo*'],
|
||||
'repositories': ['repo'],
|
||||
'exclude_patterns': None,
|
||||
'statistics': True,
|
||||
},
|
||||
patterns=[Pattern('foo'), Pattern('bar')],
|
||||
local_borg_version='1.2.3',
|
||||
global_arguments=flexmock(),
|
||||
borgmatic_runtime_directory='/borgmatic/run',
|
||||
json=True,
|
||||
)
|
||||
|
||||
assert json_output == '[]'
|
||||
|
||||
|
||||
def test_create_archive_with_quick_stats_and_json_calls_borg_without_quick_stats_flag():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
flexmock(module).should_receive('make_base_create_command').and_return(
|
||||
(('borg', 'create'), REPO_ARCHIVE, flexmock()),
|
||||
)
|
||||
flexmock(module.environment).should_receive('make_environment')
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'create', '--json', *REPO_ARCHIVE),
|
||||
working_directory=None,
|
||||
environment=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
).and_yield('[]')
|
||||
|
||||
json_output = module.create_archive(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
config={
|
||||
'source_directories': ['foo*'],
|
||||
'repositories': ['repo'],
|
||||
'exclude_patterns': None,
|
||||
'quick_statistics': True,
|
||||
},
|
||||
patterns=[Pattern('foo'), Pattern('bar')],
|
||||
local_borg_version='1.2.3',
|
||||
|
||||
@@ -66,7 +66,9 @@ def test_initialize_monitor_without_send_logs_does_not_add_handler():
|
||||
|
||||
|
||||
def test_ping_monitor_respects_dry_run():
|
||||
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive('get_handler')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda url, config: url)
|
||||
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
|
||||
'format_buffered_logs_for_payload',
|
||||
).and_return('loggy log')
|
||||
@@ -83,7 +85,9 @@ def test_ping_monitor_respects_dry_run():
|
||||
|
||||
|
||||
def test_ping_monitor_with_no_states_does_not_notify():
|
||||
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive('get_handler').never()
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda url, config: url)
|
||||
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
|
||||
'format_buffered_logs_for_payload',
|
||||
).never()
|
||||
@@ -100,7 +104,9 @@ def test_ping_monitor_with_no_states_does_not_notify():
|
||||
|
||||
|
||||
def test_ping_monitor_notifies_fail_by_default():
|
||||
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive('get_handler')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda url, config: url)
|
||||
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
|
||||
'format_buffered_logs_for_payload',
|
||||
).and_return('')
|
||||
@@ -123,7 +129,9 @@ def test_ping_monitor_notifies_fail_by_default():
|
||||
|
||||
|
||||
def test_ping_monitor_with_logs_appends_logs_to_body():
|
||||
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive('get_handler')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda url, config: url)
|
||||
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
|
||||
'format_buffered_logs_for_payload',
|
||||
).and_return('loggy log')
|
||||
@@ -146,7 +154,9 @@ def test_ping_monitor_with_logs_appends_logs_to_body():
|
||||
|
||||
|
||||
def test_ping_monitor_with_finish_default_config_notifies():
|
||||
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive('get_handler')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda url, config: url)
|
||||
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
|
||||
'format_buffered_logs_for_payload',
|
||||
).and_return('')
|
||||
|
||||
@@ -779,6 +779,7 @@ def test_raise_for_process_errors_with_no_processes_bails():
|
||||
module.raise_for_process_errors(
|
||||
buffer_readers,
|
||||
process_metadatas={},
|
||||
output_log_level=None,
|
||||
borg_local_path=flexmock(),
|
||||
borg_exit_codes=flexmock(),
|
||||
)
|
||||
@@ -795,6 +796,7 @@ def test_raise_for_process_errors_with_running_process_bails():
|
||||
module.raise_for_process_errors(
|
||||
buffer_readers,
|
||||
process_metadatas,
|
||||
output_log_level=None,
|
||||
borg_local_path=flexmock(),
|
||||
borg_exit_codes=flexmock(),
|
||||
)
|
||||
@@ -812,6 +814,7 @@ def test_raise_for_process_errors_with_running_process_and_no_buffer_readers_wai
|
||||
module.raise_for_process_errors(
|
||||
buffer_readers={},
|
||||
process_metadatas=process_metadatas,
|
||||
output_log_level=None,
|
||||
borg_local_path=flexmock(),
|
||||
borg_exit_codes=flexmock(),
|
||||
)
|
||||
@@ -829,6 +832,7 @@ def test_raise_for_process_errors_with_successful_process_bails():
|
||||
module.raise_for_process_errors(
|
||||
buffer_readers,
|
||||
process_metadatas,
|
||||
output_log_level=None,
|
||||
borg_local_path=flexmock(),
|
||||
borg_exit_codes=flexmock(),
|
||||
)
|
||||
@@ -846,6 +850,7 @@ def test_raise_for_process_errors_with_warning_process_returns_warning_status():
|
||||
module.raise_for_process_errors(
|
||||
buffer_readers,
|
||||
process_metadatas,
|
||||
output_log_level=None,
|
||||
borg_local_path=flexmock(),
|
||||
borg_exit_codes=flexmock(),
|
||||
)
|
||||
@@ -860,6 +865,7 @@ def test_raise_for_process_errors_with_error_process_raises():
|
||||
process: module.Process_metadata(last_lines=['hi', 'there'], capture=False)
|
||||
}
|
||||
flexmock(module).should_receive('interpret_exit_code').and_return(module.Exit_status.ERROR)
|
||||
flexmock(module).should_receive('log_remaining_buffer_lines').and_return(())
|
||||
command = flexmock()
|
||||
flexmock(module).should_receive('command_for_process').and_return(command)
|
||||
|
||||
@@ -867,6 +873,7 @@ def test_raise_for_process_errors_with_error_process_raises():
|
||||
module.raise_for_process_errors(
|
||||
buffer_readers,
|
||||
process_metadatas,
|
||||
output_log_level=None,
|
||||
borg_local_path=flexmock(),
|
||||
borg_exit_codes=flexmock(),
|
||||
)
|
||||
@@ -890,12 +897,14 @@ def test_raise_for_process_errors_with_success_process_and_warning_process_retur
|
||||
flexmock(module).should_receive('interpret_exit_code').with_args(
|
||||
object, 1, object, object
|
||||
).and_return(module.Exit_status.WARNING)
|
||||
flexmock(module).should_receive('log_remaining_buffer_lines').and_return(())
|
||||
flexmock(module).should_receive('command_for_process').never()
|
||||
|
||||
assert (
|
||||
module.raise_for_process_errors(
|
||||
buffer_readers,
|
||||
process_metadatas,
|
||||
output_log_level=None,
|
||||
borg_local_path=flexmock(),
|
||||
borg_exit_codes=flexmock(),
|
||||
)
|
||||
@@ -917,6 +926,7 @@ def test_raise_for_process_errors_with_warning_process_and_error_process_raises(
|
||||
flexmock(module).should_receive('interpret_exit_code').with_args(
|
||||
object, 3, object, object
|
||||
).and_return(module.Exit_status.ERROR)
|
||||
flexmock(module).should_receive('log_remaining_buffer_lines').and_return(())
|
||||
command = flexmock()
|
||||
flexmock(module).should_receive('command_for_process').and_return(command)
|
||||
|
||||
@@ -924,6 +934,7 @@ def test_raise_for_process_errors_with_warning_process_and_error_process_raises(
|
||||
module.raise_for_process_errors(
|
||||
buffer_readers,
|
||||
process_metadatas,
|
||||
output_log_level=None,
|
||||
borg_local_path=flexmock(),
|
||||
borg_exit_codes=flexmock(),
|
||||
)
|
||||
@@ -947,6 +958,7 @@ def test_raise_for_process_errors_with_warning_process_and_running_process_kills
|
||||
flexmock(module).should_receive('interpret_exit_code').with_args(
|
||||
object, 1, object, object
|
||||
).and_return(module.Exit_status.WARNING)
|
||||
flexmock(module).should_receive('log_remaining_buffer_lines').and_return(())
|
||||
command = flexmock()
|
||||
flexmock(module).should_receive('command_for_process').and_return(command)
|
||||
|
||||
@@ -954,6 +966,7 @@ def test_raise_for_process_errors_with_warning_process_and_running_process_kills
|
||||
module.raise_for_process_errors(
|
||||
buffer_readers,
|
||||
process_metadatas,
|
||||
output_log_level=None,
|
||||
borg_local_path=flexmock(),
|
||||
borg_exit_codes=flexmock(),
|
||||
)
|
||||
@@ -975,6 +988,7 @@ def test_raise_for_process_errors_with_error_process_and_running_process_kills_a
|
||||
flexmock(module).should_receive('interpret_exit_code').with_args(
|
||||
object, 3, object, object
|
||||
).and_return(module.Exit_status.ERROR)
|
||||
flexmock(module).should_receive('log_remaining_buffer_lines').and_return(())
|
||||
command = flexmock()
|
||||
flexmock(module).should_receive('command_for_process').and_return(command)
|
||||
|
||||
@@ -982,6 +996,7 @@ def test_raise_for_process_errors_with_error_process_and_running_process_kills_a
|
||||
module.raise_for_process_errors(
|
||||
buffer_readers,
|
||||
process_metadatas,
|
||||
output_log_level=None,
|
||||
borg_local_path=flexmock(),
|
||||
borg_exit_codes=flexmock(),
|
||||
)
|
||||
@@ -998,6 +1013,7 @@ def test_raise_for_process_errors_with_warning_process_and_long_output_raises_wi
|
||||
process: module.Process_metadata(last_lines=['hi', 'there'], capture=False)
|
||||
}
|
||||
flexmock(module).should_receive('interpret_exit_code').and_return(module.Exit_status.ERROR)
|
||||
flexmock(module).should_receive('log_remaining_buffer_lines').and_return(())
|
||||
command = flexmock()
|
||||
flexmock(module).should_receive('command_for_process').and_return(command)
|
||||
|
||||
@@ -1005,6 +1021,7 @@ def test_raise_for_process_errors_with_warning_process_and_long_output_raises_wi
|
||||
flexmock(module, ERROR_OUTPUT_MAX_LINE_COUNT=2).raise_for_process_errors(
|
||||
buffer_readers,
|
||||
process_metadatas,
|
||||
output_log_level=None,
|
||||
borg_local_path=flexmock(),
|
||||
borg_exit_codes=flexmock(),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user