From e1cf95233a9b7af5361415eb17eeabc246a15c3e Mon Sep 17 00:00:00 2001 From: Paul Traylor Date: Thu, 15 Feb 2024 10:39:52 +0900 Subject: [PATCH] Update to Alertmanager v2 API For most things, changing v1 to v2 works. The silence API now expects startsAt for all requests, so we make that adjustment. The Alerts and Silences API now return directly instead of a data field so we also make that adjustment. Also do some minor adjustments to use HTTPStatus instead of hardcoded status codes. --- promgen/prometheus.py | 11 ++++++--- promgen/proxy.py | 24 ++++++++++++++------ promgen/static/js/promgen.vue.js | 4 ++-- promgen/tests/__init__.py | 1 + promgen/tests/examples/silence.duration.json | 2 ++ promgen/tests/examples/silence.range.json | 1 + promgen/tests/test_silence.py | 8 ++++--- 7 files changed, 36 insertions(+), 15 deletions(-) diff --git a/promgen/prometheus.py b/promgen/prometheus.py index bab2518d6..144826309 100644 --- a/promgen/prometheus.py +++ b/promgen/prometheus.py @@ -276,20 +276,25 @@ def silence(*, labels, duration=None, **kwargs): end = start + datetime.timedelta(days=int(duration[:-1])) else: raise ValidationError("Unknown time modifier") + kwargs["startsAt"] = start.isoformat() kwargs["endsAt"] = end.isoformat() - kwargs.pop("startsAt", False) else: local_timezone = pytz.timezone(util.setting("timezone", "UTC")) for key in ["startsAt", "endsAt"]: kwargs[key] = local_timezone.localize(parser.parse(kwargs[key])).isoformat() kwargs["matchers"] = [ - {"name": name, "value": value, "isRegex": True if value.endswith("*") else False} + { + "name": name, + "value": value, + "isEqual": True, # Right now we only support =~ + "isRegex": True if value.endswith("*") else False, + } for name, value in labels.items() ] logger.debug("Sending silence for %s", kwargs) - url = urljoin(util.setting("alertmanager:url"), "/api/v1/silences") + url = urljoin(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 b38f097b8..c08c3fa59 100644 --- a/promgen/proxy.py +++ b/promgen/proxy.py @@ -4,6 +4,7 @@ import concurrent.futures import json import logging +from http import HTTPStatus from urllib.parse import urljoin import requests @@ -168,11 +169,11 @@ def get(self, request): class ProxyAlerts(View): def get(self, request): try: - url = urljoin(util.setting("alertmanager:url"), "/api/v1/alerts") + url = urljoin(util.setting("alertmanager:url"), "/api/v2/alerts") response = util.get(url) except requests.exceptions.ConnectionError: logger.error("Error connecting to %s", url) - return JsonResponse({}) + return JsonResponse({}, status=HTTPStatus.INTERNAL_SERVER_ERROR) else: return HttpResponse(response.content, content_type="application/json") @@ -180,11 +181,11 @@ def get(self, request): class ProxySilences(View): def get(self, request): try: - url = urljoin(util.setting("alertmanager:url"), "/api/v1/silences") + url = urljoin(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) - return JsonResponse({}) + return JsonResponse({}, status=HTTPStatus.INTERNAL_SERVER_ERROR) else: return HttpResponse(response.content, content_type="application/json") @@ -203,15 +204,24 @@ def post(self, request): for m in form.errors[k] ] }, - status=422, + status=HTTPStatus.UNPROCESSABLE_ENTITY, ) try: response = prometheus.silence(**form.cleaned_data) + except requests.HTTPError as e: + return JsonResponse( + { + "messages": [ + {"class": "alert alert-danger", "message": e.response.text} + ] + }, + status=e.response.status_code, + ) except Exception as e: return JsonResponse( {"messages": [{"class": "alert alert-danger", "message": str(e)}]}, - status=400, + status=HTTPStatus.UNPROCESSABLE_ENTITY, ) return HttpResponse( @@ -221,7 +231,7 @@ def post(self, request): class ProxyDeleteSilence(View): def delete(self, request, silence_id): - url = urljoin(util.setting("alertmanager:url"), "/api/v1/silence/%s" % silence_id) + url = urljoin(util.setting("alertmanager:url"), f"/api/v2/silence/{silence_id}") response = util.delete(url) return HttpResponse( response.text, status=response.status_code, content_type="application/json" diff --git a/promgen/static/js/promgen.vue.js b/promgen/static/js/promgen.vue.js index 1381d3407..18b44b04e 100644 --- a/promgen/static/js/promgen.vue.js +++ b/promgen/static/js/promgen.vue.js @@ -86,7 +86,7 @@ const app = Vue.createApp({ fetch('/proxy/v1/silences') .then(response => response.json()) .then(response => { - let silences = response.data.sort(silence => silence.startsAt); + let silences = response.sort(silence => silence.startsAt); // Pull out the matchers and do a simpler label map // to make other code easier @@ -104,7 +104,7 @@ const app = Vue.createApp({ fetch('/proxy/v1/alerts') .then(response => response.json()) .then(response => { - this.globalAlerts = response.data.sort(alert => alert.startsAt); + this.globalAlerts = response.sort(alert => alert.startsAt); }); }, diff --git a/promgen/tests/__init__.py b/promgen/tests/__init__.py index a63fdc0b8..c5ec053c9 100644 --- a/promgen/tests/__init__.py +++ b/promgen/tests/__init__.py @@ -29,6 +29,7 @@ def raw(self): class PromgenTest(TestCase): longMessage = True + maxDiff = None fixtures = ["testcases.yaml"] def fireAlert(self, source="alertmanager.json", data=None): diff --git a/promgen/tests/examples/silence.duration.json b/promgen/tests/examples/silence.duration.json index 130aad19c..f468c9c9f 100644 --- a/promgen/tests/examples/silence.duration.json +++ b/promgen/tests/examples/silence.duration.json @@ -3,8 +3,10 @@ "matchers": [{ "value": "example.com:[0-9]*", "isRegex": true, + "isEqual": true, "name": "instance" }], "comment": "Silenced from Promgen", + "startsAt": "2017-12-14T00:00:00+00:00", "endsAt": "2017-12-14T00:01:00+00:00" } diff --git a/promgen/tests/examples/silence.range.json b/promgen/tests/examples/silence.range.json index 0459b9dc5..7277afc58 100644 --- a/promgen/tests/examples/silence.range.json +++ b/promgen/tests/examples/silence.range.json @@ -3,6 +3,7 @@ "matchers": [{ "value": "example.com:[0-9]*", "isRegex": true, + "isEqual": true, "name": "instance" }], "comment": "Silenced from Promgen", diff --git a/promgen/tests/test_silence.py b/promgen/tests/test_silence.py index 070ac2f95..9a76ac034 100644 --- a/promgen/tests/test_silence.py +++ b/promgen/tests/test_silence.py @@ -41,8 +41,8 @@ def test_duration(self, mock_post): }, content_type="application/json", ) - self.assertMockCalls( - mock_post, "http://alertmanager:9093/api/v1/silences", json=TEST_DURATION + mock_post.assert_called_with( + "http://alertmanager:9093/api/v2/silences", json=TEST_DURATION ) @override_settings(PROMGEN=TEST_SETTINGS) @@ -62,7 +62,9 @@ def test_range(self, mock_post): content_type="application/json", ) - self.assertMockCalls(mock_post, "http://alertmanager:9093/api/v1/silences", json=TEST_RANGE) + mock_post.assert_called_with( + "http://alertmanager:9093/api/v2/silences", json=TEST_RANGE + ) @override_settings(PROMGEN=TEST_SETTINGS) def test_site_silence_errors(self):