Fix "--json" error when Borg includes non-JSON warnings in JSON output (#847).

This commit is contained in:
Dan Helfman 2024-04-05 12:23:50 -07:00
parent f21a2c06e3
commit ad7dcb4615
3 changed files with 30 additions and 7 deletions

1
NEWS
View file

@ -3,6 +3,7 @@
configured monitoring hooks. configured monitoring hooks.
* #843: Add documentation link to Loki dashboard for borgmatic: * #843: Add documentation link to Loki dashboard for borgmatic:
https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#loki-hook https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#loki-hook
* #847: Fix "--json" error when Borg includes non-JSON warnings in JSON output.
* Fix handling of the NO_COLOR environment variable to ignore an empty value. * Fix handling of the NO_COLOR environment variable to ignore an empty value.
* Add documentation about backing up containerized databases by configuring borgmatic to exec into * Add documentation about backing up containerized databases by configuring borgmatic to exec into
a container to run a dump command: a container to run a dump command:

View file

@ -1,12 +1,27 @@
import logging
import json import json
logger = logging.getLogger(__name__)
def parse_json(borg_json_output, label): def parse_json(borg_json_output, label):
''' '''
Given a Borg JSON output string, parse it as JSON into a dict. Inject the given borgmatic Given a Borg JSON output string, parse it as JSON into a dict. Inject the given borgmatic
repository label into it and return the dict. repository label into it and return the dict.
Raise JSONDecodeError if the JSON output cannot be parsed.
''' '''
json_data = json.loads(borg_json_output) lines = borg_json_output.splitlines()
start_line_index = 0
# Scan forward to find the first line starting with "{" and assume that's where the JSON starts.
for line_index, line in enumerate(lines):
if line.startswith('{'):
start_line_index = line_index
break
json_data = json.loads('\n'.join(lines[start_line_index:]))
if 'repository' not in json_data: if 'repository' not in json_data:
return json_data return json_data

View file

@ -1,25 +1,32 @@
import pytest
from flexmock import flexmock from flexmock import flexmock
from borgmatic.actions import json as module from borgmatic.actions import json as module
def test_parse_json_loads_json_from_string(): def test_parse_json_loads_json_from_string():
flexmock(module.json).should_receive('loads').and_return({'repository': {'id': 'foo'}})
assert module.parse_json('{"repository": {"id": "foo"}}', label=None) == { assert module.parse_json('{"repository": {"id": "foo"}}', label=None) == {
'repository': {'id': 'foo', 'label': ''} 'repository': {'id': 'foo', 'label': ''}
} }
def test_parse_json_injects_label_into_parsed_data(): def test_parse_json_skips_non_json_warnings_and_loads_subsequent_json():
flexmock(module.json).should_receive('loads').and_return({'repository': {'id': 'foo'}}) assert module.parse_json(
'/non/existent/path: stat: [Errno 2] No such file or directory: /non/existent/path\n{"repository":\n{"id": "foo"}}',
label=None,
) == {'repository': {'id': 'foo', 'label': ''}}
def test_parse_json_skips_with_invalid_json_raises():
with pytest.raises(module.json.JSONDecodeError):
module.parse_json('this is not valid JSON }', label=None)
def test_parse_json_injects_label_into_parsed_data():
assert module.parse_json('{"repository": {"id": "foo"}}', label='bar') == { assert module.parse_json('{"repository": {"id": "foo"}}', label='bar') == {
'repository': {'id': 'foo', 'label': 'bar'} 'repository': {'id': 'foo', 'label': 'bar'}
} }
def test_parse_json_injects_nothing_when_repository_missing(): def test_parse_json_injects_nothing_when_repository_missing():
flexmock(module.json).should_receive('loads').and_return({'stuff': {'id': 'foo'}})
assert module.parse_json('{"stuff": {"id": "foo"}}', label='bar') == {'stuff': {'id': 'foo'}} assert module.parse_json('{"stuff": {"id": "foo"}}', label='bar') == {'stuff': {'id': 'foo'}}