Use the Bandit security analysis tool when running tests.

SECURITY: Add timeouts to all monitoring hooks to prevent hangs on network requests.
SECURITY: For the "spot" check, use a more secure source of randomness when selecting paths to check.
This commit is contained in:
Dan Helfman
2025-06-27 22:30:00 -07:00
parent 1e6fba6e99
commit 0d54fda27a
30 changed files with 149 additions and 81 deletions
+4
View File
@@ -8,6 +8,10 @@
* #1099: Clarify documentation on command hooks order of execution.
* #1100: Fix a bug in which "borg --version" failing isn't considered a "fail" state in a command
hook.
* Use the Bandit security analysis tool when running tests.
* SECURITY: Add timeouts to all monitoring hooks to prevent hangs on network requests.
* SECURITY: For the "spot" check, use a more secure source of randomness when selecting paths to
check.
2.0.6
* #1068: Fix a warning from LVM about leaked file descriptors.
+1 -1
View File
@@ -486,7 +486,7 @@ def compare_spot_check_hashes(
sample_count = max(
int(len(source_paths) * (min(spot_check_config['data_sample_percentage'], 100) / 100)), 1
)
source_sample_paths = tuple(random.sample(source_paths, sample_count))
source_sample_paths = tuple(random.SystemRandom().sample(source_paths, sample_count))
working_directory = borgmatic.config.paths.get_working_directory(config)
hashable_source_sample_path = {
source_path
+1 -1
View File
@@ -60,7 +60,7 @@ def run_arbitrary_borg(
return execute_command(
tuple(shlex.quote(part) for part in full_command),
output_file=DO_NOT_CAPTURE,
shell=True,
shell=True, # noqa: S604
environment=dict(
(environment.make_environment(config) or {}),
**{
+1 -1
View File
@@ -750,7 +750,7 @@ def log_error_records(
except (ValueError, OSError) as error:
yield log_record(levelno=levelno, levelname=level_name, msg=str(message))
yield log_record(levelno=levelno, levelname=level_name, msg=str(error))
except: # noqa: E722
except: # noqa: E722, S110
# Raising above only as a means of determining the error type. Swallow the exception here
# because we don't want the exception to propagate out of this function.
pass
+3 -1
View File
@@ -103,7 +103,9 @@ class Runtime_directory:
self.temporary_directory = None
else:
base_directory = os.environ.get('TMPDIR') or os.environ.get('TEMP') or '/tmp'
base_directory = (
os.environ.get('TMPDIR') or os.environ.get('TEMP') or '/tmp' # noqa: S108
)
if not base_directory.startswith(os.path.sep):
raise ValueError('The temporary directory must be an absolute path')
+3 -3
View File
@@ -313,7 +313,7 @@ def execute_command(
stdin=input_file,
stdout=None if do_not_capture else (output_file or subprocess.PIPE),
stderr=None if do_not_capture else (subprocess.PIPE if output_file else subprocess.STDOUT),
shell=shell,
shell=shell, # noqa: S602
env=environment,
cwd=working_directory,
close_fds=close_fds,
@@ -363,7 +363,7 @@ def execute_command_and_capture_output(
command,
stdin=input_file,
stderr=subprocess.STDOUT if capture_stderr else None,
shell=shell,
shell=shell, # noqa: S602
env=environment,
cwd=working_directory,
close_fds=close_fds,
@@ -423,7 +423,7 @@ def execute_command_with_processes(
stderr=(
None if do_not_capture else (subprocess.PIPE if output_file else subprocess.STDOUT)
),
shell=shell,
shell=shell, # noqa: S602
env=environment,
cwd=working_directory,
close_fds=close_fds,
+1 -1
View File
@@ -144,7 +144,7 @@ def execute_hooks(command_hooks, umask, working_directory, dry_run, **context):
output_log_level=(
logging.ERROR if hook_config.get('after') == 'error' else logging.ANSWER
),
shell=True,
shell=True, # noqa: S604
environment=make_environment(os.environ),
working_directory=working_directory,
)
+4 -3
View File
@@ -74,11 +74,12 @@ def dump_data_sources(
if dump_format == 'directory':
dump.create_parent_directory_for_dump(dump_filename)
execute_command(command, shell=True)
execute_command(command, shell=True) # noqa: S604
else:
dump.create_named_pipe_for_dump(dump_filename)
processes.append(execute_command(command, shell=True, run_to_completion=False))
processes.append(
execute_command(command, shell=True, run_to_completion=False) # noqa: S604
)
if not dry_run:
patterns.append(
borgmatic.borg.pattern.Pattern(
+2 -2
View File
@@ -224,7 +224,7 @@ def dump_data_sources(
dump.create_parent_directory_for_dump(dump_filename)
execute_command(
command,
shell=True,
shell=True, # noqa: S604
environment=environment,
)
else:
@@ -232,7 +232,7 @@ def dump_data_sources(
processes.append(
execute_command(
command,
shell=True,
shell=True, # noqa: S604
environment=environment,
run_to_completion=False,
)
+3 -1
View File
@@ -88,7 +88,9 @@ def dump_data_sources(
continue
dump.create_named_pipe_for_dump(dump_filename)
processes.append(execute_command(command, shell=True, run_to_completion=False))
processes.append(
execute_command(command, shell=True, run_to_completion=False) # noqa: S604
)
if not dry_run:
patterns.append(
+2 -1
View File
@@ -11,6 +11,7 @@ MONITOR_STATE_TO_CRONHUB = {
monitor.State.FINISH: 'finish',
monitor.State.FAIL: 'fail',
}
TIMEOUT_SECONDS = 10
def initialize_monitor(
@@ -45,7 +46,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
if not dry_run:
logging.getLogger('urllib3').setLevel(logging.ERROR)
try:
response = requests.get(ping_url)
response = requests.get(ping_url, timeout=TIMEOUT_SECONDS)
if not response.ok:
response.raise_for_status()
except requests.exceptions.RequestException as error:
+2 -1
View File
@@ -11,6 +11,7 @@ MONITOR_STATE_TO_CRONITOR = {
monitor.State.FINISH: 'complete',
monitor.State.FAIL: 'fail',
}
TIMEOUT_SECONDS = 10
def initialize_monitor(
@@ -40,7 +41,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
if not dry_run:
logging.getLogger('urllib3').setLevel(logging.ERROR)
try:
response = requests.get(ping_url)
response = requests.get(ping_url, timeout=TIMEOUT_SECONDS)
if not response.ok:
response.raise_for_status()
except requests.exceptions.RequestException as error:
+5 -1
View File
@@ -17,6 +17,7 @@ MONITOR_STATE_TO_HEALTHCHECKS = {
DEFAULT_PING_BODY_LIMIT_BYTES = 100000
HANDLER_IDENTIFIER = 'healthchecks'
TIMEOUT_SECONDS = 10
def initialize_monitor(hook_config, config, config_filename, monitoring_log_level, dry_run):
@@ -84,7 +85,10 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
logging.getLogger('urllib3').setLevel(logging.ERROR)
try:
response = requests.post(
ping_url, data=payload.encode('utf-8'), verify=hook_config.get('verify_tls', True)
ping_url,
data=payload.encode('utf-8'),
verify=hook_config.get('verify_tls', True),
timeout=TIMEOUT_SECONDS,
)
if not response.ok:
response.raise_for_status()
+4 -1
View File
@@ -15,6 +15,7 @@ MONITOR_STATE_TO_LOKI = {
monitor.State.FINISH: 'Finished',
monitor.State.FAIL: 'Failed',
}
TIMEOUT_SECONDS = 10
# Threshold at which logs get flushed to loki
MAX_BUFFER_LINES = 100
@@ -69,7 +70,9 @@ class Loki_log_buffer:
request_header = {'Content-Type': 'application/json'}
try:
result = requests.post(self.url, headers=request_header, data=request_body, timeout=5)
result = requests.post(
self.url, headers=request_header, data=request_body, timeout=TIMEOUT_SECONDS
)
result.raise_for_status()
except requests.RequestException:
logger.warning('Failed to upload logs to loki')
+6 -1
View File
@@ -7,6 +7,9 @@ import borgmatic.hooks.credential.parse
logger = logging.getLogger(__name__)
TIMEOUT_SECONDS = 10
def initialize_monitor(
ping_url, config, config_filename, monitoring_log_level, dry_run
): # pragma: no cover
@@ -82,7 +85,9 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
if not dry_run:
logging.getLogger('urllib3').setLevel(logging.ERROR)
try:
response = requests.post(f'{base_url}/{topic}', headers=headers, auth=auth)
response = requests.post(
f'{base_url}/{topic}', headers=headers, auth=auth, timeout=TIMEOUT_SECONDS
)
if not response.ok:
response.raise_for_status()
except requests.exceptions.RequestException as error:
+4 -1
View File
@@ -14,6 +14,7 @@ logger = logging.getLogger(__name__)
EVENTS_API_URL = 'https://events.pagerduty.com/v2/enqueue'
DEFAULT_LOGS_PAYLOAD_LIMIT_BYTES = 10000
HANDLER_IDENTIFIER = 'pagerduty'
TIMEOUT_SECONDS = 10
def initialize_monitor(hook_config, config, config_filename, monitoring_log_level, dry_run):
@@ -94,7 +95,9 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
logging.getLogger('urllib3').setLevel(logging.ERROR)
try:
response = requests.post(EVENTS_API_URL, data=payload.encode('utf-8'))
response = requests.post(
EVENTS_API_URL, data=payload.encode('utf-8'), timeout=TIMEOUT_SECONDS
)
if not response.ok:
response.raise_for_status()
except requests.exceptions.RequestException as error:
+2
View File
@@ -8,6 +8,7 @@ logger = logging.getLogger(__name__)
EMERGENCY_PRIORITY = 2
TIMEOUT_SECONDS = 10
def initialize_monitor(
@@ -79,6 +80,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
'https://api.pushover.net/1/messages.json',
headers={'Content-type': 'application/x-www-form-urlencoded'},
data=data,
timeout=TIMEOUT_SECONDS,
)
if not response.ok:
response.raise_for_status()
+4 -1
View File
@@ -6,6 +6,9 @@ import requests
logger = logging.getLogger(__name__)
TIMEOUT_SECONDS = 10
def initialize_monitor(
ping_url, config, config_filename, monitoring_log_level, dry_run
): # pragma: no cover
@@ -61,7 +64,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
logging.getLogger('urllib3').setLevel(logging.ERROR)
try:
response = requests.post(f'{cron_url}?status={status}')
response = requests.post(f'{cron_url}?status={status}', timeout=TIMEOUT_SECONDS)
if not response.ok:
response.raise_for_status()
except requests.exceptions.RequestException as error:
+8 -1
View File
@@ -5,6 +5,9 @@ import requests
logger = logging.getLogger(__name__)
TIMEOUT_SECONDS = 10
def initialize_monitor(
push_url, config, config_filename, monitoring_log_level, dry_run
): # pragma: no cover
@@ -37,7 +40,11 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
logging.getLogger('urllib3').setLevel(logging.ERROR)
try:
response = requests.get(f'{push_url}?{query}', verify=hook_config.get('verify_tls', True))
response = requests.get(
f'{push_url}?{query}',
verify=hook_config.get('verify_tls', True),
timeout=TIMEOUT_SECONDS,
)
if not response.ok:
response.raise_for_status()
except requests.exceptions.RequestException as error:
+4 -1
View File
@@ -7,6 +7,9 @@ import borgmatic.hooks.credential.parse
logger = logging.getLogger(__name__)
TIMEOUT_SECONDS = 10
def initialize_monitor(
ping_url, config, config_filename, monitoring_log_level, dry_run
): # pragma: no cover
@@ -28,7 +31,7 @@ def send_zabbix_request(server, headers, data):
logger.debug(f'Sending a "{data["method"]}" request to the Zabbix server')
try:
response = requests.post(server, headers=headers, json=data)
response = requests.post(server, headers=headers, json=data, timeout=TIMEOUT_SECONDS)
if not response.ok:
response.raise_for_status()
+3
View File
@@ -1,6 +1,7 @@
appdirs==1.4.4
apprise==1.9.3
attrs==25.3.0
bandit==1.8.5
black==25.1.0
certifi==2025.6.15
chardet==5.2.0
@@ -8,6 +9,7 @@ click==8.2.1
codespell==2.4.1
coverage==7.9.1
flake8==7.3.0
flake8-bandit==4.1.1
flake8-quotes==3.4.0
flake8-use-fstring==1.4
flake8-variables-names==0.0.6
@@ -19,6 +21,7 @@ Markdown==3.8.2
mccabe==0.7.0
packaging==25.0
pathspec==0.12.1
pbr==6.1.1
pluggy==1.6.0
py==1.11.0
pycodestyle==2.14.0
+18 -18
View File
@@ -938,8 +938,8 @@ def test_collect_spot_check_source_paths_uses_working_directory():
def test_compare_spot_check_hashes_returns_paths_having_failing_hashes():
flexmock(module.random).should_receive('sample').replace_with(
lambda population, count: population[:count]
flexmock(module.random).should_receive('SystemRandom').and_return(
flexmock(sample=lambda population, count: population[:count])
)
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
None,
@@ -979,8 +979,8 @@ def test_compare_spot_check_hashes_returns_paths_having_failing_hashes():
def test_compare_spot_check_hashes_returns_relative_paths_having_failing_hashes():
flexmock(module.random).should_receive('sample').replace_with(
lambda population, count: population[:count]
flexmock(module.random).should_receive('SystemRandom').and_return(
flexmock(sample=lambda population, count: population[:count])
)
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
None,
@@ -1020,8 +1020,8 @@ def test_compare_spot_check_hashes_returns_relative_paths_having_failing_hashes(
def test_compare_spot_check_hashes_handles_data_sample_percentage_above_100():
flexmock(module.random).should_receive('sample').replace_with(
lambda population, count: population[:count]
flexmock(module.random).should_receive('SystemRandom').and_return(
flexmock(sample=lambda population, count: population[:count])
)
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
None,
@@ -1061,8 +1061,8 @@ def test_compare_spot_check_hashes_handles_data_sample_percentage_above_100():
def test_compare_spot_check_hashes_uses_xxh64sum_command_option():
flexmock(module.random).should_receive('sample').replace_with(
lambda population, count: population[:count]
flexmock(module.random).should_receive('SystemRandom').and_return(
flexmock(sample=lambda population, count: population[:count])
)
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
None,
@@ -1101,8 +1101,8 @@ def test_compare_spot_check_hashes_uses_xxh64sum_command_option():
def test_compare_spot_check_hashes_considers_path_missing_from_archive_as_not_matching():
flexmock(module.random).should_receive('sample').replace_with(
lambda population, count: population[:count]
flexmock(module.random).should_receive('SystemRandom').and_return(
flexmock(sample=lambda population, count: population[:count])
)
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
None,
@@ -1138,8 +1138,8 @@ def test_compare_spot_check_hashes_considers_path_missing_from_archive_as_not_ma
def test_compare_spot_check_hashes_considers_symlink_path_as_not_matching():
flexmock(module.random).should_receive('sample').replace_with(
lambda population, count: population[:count]
flexmock(module.random).should_receive('SystemRandom').and_return(
flexmock(sample=lambda population, count: population[:count])
)
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
None,
@@ -1174,8 +1174,8 @@ def test_compare_spot_check_hashes_considers_symlink_path_as_not_matching():
def test_compare_spot_check_hashes_considers_non_existent_path_as_not_matching():
flexmock(module.random).should_receive('sample').replace_with(
lambda population, count: population[:count]
flexmock(module.random).should_receive('SystemRandom').and_return(
flexmock(sample=lambda population, count: population[:count])
)
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
None,
@@ -1211,8 +1211,8 @@ def test_compare_spot_check_hashes_considers_non_existent_path_as_not_matching()
def test_compare_spot_check_hashes_with_too_many_paths_feeds_them_to_commands_in_chunks():
flexmock(module).SAMPLE_PATHS_SUBSET_COUNT = 2
flexmock(module.random).should_receive('sample').replace_with(
lambda population, count: population[:count]
flexmock(module.random).should_receive('SystemRandom').and_return(
flexmock(sample=lambda population, count: population[:count])
)
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
None,
@@ -1257,8 +1257,8 @@ def test_compare_spot_check_hashes_with_too_many_paths_feeds_them_to_commands_in
def test_compare_spot_check_hashes_uses_working_directory_to_access_source_paths():
flexmock(module.random).should_receive('sample').replace_with(
lambda population, count: population[:count]
flexmock(module.random).should_receive('SystemRandom').and_return(
flexmock(sample=lambda population, count: population[:count])
)
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
'/working/dir',
+5 -5
View File
@@ -6,7 +6,7 @@ from borgmatic.hooks.monitoring import cronhub as module
def test_ping_monitor_rewrites_ping_url_for_start_state():
hook_config = {'ping_url': 'https://example.com/start/abcdef'}
flexmock(module.requests).should_receive('get').with_args(
'https://example.com/start/abcdef'
'https://example.com/start/abcdef', timeout=int
).and_return(flexmock(ok=True))
module.ping_monitor(
@@ -22,7 +22,7 @@ def test_ping_monitor_rewrites_ping_url_for_start_state():
def test_ping_monitor_rewrites_ping_url_and_state_for_start_state():
hook_config = {'ping_url': 'https://example.com/ping/abcdef'}
flexmock(module.requests).should_receive('get').with_args(
'https://example.com/start/abcdef'
'https://example.com/start/abcdef', timeout=int
).and_return(flexmock(ok=True))
module.ping_monitor(
@@ -38,7 +38,7 @@ def test_ping_monitor_rewrites_ping_url_and_state_for_start_state():
def test_ping_monitor_rewrites_ping_url_for_finish_state():
hook_config = {'ping_url': 'https://example.com/start/abcdef'}
flexmock(module.requests).should_receive('get').with_args(
'https://example.com/finish/abcdef'
'https://example.com/finish/abcdef', timeout=int
).and_return(flexmock(ok=True))
module.ping_monitor(
@@ -54,7 +54,7 @@ def test_ping_monitor_rewrites_ping_url_for_finish_state():
def test_ping_monitor_rewrites_ping_url_for_fail_state():
hook_config = {'ping_url': 'https://example.com/start/abcdef'}
flexmock(module.requests).should_receive('get').with_args(
'https://example.com/fail/abcdef'
'https://example.com/fail/abcdef', timeout=int
).and_return(flexmock(ok=True))
module.ping_monitor(
@@ -105,7 +105,7 @@ def test_ping_monitor_with_other_error_logs_warning():
module.requests.exceptions.RequestException
)
flexmock(module.requests).should_receive('get').with_args(
'https://example.com/start/abcdef'
'https://example.com/start/abcdef', timeout=int
).and_return(response)
flexmock(module.logger).should_receive('warning').once()
+8 -8
View File
@@ -5,9 +5,9 @@ from borgmatic.hooks.monitoring import cronitor as module
def test_ping_monitor_hits_ping_url_for_start_state():
hook_config = {'ping_url': 'https://example.com'}
flexmock(module.requests).should_receive('get').with_args('https://example.com/run').and_return(
flexmock(ok=True)
)
flexmock(module.requests).should_receive('get').with_args(
'https://example.com/run', timeout=int
).and_return(flexmock(ok=True))
module.ping_monitor(
hook_config,
@@ -22,7 +22,7 @@ def test_ping_monitor_hits_ping_url_for_start_state():
def test_ping_monitor_hits_ping_url_for_finish_state():
hook_config = {'ping_url': 'https://example.com'}
flexmock(module.requests).should_receive('get').with_args(
'https://example.com/complete'
'https://example.com/complete', timeout=int
).and_return(flexmock(ok=True))
module.ping_monitor(
@@ -38,7 +38,7 @@ def test_ping_monitor_hits_ping_url_for_finish_state():
def test_ping_monitor_hits_ping_url_for_fail_state():
hook_config = {'ping_url': 'https://example.com'}
flexmock(module.requests).should_receive('get').with_args(
'https://example.com/fail'
'https://example.com/fail', timeout=int
).and_return(flexmock(ok=True))
module.ping_monitor(
@@ -88,9 +88,9 @@ def test_ping_monitor_with_other_error_logs_warning():
response.should_receive('raise_for_status').and_raise(
module.requests.exceptions.RequestException
)
flexmock(module.requests).should_receive('get').with_args('https://example.com/run').and_return(
response
)
flexmock(module.requests).should_receive('get').with_args(
'https://example.com/run', timeout=int
).and_return(response)
flexmock(module.logger).should_receive('warning').once()
module.ping_monitor(
@@ -79,7 +79,7 @@ def test_ping_monitor_hits_ping_url_for_start_state():
).never()
hook_config = {'ping_url': 'https://example.com'}
flexmock(module.requests).should_receive('post').with_args(
'https://example.com/start', data=''.encode('utf-8'), verify=True
'https://example.com/start', data=''.encode('utf-8'), verify=True, timeout=int
).and_return(flexmock(ok=True))
module.ping_monitor(
@@ -100,7 +100,7 @@ def test_ping_monitor_hits_ping_url_for_finish_state():
'format_buffered_logs_for_payload'
).and_return(payload)
flexmock(module.requests).should_receive('post').with_args(
'https://example.com', data=payload.encode('utf-8'), verify=True
'https://example.com', data=payload.encode('utf-8'), verify=True, timeout=int
).and_return(flexmock(ok=True))
module.ping_monitor(
@@ -121,7 +121,7 @@ def test_ping_monitor_hits_ping_url_for_fail_state():
'format_buffered_logs_for_payload'
).and_return(payload)
flexmock(module.requests).should_receive('post').with_args(
'https://example.com/fail', data=payload.encode('utf'), verify=True
'https://example.com/fail', data=payload.encode('utf'), verify=True, timeout=int
).and_return(flexmock(ok=True))
module.ping_monitor(
@@ -142,7 +142,7 @@ def test_ping_monitor_hits_ping_url_for_log_state():
'format_buffered_logs_for_payload'
).and_return(payload)
flexmock(module.requests).should_receive('post').with_args(
'https://example.com/log', data=payload.encode('utf'), verify=True
'https://example.com/log', data=payload.encode('utf'), verify=True, timeout=int
).and_return(flexmock(ok=True))
module.ping_monitor(
@@ -166,6 +166,7 @@ def test_ping_monitor_with_ping_uuid_hits_corresponding_url():
f"https://hc-ping.com/{hook_config['ping_url']}",
data=payload.encode('utf-8'),
verify=True,
timeout=int,
).and_return(flexmock(ok=True))
module.ping_monitor(
@@ -186,7 +187,7 @@ def test_ping_monitor_skips_ssl_verification_when_verify_tls_false():
'format_buffered_logs_for_payload'
).and_return(payload)
flexmock(module.requests).should_receive('post').with_args(
'https://example.com', data=payload.encode('utf-8'), verify=False
'https://example.com', data=payload.encode('utf-8'), verify=False, timeout=int
).and_return(flexmock(ok=True))
module.ping_monitor(
@@ -207,7 +208,7 @@ def test_ping_monitor_executes_ssl_verification_when_verify_tls_true():
'format_buffered_logs_for_payload'
).and_return(payload)
flexmock(module.requests).should_receive('post').with_args(
'https://example.com', data=payload.encode('utf-8'), verify=True
'https://example.com', data=payload.encode('utf-8'), verify=True, timeout=int
).and_return(flexmock(ok=True))
module.ping_monitor(
@@ -260,7 +261,7 @@ def test_ping_monitor_hits_ping_url_when_states_matching():
).never()
hook_config = {'ping_url': 'https://example.com', 'states': ['start', 'finish']}
flexmock(module.requests).should_receive('post').with_args(
'https://example.com/start', data=''.encode('utf-8'), verify=True
'https://example.com/start', data=''.encode('utf-8'), verify=True, timeout=int
).and_return(flexmock(ok=True))
module.ping_monitor(
@@ -279,7 +280,7 @@ def test_ping_monitor_adds_create_query_parameter_when_create_slug_true():
).never()
hook_config = {'ping_url': 'https://example.com', 'create_slug': True}
flexmock(module.requests).should_receive('post').with_args(
'https://example.com/start?create=1', data=''.encode('utf-8'), verify=True
'https://example.com/start?create=1', data=''.encode('utf-8'), verify=True, timeout=int
).and_return(flexmock(ok=True))
module.ping_monitor(
@@ -298,7 +299,7 @@ def test_ping_monitor_does_not_add_create_query_parameter_when_create_slug_false
).never()
hook_config = {'ping_url': 'https://example.com', 'create_slug': False}
flexmock(module.requests).should_receive('post').with_args(
'https://example.com/start', data=''.encode('utf-8'), verify=True
'https://example.com/start', data=''.encode('utf-8'), verify=True, timeout=int
).and_return(flexmock(ok=True))
module.ping_monitor(
@@ -317,6 +318,7 @@ def test_ping_monitor_does_not_add_create_query_parameter_when_ping_url_is_uuid(
f"https://hc-ping.com/{hook_config['ping_url']}",
data=''.encode('utf-8'),
verify=True,
timeout=int,
).and_return(flexmock(ok=True))
module.ping_monitor(
@@ -352,7 +354,7 @@ def test_ping_monitor_with_connection_error_logs_warning():
).never()
hook_config = {'ping_url': 'https://example.com'}
flexmock(module.requests).should_receive('post').with_args(
'https://example.com/start', data=''.encode('utf-8'), verify=True
'https://example.com/start', data=''.encode('utf-8'), verify=True, timeout=int
).and_raise(module.requests.exceptions.ConnectionError)
flexmock(module.logger).should_receive('warning').once()
@@ -376,7 +378,7 @@ def test_ping_monitor_with_other_error_logs_warning():
module.requests.exceptions.RequestException
)
flexmock(module.requests).should_receive('post').with_args(
'https://example.com/start', data=''.encode('utf-8'), verify=True
'https://example.com/start', data=''.encode('utf-8'), verify=True, timeout=int
).and_return(response)
flexmock(module.logger).should_receive('warning').once()
+11 -1
View File
@@ -43,6 +43,7 @@ def test_ping_monitor_minimal_config_hits_hosted_ntfy_on_fail():
f'{default_base_url}/{topic}',
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
auth=None,
timeout=int,
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -67,6 +68,7 @@ def test_ping_monitor_with_access_token_hits_hosted_ntfy_on_fail():
f'{default_base_url}/{topic}',
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
auth=module.requests.auth.HTTPBasicAuth('', 'abc123'),
timeout=int,
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -93,6 +95,7 @@ def test_ping_monitor_with_username_password_and_access_token_ignores_username_p
f'{default_base_url}/{topic}',
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
auth=module.requests.auth.HTTPBasicAuth('', 'abc123'),
timeout=int,
).and_return(flexmock(ok=True)).once()
flexmock(module.logger).should_receive('warning').once()
@@ -119,6 +122,7 @@ def test_ping_monitor_with_username_password_hits_hosted_ntfy_on_fail():
f'{default_base_url}/{topic}',
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
auth=module.requests.auth.HTTPBasicAuth('testuser', 'fakepassword'),
timeout=int,
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -140,6 +144,7 @@ def test_ping_monitor_with_password_but_no_username_warns():
f'{default_base_url}/{topic}',
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
auth=None,
timeout=int,
).and_return(flexmock(ok=True)).once()
flexmock(module.logger).should_receive('warning').once()
@@ -162,6 +167,7 @@ def test_ping_monitor_with_username_but_no_password_warns():
f'{default_base_url}/{topic}',
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
auth=None,
timeout=int,
).and_return(flexmock(ok=True)).once()
flexmock(module.logger).should_receive('warning').once()
@@ -218,6 +224,7 @@ def test_ping_monitor_minimal_config_hits_selfhosted_ntfy_on_fail():
f'{custom_base_url}/{topic}',
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
auth=None,
timeout=int,
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -253,7 +260,7 @@ def test_ping_monitor_custom_message_hits_hosted_ntfy_on_fail():
'resolve_credential'
).replace_with(lambda value, config: value)
flexmock(module.requests).should_receive('post').with_args(
f'{default_base_url}/{topic}', headers=custom_message_headers, auth=None
f'{default_base_url}/{topic}', headers=custom_message_headers, auth=None, timeout=int
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -275,6 +282,7 @@ def test_ping_monitor_custom_state_hits_hosted_ntfy_on_start():
f'{default_base_url}/{topic}',
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.START),
auth=None,
timeout=int,
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -296,6 +304,7 @@ def test_ping_monitor_with_connection_error_logs_warning():
f'{default_base_url}/{topic}',
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
auth=None,
timeout=int,
).and_raise(module.requests.exceptions.ConnectionError)
flexmock(module.logger).should_receive('warning').once()
@@ -340,6 +349,7 @@ def test_ping_monitor_with_other_error_logs_warning():
f'{default_base_url}/{topic}',
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
auth=None,
timeout=int,
).and_return(response)
flexmock(module.logger).should_receive('warning').once()
@@ -23,6 +23,7 @@ def test_ping_monitor_config_with_minimum_config_fail_state_backup_successfully_
'user': '983hfe0of902lkjfa2amanfgui',
'message': 'fail',
},
timeout=int,
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -81,6 +82,7 @@ def test_ping_monitor_start_state_backup_default_message_successfully_send_to_pu
'user': '983hfe0of902lkjfa2amanfgui',
'message': 'start',
},
timeout=int,
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -117,6 +119,7 @@ def test_ping_monitor_start_state_backup_custom_message_successfully_send_to_pus
'user': '983hfe0of902lkjfa2amanfgui',
'message': 'custom start message',
},
timeout=int,
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -155,6 +158,7 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_emergency
'retry': 30,
'expire': 600,
},
timeout=int,
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -193,6 +197,7 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_emergency
'retry': 30,
'expire': 600,
},
timeout=int,
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -231,6 +236,7 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_emergency
'retry': 30,
'expire': 600,
},
timeout=int,
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -328,6 +334,7 @@ def test_ping_monitor_start_state_backup_based_on_documentation_advanced_example
'html': 1,
'ttl': 10,
},
timeout=int,
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -399,6 +406,7 @@ def test_ping_monitor_fail_state_backup_based_on_documentation_advanced_example_
'url': 'https://ticketing-system.example.com/login',
'url_title': 'Login to ticketing system',
},
timeout=int,
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -467,6 +475,7 @@ def test_ping_monitor_finish_state_backup_based_on_documentation_advanced_exampl
'url': 'https://ticketing-system.example.com/login',
'url_title': 'Login to ticketing system',
},
timeout=int,
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -550,6 +559,7 @@ def test_ping_monitor_push_post_error_bails():
'user': '983hfe0of902lkjfa2amanfgui',
'message': 'fail',
},
timeout=int,
).and_return(push_response).once()
flexmock(module.logger).should_receive('warning').once()
+4 -2
View File
@@ -29,7 +29,8 @@ def test_ping_monitor_constructs_cron_url_and_pings_it(state, configured_states,
hook_config['states'] = configured_states
flexmock(module.requests).should_receive('post').with_args(
f'https://o294220.ingest.us.sentry.io/api/203069/cron/test/5f80ec/?status={expected_status}'
f'https://o294220.ingest.us.sentry.io/api/203069/cron/test/5f80ec/?status={expected_status}',
timeout=int,
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -134,7 +135,8 @@ def test_ping_monitor_with_network_error_does_not_raise():
module.requests.exceptions.ConnectionError
)
flexmock(module.requests).should_receive('post').with_args(
'https://o294220.ingest.us.sentry.io/api/203069/cron/test/5f80ec/?status=in_progress'
'https://o294220.ingest.us.sentry.io/api/203069/cron/test/5f80ec/?status=in_progress',
timeout=int,
).and_return(response).once()
module.ping_monitor(
@@ -10,7 +10,7 @@ CUSTOM_PUSH_URL = 'https://uptime.example.com/api/push/efgh5678'
def test_ping_monitor_hits_default_uptimekuma_on_fail():
hook_config = {}
flexmock(module.requests).should_receive('get').with_args(
f'{DEFAULT_PUSH_URL}?status=down&msg=fail', verify=True
f'{DEFAULT_PUSH_URL}?status=down&msg=fail', verify=True, timeout=int
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -26,7 +26,7 @@ def test_ping_monitor_hits_default_uptimekuma_on_fail():
def test_ping_monitor_hits_custom_uptimekuma_on_fail():
hook_config = {'push_url': CUSTOM_PUSH_URL}
flexmock(module.requests).should_receive('get').with_args(
f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True
f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True, timeout=int
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -42,7 +42,7 @@ def test_ping_monitor_hits_custom_uptimekuma_on_fail():
def test_ping_monitor_custom_uptimekuma_on_start():
hook_config = {'push_url': CUSTOM_PUSH_URL}
flexmock(module.requests).should_receive('get').with_args(
f'{CUSTOM_PUSH_URL}?status=up&msg=start', verify=True
f'{CUSTOM_PUSH_URL}?status=up&msg=start', verify=True, timeout=int
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -58,7 +58,7 @@ def test_ping_monitor_custom_uptimekuma_on_start():
def test_ping_monitor_custom_uptimekuma_on_finish():
hook_config = {'push_url': CUSTOM_PUSH_URL}
flexmock(module.requests).should_receive('get').with_args(
f'{CUSTOM_PUSH_URL}?status=up&msg=finish', verify=True
f'{CUSTOM_PUSH_URL}?status=up&msg=finish', verify=True, timeout=int
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -116,7 +116,7 @@ def test_ping_monitor_does_not_hit_custom_uptimekuma_on_finish_dry_run():
def test_ping_monitor_with_connection_error_logs_warning():
hook_config = {'push_url': CUSTOM_PUSH_URL}
flexmock(module.requests).should_receive('get').with_args(
f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True
f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True, timeout=int
).and_raise(module.requests.exceptions.ConnectionError)
flexmock(module.logger).should_receive('warning').once()
@@ -137,7 +137,7 @@ def test_ping_monitor_with_other_error_logs_warning():
module.requests.exceptions.RequestException
)
flexmock(module.requests).should_receive('get').with_args(
f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True
f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True, timeout=int
).and_return(response)
flexmock(module.logger).should_receive('warning').once()
@@ -168,7 +168,7 @@ def test_ping_monitor_with_invalid_run_state():
def test_ping_monitor_skips_ssl_verification_when_verify_tls_false():
hook_config = {'push_url': CUSTOM_PUSH_URL, 'verify_tls': False}
flexmock(module.requests).should_receive('get').with_args(
f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=False
f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=False, timeout=int
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
@@ -184,7 +184,7 @@ def test_ping_monitor_skips_ssl_verification_when_verify_tls_false():
def test_ping_monitor_executes_ssl_verification_when_verify_tls_true():
hook_config = {'push_url': CUSTOM_PUSH_URL, 'verify_tls': True}
flexmock(module.requests).should_receive('get').with_args(
f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True
f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True, timeout=int
).and_return(flexmock(ok=True)).once()
module.ping_monitor(
+5 -5
View File
@@ -74,7 +74,7 @@ def test_send_zabbix_request_with_post_error_bails():
)
flexmock(module.requests).should_receive('post').with_args(
server, headers=headers, json=data
server, headers=headers, json=data, timeout=int
).and_return(response)
assert module.send_zabbix_request(server, headers, data) is None
@@ -89,7 +89,7 @@ def test_send_zabbix_request_with_invalid_json_response_bails():
response.should_receive('json').and_raise(module.requests.exceptions.JSONDecodeError)
flexmock(module.requests).should_receive('post').with_args(
server, headers=headers, json=data
server, headers=headers, json=data, timeout=int
).and_return(response)
assert module.send_zabbix_request(server, headers, data) is None
@@ -103,7 +103,7 @@ def test_send_zabbix_request_with_success_returns_response_result():
response.should_receive('json').and_return({'result': {'foo': 'bar'}})
flexmock(module.requests).should_receive('post').with_args(
server, headers=headers, json=data
server, headers=headers, json=data, timeout=int
).and_return(response)
assert module.send_zabbix_request(server, headers, data) == {'foo': 'bar'}
@@ -117,7 +117,7 @@ def test_send_zabbix_request_with_success_passes_through_missing_result():
response.should_receive('json').and_return({})
flexmock(module.requests).should_receive('post').with_args(
server, headers=headers, json=data
server, headers=headers, json=data, timeout=int
).and_return(response)
assert module.send_zabbix_request(server, headers, data) is None
@@ -131,7 +131,7 @@ def test_send_zabbix_request_with_error_bails():
response.should_receive('json').and_return({'result': {'data': [{'error': 'oops'}]}})
flexmock(module.requests).should_receive('post').with_args(
server, headers=headers, json=data
server, headers=headers, json=data, timeout=int
).and_return(response)
assert module.send_zabbix_request(server, headers, data) is None