diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index e180fcd..05520da 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -1678,23 +1678,15 @@ properties: documentation for details. uptimekuma: type: object - required: ['server', 'push_code'] + required: ['push_url'] additionalProperties: false properties: - server: + push_url: type: string description: | - Uptime Kuma base URL or UUID to notify when a backup - begins, ends, or errors - example: https://example.uptime.kuma - push_code: - type: string - description: | - Uptime Kuma "Push Code" from the push URL you have been - given. For example, the push code for - https://base.url/api/push/12345678?status=up&msg=OK&ping= - would be 12345678 - example: 12345678 + Uptime Kuma push URL without query string (do not include the + question mark or anything after it). + example: https://example.uptime.kuma/api/push/abcd1234 states: type: array items: diff --git a/borgmatic/hooks/uptimekuma.py b/borgmatic/hooks/uptimekuma.py index fcd171d..59f20df 100644 --- a/borgmatic/hooks/uptimekuma.py +++ b/borgmatic/hooks/uptimekuma.py @@ -16,40 +16,33 @@ def initialize_monitor( def ping_monitor(hook_config, config, config_filename, state, monitoring_log_level, dry_run): ''' - Ping the configured Uptime Kuma push_code. Use the given configuration filename in any log entries. + Ping the configured Uptime Kuma push_url. + Use the given configuration filename in any log entries. If this is a dry run, then don't actually ping anything. ''' - run_states = hook_config.get('states', ['start', 'finish', 'fail']) + if state.name.lower() not in run_states: + return + dry_run_label = ' (dry run; not actually pinging)' if dry_run else '' + status = 'down' if state.name.lower() == 'fail' else 'up' + push_url = hook_config.get('push_url', 'https://example.uptime.kuma/api/push/abcd1234') + query = f'status={status}&msg={state.name.lower()}' - if state.name.lower() in run_states: - - dry_run_label = ' (dry run; not actually pinging)' if dry_run else '' - - status = 'up' - if state.name.lower() == 'fail': - status = 'down' - - base_url = hook_config.get('server', 'https://example.uptime.kuma') + '/api/push' - push_code = hook_config.get('push_code') - - logger.info(f'{config_filename}: Pinging Uptime Kuma push_code {push_code}{dry_run_label}') - logger.debug(f'{config_filename}: Using Uptime Kuma ping URL {base_url}/{push_code}') - logger.debug( - f'{config_filename}: Full Uptime Kuma state URL {base_url}/{push_code}?status={status}&msg={state.name.lower()}&ping=' - ) - - if not dry_run: - logging.getLogger('urllib3').setLevel(logging.ERROR) - try: - response = requests.get( - f'{base_url}/{push_code}?status={status}&msg={state.name.lower()}&ping=' - ) - if not response.ok: - response.raise_for_status() - except requests.exceptions.RequestException as error: - logger.warning(f'{config_filename}: Uptime Kuma error: {error}') + logger.info(f'{config_filename}: Pinging Uptime Kuma push_url {push_url}?{query} {dry_run_label}') + logger.debug( + f'{config_filename}: Full Uptime Kuma state URL {push_url}?{query}' + ) + if not dry_run: + logging.getLogger('urllib3').setLevel(logging.ERROR) + try: + response = requests.get( + f'{push_url}?{query}' + ) + if not response.ok: + response.raise_for_status() + except requests.exceptions.RequestException as error: + logger.warning(f'{config_filename}: Uptime Kuma error: {error}') def destroy_monitor( ping_url_or_uuid, config, config_filename, monitoring_log_level, dry_run diff --git a/tests/unit/hooks/test_uptimekuma.py b/tests/unit/hooks/test_uptimekuma.py index 547a6f2..1d57444 100644 --- a/tests/unit/hooks/test_uptimekuma.py +++ b/tests/unit/hooks/test_uptimekuma.py @@ -3,15 +3,13 @@ from flexmock import flexmock import borgmatic.hooks.monitor from borgmatic.hooks import uptimekuma as module -default_base_url = 'https://example.uptime.kuma' -custom_base_url = 'https://uptime.example.com' -push_code = 'abcd1234' +DEFAULT_BASE_URL = 'https://example.uptime.kuma/api/push/abcd1234' +CUSTOM_BASE_URL = 'https://uptime.example.com/api/push/efgh5678' def test_ping_monitor_hits_default_uptimekuma_on_fail(): - hook_config = {'push_code': push_code} flexmock(module.requests).should_receive('get').with_args( - f'{default_base_url}/api/push/{push_code}?status=down&msg=fail&ping=' + f'{DEFAULT_BASE_URL}?status=down&msg=fail' ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -25,9 +23,9 @@ def test_ping_monitor_hits_default_uptimekuma_on_fail(): def test_ping_monitor_hits_custom_uptimekuma_on_fail(): - hook_config = {'server': custom_base_url, 'push_code': push_code} + hook_config = {'push_url': push_url} flexmock(module.requests).should_receive('get').with_args( - f'{custom_base_url}/api/push/{push_code}?status=down&msg=fail&ping=' + f'{CUSTOM_BASE_URL}?status=down&msg=fail' ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -41,9 +39,8 @@ def test_ping_monitor_hits_custom_uptimekuma_on_fail(): def test_ping_monitor_hits_default_uptimekuma_on_start(): - hook_config = {'push_code': push_code} flexmock(module.requests).should_receive('get').with_args( - f'{default_base_url}/api/push/{push_code}?status=up&msg=start&ping=' + f'{DEFAULT_BASE_URL}?status=up&msg=start' ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -57,9 +54,9 @@ def test_ping_monitor_hits_default_uptimekuma_on_start(): def test_ping_monitor_custom_uptimekuma_on_start(): - hook_config = {'server': custom_base_url, 'push_code': push_code} + hook_config = {'push_url': push_url} flexmock(module.requests).should_receive('get').with_args( - f'{custom_base_url}/api/push/{push_code}?status=up&msg=start&ping=' + f'{CUSTOM_BASE_URL}?status=up&msg=start' ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -73,9 +70,8 @@ def test_ping_monitor_custom_uptimekuma_on_start(): def test_ping_monitor_hits_default_uptimekuma_on_finish(): - hook_config = {'push_code': push_code} flexmock(module.requests).should_receive('get').with_args( - f'{default_base_url}/api/push/{push_code}?status=up&msg=finish&ping=' + f'{DEFAULT_BASE_URL}?status=up&msg=finish' ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -89,9 +85,9 @@ def test_ping_monitor_hits_default_uptimekuma_on_finish(): def test_ping_monitor_custom_uptimekuma_on_finish(): - hook_config = {'server': custom_base_url, 'push_code': push_code} + hook_config = {'push_url': CUSTOM_BASE_URL} flexmock(module.requests).should_receive('get').with_args( - f'{custom_base_url}/api/push/{push_code}?status=up&msg=finish&ping=' + f'{CUSTOM_BASE_URL}?status=up&msg=finish' ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -105,7 +101,6 @@ def test_ping_monitor_custom_uptimekuma_on_finish(): def test_ping_monitor_does_not_hit_default_uptimekuma_on_fail_dry_run(): - hook_config = {'push_code': push_code} flexmock(module.requests).should_receive('get').never() module.ping_monitor( @@ -119,7 +114,7 @@ def test_ping_monitor_does_not_hit_default_uptimekuma_on_fail_dry_run(): def test_ping_monitor_does_not_hit_custom_uptimekuma_on_fail_dry_run(): - hook_config = {'server': custom_base_url, 'push_code': push_code} + hook_config = {'push_url': CUSTOM_BASE_URL} flexmock(module.requests).should_receive('get').never() module.ping_monitor( @@ -133,7 +128,6 @@ def test_ping_monitor_does_not_hit_custom_uptimekuma_on_fail_dry_run(): def test_ping_monitor_does_not_hit_default_uptimekuma_on_start_dry_run(): - hook_config = {'push_code': push_code} flexmock(module.requests).should_receive('get').never() module.ping_monitor( @@ -147,7 +141,7 @@ def test_ping_monitor_does_not_hit_default_uptimekuma_on_start_dry_run(): def test_ping_monitor_does_not_hit_custom_uptimekuma_on_start_dry_run(): - hook_config = {'server': custom_base_url, 'push_code': push_code} + hook_config = {'push_url': CUSTOM_BASE_URL} flexmock(module.requests).should_receive('get').never() module.ping_monitor( @@ -161,7 +155,6 @@ def test_ping_monitor_does_not_hit_custom_uptimekuma_on_start_dry_run(): def test_ping_monitor_does_not_hit_default_uptimekuma_on_finish_dry_run(): - hook_config = {'push_code': push_code} flexmock(module.requests).should_receive('get').never() module.ping_monitor( @@ -175,7 +168,7 @@ def test_ping_monitor_does_not_hit_default_uptimekuma_on_finish_dry_run(): def test_ping_monitor_does_not_hit_custom_uptimekuma_on_finish_dry_run(): - hook_config = {'server': custom_base_url, 'push_code': push_code} + hook_config = {'push_url': CUSTOM_BASE_URL} flexmock(module.requests).should_receive('get').never() module.ping_monitor( @@ -189,9 +182,8 @@ 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_code': push_code} flexmock(module.requests).should_receive('get').with_args( - f'{default_base_url}/api/push/{push_code}?status=down&msg=fail&ping=' + f'{DEFAULT_BASE_URL}?status=down&msg=fail' ).and_raise(module.requests.exceptions.ConnectionError) flexmock(module.logger).should_receive('warning').once() @@ -206,13 +198,12 @@ def test_ping_monitor_with_connection_error_logs_warning(): def test_ping_monitor_with_other_error_logs_warning(): - hook_config = {'push_code': push_code} response = flexmock(ok=False) response.should_receive('raise_for_status').and_raise( module.requests.exceptions.RequestException ) flexmock(module.requests).should_receive('post').with_args( - f'{default_base_url}/api/push/{push_code}?status=down&msg=fail&ping=' + f'{DEFAULT_BASE_URL}?status=down&msg=fail' ).and_return(response) flexmock(module.logger).should_receive('warning').once() @@ -224,3 +215,17 @@ def test_ping_monitor_with_other_error_logs_warning(): monitoring_log_level=1, dry_run=False, ) + +def test_ping_monitor_with_invalid_run_state(): + hook_config = {'push_url': CUSTOM_BASE_URL} + flexmock(module.requests).should_receive('get').never() + + module.ping_monitor( + hook_config, + {}, + 'config.yaml', + borgmatic.hooks.monitor.State.LOG, + monitoring_log_level=1, + dry_run=True, + ) +