Compare commits

...
13 Commits
Author SHA1 Message Date
Dan Helfman fbdb09b87d Bump version for release. 2025-03-10 10:17:36 -07:00
Dan Helfman bec5a0c0ca Fix end-to-end tests for Btrfs (#1023). 2025-03-10 10:15:23 -07:00
Dan Helfman 4ee7f72696 Fix an error in the Btrfs hook when attempting to snapshot a read-only subvolume (#1023). 2025-03-09 23:04:55 -07:00
Dan Helfman 68b6d01071 Fix a regression in which the "exclude_patterns" option didn't expand "~" (#1021). 2025-03-09 13:35:22 -07:00
Dan Helfman 86b138e73b Clarify command hook documentation. 2025-03-08 21:00:58 -08:00
Dan Helfman c76a108422 Link to Zabbix documentation from NEWS. 2025-03-06 10:37:00 -08:00
Dan Helfman eb5dc128bf Fix incorrect test name (#1017). 2025-03-06 10:34:28 -08:00
Dan Helfman 1d486d024b Fix a regression in which some MariaDB/MySQL passwords were not escaped correctly (#1017). 2025-03-06 10:32:38 -08:00
Dan Helfman 5a8f27d75c Add single quotes around the MariaDB password (#1017).
Reviewed-on: https://projects.torsion.org/borgmatic-collective/borgmatic/pulls/1017
2025-03-06 18:01:43 +00:00
Geoff Holden a926b413bc Updating automated test, and fixing linting errors. 2025-03-06 09:00:33 -03:30
Geoff Holden 18ffd96d62 Add single quotes around the password.
When the DB password uses some special characters, the
defaults-extra-file can be incorrect. In the case of a password with
the # symbol, anything after that is considered a comment. The single
quotes around the password rectify this.
2025-03-05 22:51:41 -03:30
Dan Helfman c0135864c2 With the PagerDuty monitoring hook, send borgmatic logs to PagerDuty so they show up in the incident UI (#409). 2025-03-04 08:55:09 -08:00
Dan Helfman ddfd3c6ca1 Clarify Zabbix monitoring hook documentation about creating items (#936). 2025-03-03 16:02:22 -08:00
16 changed files with 321 additions and 41 deletions
+12
View File
@@ -1,3 +1,15 @@
1.9.14
* #409: With the PagerDuty monitoring hook, send borgmatic logs to PagerDuty so they show up in the
incident UI. See the documentation for more information:
https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#pagerduty-hook
* #936: Clarify Zabbix monitoring hook documentation about creating items:
https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#zabbix-hook
* #1017: Fix a regression in which some MariaDB/MySQL passwords were not escaped correctly.
* #1021: Fix a regression in which the "exclude_patterns" option didn't expand "~" (the user's
home directory). This fix means that all "patterns" and "patterns_from" also now expand "~".
* #1023: Fix an error in the Btrfs hook when attempting to snapshot a read-only subvolume. Now,
read-only subvolumes are ignored since Btrfs can't actually snapshot them.
1.9.13
* #975: Add a "compression" option to the PostgreSQL database hook.
* #1001: Fix a ZFS error during snapshot cleanup.
+14 -3
View File
@@ -130,8 +130,11 @@ def expand_directory(directory, working_directory):
def expand_patterns(patterns, working_directory=None, skip_paths=None):
'''
Given a sequence of borgmatic.borg.pattern.Pattern instances and an optional working directory,
expand tildes and globs in each root pattern. Return all the resulting patterns (not just the
root patterns) as a tuple.
expand tildes and globs in each root pattern and expand just tildes in each non-root pattern.
The idea is that non-root patterns may be regular expressions or other pattern styles containing
"*" that borgmatic should not expand as a shell glob.
Return all the resulting patterns as a tuple.
If a set of paths are given to skip, then don't expand any patterns matching them.
'''
@@ -153,7 +156,15 @@ def expand_patterns(patterns, working_directory=None, skip_paths=None):
)
if pattern.type == borgmatic.borg.pattern.Pattern_type.ROOT
and pattern.path not in (skip_paths or ())
else (pattern,)
else (
borgmatic.borg.pattern.Pattern(
os.path.expanduser(pattern.path),
pattern.type,
pattern.style,
pattern.device,
pattern.source,
),
)
)
for pattern in patterns
)
+6
View File
@@ -2279,6 +2279,12 @@ properties:
PagerDuty integration key used to notify PagerDuty when a
backup errors. Supports the "{credential ...}" syntax.
example: a177cad45bd374409f78906a810a3074
send_logs:
type: boolean
description: |
Send borgmatic logs to PagerDuty when a backup errors.
Defaults to true.
example: false
description: |
Configuration for a monitoring integration with PagerDuty. Create an
account at https://www.pagerduty.com if you'd like to use this
+46 -1
View File
@@ -48,6 +48,47 @@ def get_subvolume_mount_points(findmnt_command):
Subvolume = collections.namedtuple('Subvolume', ('path', 'contained_patterns'), defaults=((),))
def get_subvolume_property(btrfs_command, subvolume_path, property_name):
output = borgmatic.execute.execute_command_and_capture_output(
tuple(btrfs_command.split(' '))
+ (
'property',
'get',
'-t', # Type.
'subvol',
subvolume_path,
property_name,
),
)
try:
value = output.strip().split('=')[1]
except IndexError:
raise ValueError(f'Invalid {btrfs_command} property output')
return {
'true': True,
'false': False,
}.get(value, value)
def omit_read_only_subvolume_mount_points(btrfs_command, subvolume_paths):
'''
Given a Btrfs command to run and a sequence of Btrfs subvolume mount points, filter them down to
just those that are read-write. The idea is that Btrfs can't actually snapshot a read-only
subvolume, so we should just ignore them.
'''
retained_subvolume_paths = []
for subvolume_path in subvolume_paths:
if get_subvolume_property(btrfs_command, subvolume_path, 'ro'):
logger.debug(f'Ignoring Btrfs subvolume {subvolume_path} because it is read-only')
else:
retained_subvolume_paths.append(subvolume_path)
return tuple(retained_subvolume_paths)
def get_subvolumes(btrfs_command, findmnt_command, patterns=None):
'''
Given a Btrfs command to run and a sequence of configured patterns, find the intersection
@@ -67,7 +108,11 @@ def get_subvolumes(btrfs_command, findmnt_command, patterns=None):
# backup. Sort the subvolumes from longest to shortest mount points, so longer mount points get
# a whack at the candidate pattern piñata before their parents do. (Patterns are consumed during
# this process, so no two subvolumes end up with the same contained patterns.)
for mount_point in reversed(get_subvolume_mount_points(findmnt_command)):
for mount_point in reversed(
omit_read_only_subvolume_mount_points(
btrfs_command, get_subvolume_mount_points(findmnt_command)
)
):
subvolumes.extend(
Subvolume(mount_point, contained_patterns)
for contained_patterns in (
+3 -1
View File
@@ -65,10 +65,12 @@ def make_defaults_file_options(username=None, password=None, defaults_extra_file
Do not use the returned value for multiple different command invocations. That will not work
because each pipe is "used up" once read.
'''
escaped_password = None if password is None else password.replace('\\', '\\\\')
values = '\n'.join(
(
(f'user={username}' if username is not None else ''),
(f'password={password}' if password is not None else ''),
(f'password="{escaped_password}"' if escaped_password is not None else ''),
)
).strip()
+1 -1
View File
@@ -64,7 +64,7 @@ def get_handler(identifier):
def format_buffered_logs_for_payload(identifier):
'''
Get the handler previously added to the root logger, and slurp buffered logs out of it to
send to Healthchecks.
send to the monitoring service.
'''
try:
buffering_handler = get_handler(identifier)
+32 -11
View File
@@ -6,20 +6,36 @@ import platform
import requests
import borgmatic.hooks.credential.parse
import borgmatic.hooks.monitoring.logs
from borgmatic.hooks.monitoring import monitor
logger = logging.getLogger(__name__)
EVENTS_API_URL = 'https://events.pagerduty.com/v2/enqueue'
DEFAULT_LOGS_PAYLOAD_LIMIT_BYTES = 10000
HANDLER_IDENTIFIER = 'pagerduty'
def initialize_monitor(
integration_key, config, config_filename, monitoring_log_level, dry_run
): # pragma: no cover
def initialize_monitor(hook_config, config, config_filename, monitoring_log_level, dry_run):
'''
No initialization is necessary for this monitor.
Add a handler to the root logger that stores in memory the most recent logs emitted. That way,
we can send them all to PagerDuty upon a failure state. But skip this if the "send_logs" option
is false.
'''
pass
if hook_config.get('send_logs') is False:
return
ping_body_limit = max(
DEFAULT_LOGS_PAYLOAD_LIMIT_BYTES
- len(borgmatic.hooks.monitoring.logs.PAYLOAD_TRUNCATION_INDICATOR),
0,
)
borgmatic.hooks.monitoring.logs.add_handler(
borgmatic.hooks.monitoring.logs.Forgetful_buffering_handler(
HANDLER_IDENTIFIER, ping_body_limit, monitoring_log_level
)
)
def ping_monitor(hook_config, config, config_filename, state, monitoring_log_level, dry_run):
@@ -37,9 +53,6 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
dry_run_label = ' (dry run; not actually sending)' if dry_run else ''
logger.info(f'Sending failure event to PagerDuty {dry_run_label}')
if dry_run:
return
try:
integration_key = borgmatic.hooks.credential.parse.resolve_credential(
hook_config.get('integration_key'), config
@@ -48,6 +61,10 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
logger.warning(f'PagerDuty credential error: {error}')
return
logs_payload = borgmatic.hooks.monitoring.logs.format_buffered_logs_for_payload(
HANDLER_IDENTIFIER
)
hostname = platform.node()
local_timestamp = datetime.datetime.now(datetime.timezone.utc).astimezone().isoformat()
payload = json.dumps(
@@ -66,11 +83,14 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
'hostname': hostname,
'configuration filename': config_filename,
'server time': local_timestamp,
'logs': logs_payload,
},
},
}
)
logger.debug(f'Using PagerDuty payload: {payload}')
if dry_run:
return
logging.getLogger('urllib3').setLevel(logging.ERROR)
try:
@@ -83,6 +103,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
def destroy_monitor(ping_url_or_uuid, config, monitoring_log_level, dry_run): # pragma: no cover
'''
No destruction is necessary for this monitor.
Remove the monitor handler that was added to the root logger. This prevents the handler from
getting reused by other instances of this monitor.
'''
pass
borgmatic.hooks.monitoring.logs.remove_handler(HANDLER_IDENTIFIER)
@@ -7,12 +7,13 @@ eleventyNavigation:
---
## Preparation and cleanup hooks
If you find yourself performing preparation tasks before your backup runs, or
cleanup work afterwards, borgmatic hooks may be of interest. Hooks are shell
commands that borgmatic executes for you at various points as it runs, and
they're configured in the `hooks` section of your configuration file. But if
you're looking to backup a database, it's probably easier to use the [database
backup
If you find yourself performing preparation tasks before your backup runs or
cleanup work afterwards, borgmatic command hooks may be of interest. These are
custom shell commands you can configure borgmatic to execute at various points
as it runs.
But if you're looking to backup a database, it's probably easier to use the
[database backup
feature](https://torsion.org/borgmatic/docs/how-to/backup-your-databases/)
instead.
+33 -4
View File
@@ -292,6 +292,27 @@ If you have any issues with the integration, [please contact
us](https://torsion.org/borgmatic/#support-and-contributing).
### Sending logs
<span class="minilink minilink-addedin">New in version 1.9.14</span> borgmatic
logs are included in the payload data sent to PagerDuty. This means that
(truncated) borgmatic logs, including error messages, show up in the PagerDuty
incident UI and corresponding notification emails.
You can customize the verbosity of the logs that are sent with borgmatic's
`--monitoring-verbosity` flag. The `--list` and `--stats` flags may also be of
use. See `borgmatic create --help` for more information.
If you don't want any logs sent, you can disable this feature by setting
`send_logs` to `false`:
```yaml
pagerduty:
integration_key: a177cad45bd374409f78906a810a3074
send_logs: false
```
## Pushover hook
<span class="minilink minilink-addedin">New in version 1.9.2</span>
@@ -724,11 +745,19 @@ Authentication can be accomplished via `api_key` or both `username` and
### Items
The item to be updated can be chosen by either declaring the `itemid` or both
`host` and `key`. If all three are declared, only `itemid` is used.
borgmatic writes its monitoring updates to a particular Zabbix item, which
you'll need to create in advance. In the Zabbix web UI, [make a new item with a
Type of "Zabbix
trapper"](https://www.zabbix.com/documentation/current/en/manual/config/items/itemtypes/trapper)
and a named Key. The "Type of information" for the item should be "Text", and
"History" designates how much data you want to retain.
Keep in mind that `host` is referring to the "Host name" on the Zabbix server
and not the "Visual name".
When configuring borgmatic with this item to be updated, you can either declare
the `itemid` or both `host` and `key`. If all three are declared, only `itemid`
is used.
Keep in mind that `host` refers to the "Host name" on the Zabbix server and not
the "Visual name".
## Scripting borgmatic
+3 -2
View File
@@ -148,8 +148,9 @@ feedback](https://torsion.org/borgmatic/#issues) you have on this feature.
#### Subvolume discovery
For any subvolume you'd like backed up, add its path to borgmatic's
`source_directories` option.
For any read-write subvolume you'd like backed up, add its path to borgmatic's
`source_directories` option. Btrfs does not support snapshotting read-only
subvolumes.
<span class="minilink minilink-addedin">New in version 1.9.6</span> Or include
the mount point as a root pattern with borgmatic's `patterns` or `patterns_from`
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "borgmatic"
version = "1.9.13"
version = "1.9.14"
authors = [
{ name="Dan Helfman", email="witten@torsion.org" },
]
+15 -2
View File
@@ -24,7 +24,14 @@ def parse_arguments(*unparsed_arguments):
delete_parser = subvolume_subparser.add_parser('delete')
delete_parser.add_argument('snapshot_path')
return global_parser.parse_args(unparsed_arguments)
property_parser = action_parsers.add_parser('property')
property_subparser = property_parser.add_subparsers(dest='subaction')
get_parser = property_subparser.add_parser('get')
get_parser.add_argument('-t', dest='type')
get_parser.add_argument('subvolume_path')
get_parser.add_argument('property_name')
return (global_parser, global_parser.parse_args(unparsed_arguments))
BUILTIN_SUBVOLUME_LIST_LINES = (
@@ -60,9 +67,13 @@ def print_subvolume_list(arguments, snapshot_paths):
def main():
arguments = parse_arguments(*sys.argv[1:])
(global_parser, arguments) = parse_arguments(*sys.argv[1:])
snapshot_paths = load_snapshots()
if not hasattr(arguments, 'subaction'):
global_parser.print_help()
sys.exit(1)
if arguments.subaction == 'list':
print_subvolume_list(arguments, snapshot_paths)
elif arguments.subaction == 'snapshot':
@@ -84,6 +95,8 @@ def main():
if snapshot_path.endswith('/' + arguments.snapshot_path)
]
save_snapshots(snapshot_paths)
elif arguments.action == 'property' and arguments.subaction == 'get':
print(f'{arguments.property_name}=false')
if __name__ == '__main__':
+15 -6
View File
@@ -225,15 +225,24 @@ def test_expand_patterns_considers_none_as_no_patterns():
assert module.expand_patterns(None) == ()
def test_expand_patterns_only_considers_root_patterns():
flexmock(module).should_receive('expand_directory').with_args('~/foo', None).and_return(
['/root/foo']
def test_expand_patterns_expands_tildes_and_globs_in_root_patterns():
flexmock(module.os.path).should_receive('expanduser').never()
flexmock(module).should_receive('expand_directory').and_return(
['/root/foo/one', '/root/foo/two']
)
flexmock(module).should_receive('expand_directory').with_args('bar*', None).never()
paths = module.expand_patterns((Pattern('~/foo'), Pattern('bar*', Pattern_type.INCLUDE)))
paths = module.expand_patterns((Pattern('~/foo/*'),))
assert paths == (Pattern('/root/foo'), Pattern('bar*', Pattern_type.INCLUDE))
assert paths == (Pattern('/root/foo/one'), Pattern('/root/foo/two'))
def test_expand_patterns_expands_only_tildes_in_non_root_patterns():
flexmock(module).should_receive('expand_directory').never()
flexmock(module.os.path).should_receive('expanduser').and_return('/root/bar/*')
paths = module.expand_patterns((Pattern('~/bar/*', Pattern_type.INCLUDE),))
assert paths == (Pattern('/root/bar/*', Pattern_type.INCLUDE),)
def test_device_map_patterns_gives_device_id_per_path():
@@ -49,8 +49,61 @@ def test_get_subvolume_mount_points_with_findmnt_json_missing_filesystems_errors
module.get_subvolume_mount_points('findmnt')
def test_get_subvolume_property_with_invalid_btrfs_output_errors():
flexmock(module.borgmatic.execute).should_receive(
'execute_command_and_capture_output'
).and_return('invalid')
with pytest.raises(ValueError):
module.get_subvolume_property('btrfs', '/foo', 'ro')
def test_get_subvolume_property_with_true_output_returns_true_bool():
flexmock(module.borgmatic.execute).should_receive(
'execute_command_and_capture_output'
).and_return('ro=true')
assert module.get_subvolume_property('btrfs', '/foo', 'ro') is True
def test_get_subvolume_property_with_false_output_returns_false_bool():
flexmock(module.borgmatic.execute).should_receive(
'execute_command_and_capture_output'
).and_return('ro=false')
assert module.get_subvolume_property('btrfs', '/foo', 'ro') is False
def test_get_subvolume_property_passes_through_general_value():
flexmock(module.borgmatic.execute).should_receive(
'execute_command_and_capture_output'
).and_return('thing=value')
assert module.get_subvolume_property('btrfs', '/foo', 'thing') == 'value'
def test_omit_read_only_subvolume_mount_points_filters_out_read_only():
flexmock(module).should_receive('get_subvolume_property').with_args(
'btrfs', '/foo', 'ro'
).and_return(False)
flexmock(module).should_receive('get_subvolume_property').with_args(
'btrfs', '/bar', 'ro'
).and_return(True)
flexmock(module).should_receive('get_subvolume_property').with_args(
'btrfs', '/baz', 'ro'
).and_return(False)
assert module.omit_read_only_subvolume_mount_points('btrfs', ('/foo', '/bar', '/baz')) == (
'/foo',
'/baz',
)
def test_get_subvolumes_collects_subvolumes_matching_patterns():
flexmock(module).should_receive('get_subvolume_mount_points').and_return(('/mnt1', '/mnt2'))
flexmock(module).should_receive('omit_read_only_subvolume_mount_points').and_return(
('/mnt1', '/mnt2')
)
contained_pattern = Pattern(
'/mnt1',
@@ -76,6 +129,9 @@ def test_get_subvolumes_collects_subvolumes_matching_patterns():
def test_get_subvolumes_skips_non_root_patterns():
flexmock(module).should_receive('get_subvolume_mount_points').and_return(('/mnt1', '/mnt2'))
flexmock(module).should_receive('omit_read_only_subvolume_mount_points').and_return(
('/mnt1', '/mnt2')
)
flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
'get_contained_patterns'
@@ -107,6 +163,9 @@ def test_get_subvolumes_skips_non_root_patterns():
def test_get_subvolumes_skips_non_config_patterns():
flexmock(module).should_receive('get_subvolume_mount_points').and_return(('/mnt1', '/mnt2'))
flexmock(module).should_receive('omit_read_only_subvolume_mount_points').and_return(
('/mnt1', '/mnt2')
)
flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
'get_contained_patterns'
@@ -138,6 +197,9 @@ def test_get_subvolumes_skips_non_config_patterns():
def test_get_subvolumes_without_patterns_collects_all_subvolumes():
flexmock(module).should_receive('get_subvolume_mount_points').and_return(('/mnt1', '/mnt2'))
flexmock(module).should_receive('omit_read_only_subvolume_mount_points').and_return(
('/mnt1', '/mnt2')
)
flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
'get_contained_patterns'
+19 -3
View File
@@ -36,7 +36,7 @@ def test_make_defaults_file_option_with_username_and_password_writes_them_to_fil
flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
flexmock(module.os).should_receive('write').with_args(
write_descriptor, b'[client]\nuser=root\npassword=trustsome1'
write_descriptor, b'[client]\nuser=root\npassword="trustsome1"'
).once()
flexmock(module.os).should_receive('close')
flexmock(module.os).should_receive('set_inheritable')
@@ -46,6 +46,22 @@ def test_make_defaults_file_option_with_username_and_password_writes_them_to_fil
)
def test_make_defaults_file_escapes_password_containing_backslash():
read_descriptor = 99
write_descriptor = flexmock()
flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
flexmock(module.os).should_receive('write').with_args(
write_descriptor, b'[client]\nuser=root\n' + br'password="trust\\nsome1"'
).once()
flexmock(module.os).should_receive('close')
flexmock(module.os).should_receive('set_inheritable')
assert module.make_defaults_file_options(username='root', password=r'trust\nsome1') == (
'--defaults-extra-file=/dev/fd/99',
)
def test_make_defaults_file_pipe_with_only_username_writes_it_to_file_descriptor():
read_descriptor = 99
write_descriptor = flexmock()
@@ -68,7 +84,7 @@ def test_make_defaults_file_pipe_with_only_password_writes_it_to_file_descriptor
flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
flexmock(module.os).should_receive('write').with_args(
write_descriptor, b'[client]\npassword=trustsome1'
write_descriptor, b'[client]\npassword="trustsome1"'
).once()
flexmock(module.os).should_receive('close')
flexmock(module.os).should_receive('set_inheritable')
@@ -84,7 +100,7 @@ def test_make_defaults_file_option_with_defaults_extra_filename_includes_it_in_f
flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
flexmock(module.os).should_receive('write').with_args(
write_descriptor, b'!include extra.cnf\n[client]\nuser=root\npassword=trustsome1'
write_descriptor, b'!include extra.cnf\n[client]\nuser=root\npassword="trustsome1"'
).once()
flexmock(module.os).should_receive('close')
flexmock(module.os).should_receive('set_inheritable')
@@ -3,6 +3,46 @@ from flexmock import flexmock
from borgmatic.hooks.monitoring import pagerduty as module
def mock_logger():
logger = flexmock()
logger.should_receive('addHandler')
logger.should_receive('removeHandler')
flexmock(module.logging).should_receive('getLogger').and_return(logger)
def test_initialize_monitor_creates_log_handler():
monitoring_log_level = 1
mock_logger()
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
'Forgetful_buffering_handler'
).once()
module.initialize_monitor({}, {}, 'test.yaml', monitoring_log_level, dry_run=False)
def test_initialize_monitor_creates_log_handler_when_send_logs_true():
mock_logger()
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
'Forgetful_buffering_handler'
).once()
module.initialize_monitor(
{'send_logs': True}, {}, 'test.yaml', monitoring_log_level=1, dry_run=False
)
def test_initialize_monitor_bails_when_send_logs_false():
mock_logger()
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
'Forgetful_buffering_handler'
).never()
module.initialize_monitor(
{'send_logs': False}, {}, 'test.yaml', monitoring_log_level=1, dry_run=False
)
def test_ping_monitor_ignores_start_state():
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
'resolve_credential'
@@ -39,6 +79,9 @@ def test_ping_monitor_calls_api_for_fail_state():
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
'resolve_credential'
).replace_with(lambda value, config: value)
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
'format_buffered_logs_for_payload'
).and_return('loggy\nlogs')
flexmock(module.requests).should_receive('post').and_return(flexmock(ok=True))
module.ping_monitor(
@@ -55,6 +98,9 @@ def test_ping_monitor_dry_run_does_not_call_api():
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
'resolve_credential'
).replace_with(lambda value, config: value)
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
'format_buffered_logs_for_payload'
).and_return('loggy\nlogs')
flexmock(module.requests).should_receive('post').never()
module.ping_monitor(
@@ -71,6 +117,9 @@ def test_ping_monitor_with_connection_error_logs_warning():
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
'resolve_credential'
).replace_with(lambda value, config: value)
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
'format_buffered_logs_for_payload'
).and_return('loggy\nlogs')
flexmock(module.requests).should_receive('post').and_raise(
module.requests.exceptions.ConnectionError
)
@@ -108,6 +157,9 @@ def test_ping_monitor_with_other_error_logs_warning():
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
'resolve_credential'
).replace_with(lambda value, config: value)
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
'format_buffered_logs_for_payload'
).and_return('loggy\nlogs')
response.should_receive('raise_for_status').and_raise(
module.requests.exceptions.RequestException
)