diff --git a/promgen/prometheus.py b/promgen/prometheus.py index 2857e27b..5d5c4d9b 100644 --- a/promgen/prometheus.py +++ b/promgen/prometheus.py @@ -6,7 +6,7 @@ import logging import subprocess import tempfile -from urllib.parse import parse_qs, urljoin, urlparse +from urllib.parse import parse_qs, urlparse import yaml from dateutil import parser @@ -335,7 +335,7 @@ def silence(*, labels, duration=None, **kwargs): ] logger.debug("Sending silence for %s", kwargs) - url = urljoin(util.setting("alertmanager:url"), "/api/v2/silences") + url = util.join_url(util.setting("alertmanager:url"), "/api/v2/silences") response = util.post(url, json=kwargs) response.raise_for_status() return response diff --git a/promgen/proxy.py b/promgen/proxy.py index a246d96b..2866d7db 100644 --- a/promgen/proxy.py +++ b/promgen/proxy.py @@ -5,7 +5,6 @@ import json import logging from http import HTTPStatus -from urllib.parse import urljoin import requests from django.http import HttpResponse, JsonResponse @@ -47,7 +46,7 @@ def proxy(self, request): futures.append( executor.submit( util.get, - urljoin(shard.url, request.get_full_path_info()), + util.join_url(shard.url, request.get_full_path_info()), headers=headers, ) ) @@ -160,7 +159,7 @@ def get(self, request): class ProxyAlerts(View): def get(self, request): try: - url = urljoin(util.setting("alertmanager:url"), "/api/v2/alerts") + url = util.join_url(util.setting("alertmanager:url"), "/api/v2/alerts") response = util.get(url) except requests.exceptions.ConnectionError: logger.error("Error connecting to %s", url) @@ -270,7 +269,7 @@ def get_queryset_for_matcher(matcher, klass): class ProxySilences(View): def get(self, request): try: - url = urljoin(util.setting("alertmanager:url"), "/api/v2/silences") + url = util.join_url(util.setting("alertmanager:url"), "/api/v2/silences") response = util.get(url, params={"silenced": False}) except requests.exceptions.ConnectionError: logger.error("Error connecting to %s", url) @@ -491,7 +490,7 @@ def post(self, request, *args, **kwargs): class ProxyDeleteSilence(View): def delete(self, request, silence_id): - url = urljoin(util.setting("alertmanager:url"), f"/api/v2/silence/{silence_id}") + url = util.join_url(util.setting("alertmanager:url"), f"/api/v2/silence/{silence_id}") # First, check if the silence exists response = util.get(url) if response.status_code != 200: diff --git a/promgen/tasks.py b/promgen/tasks.py index 8eda978f..e912b72f 100644 --- a/promgen/tasks.py +++ b/promgen/tasks.py @@ -5,7 +5,6 @@ import os import socket from datetime import datetime -from urllib.parse import urljoin from atomicwrites import atomic_write from billiard.exceptions import SoftTimeLimitExceeded @@ -184,7 +183,7 @@ def send_notification(*args, alert_pk, **kwargs): def reload_prometheus(): from promgen import signals - target = urljoin(util.setting("prometheus:url"), "/-/reload") + target = util.join_url(util.setting("prometheus:url"), "/-/reload") response = util.post(target) response.raise_for_status() signals.post_reload.send(response) @@ -192,7 +191,7 @@ def reload_prometheus(): @shared_task(base=BaseTaskWithRetry) def clear_tombstones(): - target = urljoin(util.setting("prometheus:url"), "/api/v1/admin/tsdb/clean_tombstones") + target = util.join_url(util.setting("prometheus:url"), "/api/v1/admin/tsdb/clean_tombstones") response = util.post(target) response.raise_for_status() diff --git a/promgen/tests/test_util.py b/promgen/tests/test_util.py index a5654c51..51662536 100644 --- a/promgen/tests/test_util.py +++ b/promgen/tests/test_util.py @@ -23,3 +23,28 @@ def test_categorize_error(self): for error, expected in cases: with self.subTest(error=type(error).__name__, expected=expected): self.assertEqual(util.categorize_error(error), expected) + + def test_join_url(self): + cases = [ + ("http://prometheus:9090", "/-/reload", "http://prometheus:9090/-/reload"), + ("http://prometheus:9090/", "/-/reload", "http://prometheus:9090/-/reload"), + ( + "http://example.com/prometheus", + "/-/reload", + "http://example.com/prometheus/-/reload", + ), + ( + "http://example.com/prometheus/", + "/api/v1/query?query=up", + "http://example.com/prometheus/api/v1/query?query=up", + ), + ( + "http://example.com/alertmanager/", + "api/v2/silences", + "http://example.com/alertmanager/api/v2/silences", + ), + ] + + for base, path, expected in cases: + with self.subTest(base=base, path=path): + self.assertEqual(util.join_url(base, path), expected) diff --git a/promgen/util.py b/promgen/util.py index 6c53cbe9..1c0746b2 100644 --- a/promgen/util.py +++ b/promgen/util.py @@ -60,6 +60,13 @@ def scrape(url, params=None, **kwargs): return requests.get(url, params=params, **kwargs) +def join_url(base, path): + """ + Append a path to a base URL, keeping any path prefix of the base URL + """ + return f"{base.removesuffix('/')}/{path.removeprefix('/')}" + + def setting(key, default=None, domain=None): """ Settings helper based on saltstack's query