From 599e4ae36ad57605fb3e35b59fc4dbf3d2f7c4f1 Mon Sep 17 00:00:00 2001 From: Tal Date: Mon, 11 Nov 2024 15:09:12 +0200 Subject: [PATCH] fix(victoriametrics): support either full URL or host and port (#2432) Co-authored-by: shahargl --- .../victoriametrics-provider.mdx | 30 +++++---- .../versions/2024-11-08-20-58_895fe80117aa.py | 30 ++++----- .../victoriametrics_provider.py | 65 +++++++++++++------ 3 files changed, 77 insertions(+), 48 deletions(-) diff --git a/docs/providers/documentation/victoriametrics-provider.mdx b/docs/providers/documentation/victoriametrics-provider.mdx index 5b6f8f760..c0e63abd6 100644 --- a/docs/providers/documentation/victoriametrics-provider.mdx +++ b/docs/providers/documentation/victoriametrics-provider.mdx @@ -6,11 +6,15 @@ description: "The VictoriametricsProvider allows you to fetch alerts in Victoria ## Authentication Parameters -The Victoriametrics provider requires the following authentication parameters: +The Victoriametrics provider requires either of the following authentication parameters: - `VMAlertHost`: The hostname or IP address where VMAlert is running. Example: `localhost`, `192.168.1.100`, or `vmalert.mydomain.com`. - `VMAlertPort`: The port number on which VMAlert is listening. Example: 8880 (if VMAlert is set to listen on port 8880). +or + +- `VMAlertURL`: The full URL to the VMAlert instance. For example: `http://vmalert.mydomain.com:8880`. + ## Connecting with the Provider 1. Ensure you have a running instance of VMAlert accessible by the host and port specified. @@ -21,6 +25,7 @@ The Victoriametrics provider requires the following authentication parameters: The Victoriametrics provider allows you to query from Victoriametrics through `query` and `query_range` types. The following are the parameters available for querying: 1. `query` type: + - `query`: The query to execute on Victoriametrics. Example: `sum(rate(http_requests_total{job="api-server"}[5m]))`. - `start`: The time to query the data for. Example: `2024-01-01T00:00:00Z` @@ -33,24 +38,25 @@ The Victoriametrics provider allows you to query from Victoriametrics through `q ## Push alerts to keep using webhooks You can push alerts to keep without connecting to Victoriametrics This provider takes advantage of configurable webhooks available with Prometheus Alertmanager. Use the following template to configure AlertManager: + ```yml route: receiver: "keep" - group_by: ['alertname'] - group_wait: 15s - group_interval: 15s + group_by: ["alertname"] + group_wait: 15s + group_interval: 15s repeat_interval: 1m continue: true receivers: -- name: "keep" - webhook_configs: - - url: '{keep_webhook_api_url}' - send_resolved: true - http_config: - basic_auth: - username: api_key - password: {api_key} + - name: "keep" + webhook_configs: + - url: "{keep_webhook_api_url}" + send_resolved: true + http_config: + basic_auth: + username: api_key + password: { api_key } ``` ## Useful Links diff --git a/keep/api/models/db/migrations/versions/2024-11-08-20-58_895fe80117aa.py b/keep/api/models/db/migrations/versions/2024-11-08-20-58_895fe80117aa.py index fb11c774d..957c84b2a 100644 --- a/keep/api/models/db/migrations/versions/2024-11-08-20-58_895fe80117aa.py +++ b/keep/api/models/db/migrations/versions/2024-11-08-20-58_895fe80117aa.py @@ -18,24 +18,24 @@ def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - - with op.batch_alter_table("alertraw", schema=None) as batch_op: - batch_op.add_column( - sa.Column( - "timestamp", - sa.DateTime(), - nullable=False, - server_default=sa.text("CURRENT_TIMESTAMP"), - ) - ) - batch_op.add_column(sa.Column("provider_type", sa.String(255), nullable=True)) - + op.add_column( + "alertraw", + sa.Column( + "timestamp", + sa.DateTime(), + nullable=False, + server_default=sa.text("CURRENT_TIMESTAMP"), + ), + ) + op.add_column( + "alertraw", + sa.Column("provider_type", sa.String(255), nullable=True), + ) # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table("alertraw", schema=None) as batch_op: - batch_op.drop_column("timestamp") - batch_op.drop_column("provider_type") + op.drop_column("alertraw", "provider_type") + op.drop_column("alertraw", "timestamp") # ### end Alembic commands ### diff --git a/keep/providers/victoriametrics_provider/victoriametrics_provider.py b/keep/providers/victoriametrics_provider/victoriametrics_provider.py index 18e0eaec3..f8a140924 100644 --- a/keep/providers/victoriametrics_provider/victoriametrics_provider.py +++ b/keep/providers/victoriametrics_provider/victoriametrics_provider.py @@ -25,20 +25,36 @@ class VictoriametricsProviderAuthConfig: vmalert authentication configuration. """ - VMAlertHost: str = dataclasses.field( + VMAlertHost: str | None = dataclasses.field( metadata={ - "required": True, + "required": False, "description": "The hostname or IP address where VMAlert is running. This can be a local or remote server address.", "hint": "Example: 'localhost', '192.168.1.100', or 'vmalert.mydomain.com'", + "config_sub_group": "host", + "config_main_group": "address", }, + default=None, ) VMAlertPort: int = dataclasses.field( metadata={ - "required": True, + "required": False, "description": "The port number on which VMAlert is listening. This should match the port configured in your VMAlert setup.", - "hint": "Example: 8880 (if VMAlert is set to listen on port 8880)", + "hint": "Example: 8880 (if VMAlert is set to listen on port 8880), defaults to 8880", + "config_sub_group": "host", + "config_main_group": "address", }, + default=8880, + ) + + VMAlertURL: str | None = dataclasses.field( + metadata={ + "required": False, + "description": "The full URL to the VMAlert instance. For example: http://vmalert.mydomain.com:8880", + "config_sub_group": "url", + "config_main_group": "address", + }, + default=None, ) @@ -91,9 +107,7 @@ class VictoriametricsProvider(BaseProvider): } def validate_scopes(self) -> dict[str, bool | str]: - response = requests.get( - f"{self.vmalert_host}:{self.authentication_config.VMAlertPort}" - ) + response = requests.get(self.vmalert_host) if response.status_code == 200: connected_to_client = True self.logger.info("Connected to client successfully") @@ -128,6 +142,11 @@ def validate_config(self): self.authentication_config = VictoriametricsProviderAuthConfig( **self.config.authentication ) + if ( + self.authentication_config.VMAlertURL is None + and self.authentication_config.VMAlertHost is None + ): + raise Exception("VMAlertURL or VMAlertHost is required") @property def vmalert_host(self): @@ -135,29 +154,35 @@ def vmalert_host(self): if self._host: return self._host.rstrip("/") + host = None + + if self.authentication_config.VMAlertURL is not None: + host = self.authentication_config.VMAlertURL + else: + host = f"{self.authentication_config.VMAlertHost}:{self.authentication_config.VMAlertPort}" + # if the user explicitly supplied a host with http/https, use it - if self.authentication_config.VMAlertHost.startswith( - "http://" - ) or self.authentication_config.VMAlertHost.startswith("https://"): - self._host = self.authentication_config.VMAlertHost - return self.authentication_config.VMAlertHost.rstrip("/") + if host.startswith("http://") or host.startswith("https://"): + self._host = host + return host.rstrip("/") # otherwise, try to use https: try: + url = f"https://{host}" requests.get( - f"https://{self.authentication_config.VMAlertHost}:{self.authentication_config.VMAlertPort}", + url, verify=False, ) self.logger.debug("Using https") - self._host = f"https://{self.authentication_config.VMAlertHost}" + self._host = f"https://{host}" return self._host.rstrip("/") except requests.exceptions.SSLError: self.logger.debug("Using http") - self._host = f"http://{self.authentication_config.VMAlertHost}" + self._host = f"http://{host}" return self._host.rstrip("/") # should happen only if the user supplied invalid host, so just let validate_config fail except Exception: - return self.authentication_config.VMAlertHost.rstrip("/") + return host.rstrip("/") @staticmethod def _format_alert( @@ -185,9 +210,7 @@ def _format_alert( return alerts def _get_alerts(self) -> list[AlertDto]: - response = requests.get( - f"{self.vmalert_host}:{self.authentication_config.VMAlertPort}/api/v1/alerts" - ) + response = requests.get(f"{self.vmalert_host}/api/v1/alerts") if response.status_code == 200: alerts = [] response = response.json() @@ -217,7 +240,7 @@ def _get_alerts(self) -> list[AlertDto]: def _query(self, query="", start="", end="", step="", queryType="", **kwargs: dict): if queryType == "query": response = requests.get( - f"{self.vmalert_host}:{self.authentication_config.VMAlertPort}/api/v1/query", + f"{self.vmalert_host}/api/v1/query", params={"query": query, "time": start}, ) if response.status_code == 200: @@ -230,7 +253,7 @@ def _query(self, query="", start="", end="", step="", queryType="", **kwargs: di elif queryType == "query_range": response = requests.get( - f"{self.vmalert_host}:{self.authentication_config.VMAlertPort}/api/v1/query_range", + f"{self.vmalert_host}/api/v1/query_range", params={"query": query, "start": start, "end": end, "step": step}, ) if response.status_code == 200: