-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds toolset to query loki logs proxying through grafana. Some env vars configure the access to grafana (url, username & api key). The fields used to get pods and nodes logs are configurable through the config file. --------- Co-authored-by: Avi-Robusta <[email protected]> Co-authored-by: Mohse Morad <[email protected]>
- Loading branch information
1 parent
bfab843
commit 79820ce
Showing
16 changed files
with
1,748 additions
and
646 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -598,9 +598,9 @@ If your llm provider url uses a certificate from a custom CA, in order to trust | |
<summary>Confluence</summary> | ||
HolmesGPT can read runbooks from Confluence. To give it access, set the following environment variables: | ||
|
||
* CONFLUENCE_BASE_URL - e.g. https://robusta-dev-test.atlassian.net | ||
* CONFLUENCE_USER - e.g. [email protected] | ||
* CONFLUENCE_API_KEY - [refer to Atlassian docs on generating API keys](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/) | ||
* `CONFLUENCE_BASE_URL` - e.g. https://robusta-dev-test.atlassian.net | ||
* `CONFLUENCE_USER` - e.g. [email protected] | ||
* `CONFLUENCE_API_KEY` - [refer to Atlassian docs on generating API keys](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/) | ||
</details> | ||
|
||
<details> | ||
|
@@ -624,13 +624,59 @@ This is done through a HTTP GET and the resulting HTML is then cleaned and parse | |
Any Javascript that is on the webpage is ignored. | ||
</details> | ||
|
||
<details> | ||
<summary> | ||
Using Grafana Loki | ||
</summary> | ||
|
||
HolmesGPT can consult logs from [Loki](https://grafana.com/oss/loki/) by proxying through a [Grafana](https://grafana.com/oss/grafana/) instance. | ||
|
||
There are 2 parts to configuring access to Grafana Loki: Access/Authentication and search terms. | ||
|
||
For access and authentication, add the following environment variables: | ||
|
||
* `GRAFANA_URL` - e.g. https://my-org.grafana.net | ||
* `GRAFANA_API_KEY` - e.g. glsa_bsm6ZS_sdfs25f | ||
|
||
For search terms, you can optionally tweak the search terms used by the toolset. | ||
This is done by appending the following to your Holmes configuration file: | ||
|
||
```yaml | ||
grafana: | ||
url: https://my-org.grafana.net # | ||
api_key: glsa_bsm6ZS_sdfs25f | ||
loki: | ||
pod_name_search_key: "pod" | ||
namespace_search_key: "namespace" | ||
node_name_search_key: "node" | ||
``` | ||
> You only need to tweak the configuration file if your Loki logs settings for pod, namespace and node differ from the above defaults. | ||
The Loki toolset is configured the using the same Grafana settings as the Grafana Tempo toolset. | ||
</details> | ||
<summary> | ||
Using Grafana Tempo | ||
</summary> | ||
HolmesGPT can fetch trace information from Grafana Tempo to debug performance related issues. | ||
Tempo is configured the using the same Grafana settings as the Grafana Loki toolset. | ||
grafana: | ||
url: https://my-org.grafana.net # | ||
</details> | ||
<details> | ||
<summary> | ||
ArgoCD | ||
</summary> | ||
Holmes can use the `argocd` CLI to get details about the ArgoCD setup like the apps configuration and status, clusters and projects within ArgoCD. | ||
To enable ArgoCD, set the `ARGOCD_AUTH_TOKEN` environment variable as described in the [argocd documentation](https://argo-cd.readthedocs.io/en/latest/user-guide/commands/argocd_account_generate-token/). | ||
|
||
</details> | ||
|
||
## More Use Cases | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import logging | ||
from typing import Any | ||
from holmes.core.tools import ( | ||
Tool, | ||
Toolset, | ||
ToolsetTag, | ||
CallablePrerequisite, | ||
) | ||
from holmes.plugins.toolsets.grafana.common import GrafanaConfig | ||
from holmes.plugins.toolsets.grafana.grafana_api import get_health | ||
|
||
|
||
class BaseGrafanaToolset(Toolset): | ||
def __init__(self, name: str, description: str, icon_url: str, tools: list[Tool]): | ||
super().__init__( | ||
name=name, | ||
description=description, | ||
icon_url=icon_url, | ||
prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)], | ||
tools=tools, | ||
tags=[ | ||
ToolsetTag.CORE, | ||
], | ||
enabled=False | ||
) | ||
|
||
def prerequisites_callable(self, config: dict[str, Any]) -> bool: | ||
if not config: | ||
logging.warning("Grafana config not provided") | ||
return False | ||
|
||
try: | ||
self._grafana_config = GrafanaConfig(**config) | ||
is_healthy = get_health(self._grafana_config.url, self._grafana_config.api_key) | ||
return is_healthy | ||
|
||
except Exception: | ||
logging.exception("Failed to set up grafana toolset") | ||
return False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
from typing import Dict, Optional, Union | ||
import uuid | ||
import time | ||
import os | ||
from pydantic import BaseModel | ||
|
||
|
||
GRAFANA_URL_ENV_NAME = "GRAFANA_URL" | ||
GRAFANA_API_KEY_ENV_NAME = "GRAFANA_API_KEY" | ||
ONE_HOUR_IN_SECONDS = 3600 | ||
|
||
|
||
class GrafanaLokiConfig(BaseModel): | ||
pod_name_search_key: str = "pod" | ||
namespace_search_key: str = "namespace" | ||
node_name_search_key: str = "node" | ||
|
||
|
||
class GrafanaConfig(BaseModel): | ||
loki: GrafanaLokiConfig = GrafanaLokiConfig() | ||
api_key: str | ||
url: str | ||
|
||
|
||
def headers(api_key: str): | ||
return { | ||
"Authorization": f"Bearer {api_key}", | ||
"Accept": "application/json", | ||
"Content-Type": "application/json", | ||
} | ||
|
||
|
||
def process_timestamps( | ||
start_timestamp: Optional[Union[int, str]], end_timestamp: Optional[Union[int, str]] | ||
): | ||
if start_timestamp and isinstance(start_timestamp, str): | ||
start_timestamp = int(start_timestamp) | ||
if end_timestamp and isinstance(end_timestamp, str): | ||
end_timestamp = int(end_timestamp) | ||
|
||
if not end_timestamp: | ||
end_timestamp = int(time.time()) | ||
if not start_timestamp: | ||
start_timestamp = end_timestamp - ONE_HOUR_IN_SECONDS | ||
if start_timestamp < 0: | ||
start_timestamp = end_timestamp + start_timestamp | ||
return (start_timestamp, end_timestamp) | ||
|
||
|
||
def get_param_or_raise(dict: Dict, param: str) -> str: | ||
value = dict.get(param) | ||
if not value: | ||
raise Exception(f'Missing param "{param}"') | ||
return value | ||
|
||
|
||
def get_datasource_id(dict: Dict, param: str) -> str: | ||
datasource_id = get_param_or_raise(dict, param) | ||
try: | ||
if uuid.UUID(datasource_id, version=4): | ||
return f"uid/{datasource_id}" | ||
except: | ||
pass | ||
|
||
return datasource_id |
Oops, something went wrong.