Skip to content

Commit

Permalink
Merge branch 'main' into feat-pd-app
Browse files Browse the repository at this point in the history
  • Loading branch information
35C4n0r authored Nov 11, 2024
2 parents 7573aac + 3d6d5f1 commit 5a5b19a
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 54 deletions.
30 changes: 18 additions & 12 deletions docs/providers/documentation/victoriametrics-provider.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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`

Expand All @@ -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
Expand Down
21 changes: 16 additions & 5 deletions keep/api/core/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# Just a fake random tenant id
SINGLE_TENANT_UUID = "keep"
SINGLE_TENANT_EMAIL = "admin@keephq"
PUSHER_DISABLED = os.environ.get("PUSHER_DISABLED", "false") == "true"


async def extract_generic_body(request: Request) -> dict | bytes | FormData:
Expand All @@ -36,20 +37,30 @@ async def extract_generic_body(request: Request) -> dict | bytes | FormData:


def get_pusher_client() -> Pusher | None:
if os.environ.get("PUSHER_DISABLED", "false") == "true":
pusher_host = os.environ.get("PUSHER_HOST")
pusher_app_id = os.environ.get("PUSHER_APP_ID")
pusher_app_key = os.environ.get("PUSHER_APP_KEY")
pusher_app_secret = os.environ.get("PUSHER_APP_SECRET")
if (
PUSHER_DISABLED
or pusher_app_id is None
or pusher_app_key is None
or pusher_app_secret is None
):
logger.debug("Pusher is disabled or missing environment variables")
return None

# TODO: defaults on open source no docker
return Pusher(
host=os.environ.get("PUSHER_HOST"),
host=pusher_host,
port=(
int(os.environ.get("PUSHER_PORT"))
if os.environ.get("PUSHER_PORT")
else None
),
app_id=os.environ.get("PUSHER_APP_ID"),
key=os.environ.get("PUSHER_APP_KEY"),
secret=os.environ.get("PUSHER_APP_SECRET"),
app_id=pusher_app_id,
key=pusher_app_key,
secret=pusher_app_secret,
ssl=False if os.environ.get("PUSHER_USE_SSL", False) is False else True,
cluster=os.environ.get("PUSHER_CLUSTER"),
)
Original file line number Diff line number Diff line change
Expand Up @@ -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 ###
5 changes: 5 additions & 0 deletions keep/providers/servicenow_provider/servicenow_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def validate_scopes(self):
Validates that the user has the required scopes to use the provider.
"""
try:
self.logger.info("Validating ServiceNow scopes")
url = f"{self.authentication_config.service_now_base_url}/api/now/table/sys_user_role?sysparm_query=user_name={self.authentication_config.username}"
response = requests.get(
url,
Expand All @@ -86,19 +87,23 @@ def validate_scopes(self):
self.authentication_config.password,
),
verify=False,
timeout=10,
)
if response.status_code == 200:
roles = response.json()
roles_names = [role.get("name") for role in roles.get("result")]
if "itil" in roles_names:
self.logger.info("User has ITIL role")
scopes = {
"itil": True,
}
else:
self.logger.info("User does not have ITIL role")
scopes = {
"itil": "This user does not have the ITIL role",
}
else:
self.logger.info("Failed to get roles from ServiceNow")
scopes["itil"] = "Failed to get roles from ServiceNow"
except Exception as e:
self.logger.exception("Error validating scopes")
Expand Down
65 changes: 44 additions & 21 deletions keep/providers/victoriametrics_provider/victoriametrics_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)


Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -128,36 +142,47 @@ 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):
# if not the first time, return the cached host
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(
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "keep"
version = "0.28.4"
version = "0.28.6"
description = "Alerting. for developers, by developers."
authors = ["Keep Alerting LTD"]
readme = "README.md"
Expand Down

0 comments on commit 5a5b19a

Please sign in to comment.