Skip to content

Commit

Permalink
Merge branch 'main' into feat-add-last-updated-timestamp-to-mappings
Browse files Browse the repository at this point in the history
  • Loading branch information
talboren authored Apr 21, 2024
2 parents 58cbd6c + 08e7883 commit fa43730
Show file tree
Hide file tree
Showing 10 changed files with 309 additions and 1 deletion.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ Workflow triggers can either be executed manually when an alert is activated or
<img width=32 height=32 src="https://github.com/keephq/keep/blob/main/keep-ui/public/icons/discord-icon.png?raw=true"/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<img width=32 height=32 src="https://github.com/keephq/keep/blob/main/keep-ui/public/icons/twilio-icon.png?raw=true"/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<img width=32 height=32 src="https://github.com/keephq/keep/blob/main/keep-ui/public/icons/ntfy-icon.png?raw=true"/>
</p>
<h3 align="center">Incident Management tools</h2>
<p align="center">
Expand Down
1 change: 1 addition & 0 deletions docs/mint.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
"providers/documentation/mock-provider",
"providers/documentation/mysql-provider",
"providers/documentation/new-relic-provider",
"providers/documentation/ntfy-provider",
"providers/documentation/openshift-provider",
"providers/documentation/opsgenie-provider",
"providers/documentation/pagerduty-provider",
Expand Down
42 changes: 42 additions & 0 deletions docs/providers/documentation/ntfy-provider.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
title: 'Ntfy.sh'
sidebarTitle: 'Ntfy.sh Provider'
description: 'Ntfy.sh allows you to send notifications to your devices'
---

## Authentication Parameters

The Ntfy.sh provider requires the following authentication parameters:

- `Ntfy Access Token`: The access token for the Ntfy.sh account. This is required for the Ntfy.sh provider.
- `Ntfy Host URL`: (For self-hosted Ntfy) The URL of the self-hosted Ntfy instance in the format `https://ntfy.example.com`.
- `Ntfy Username`: (For self-hosted Ntfy) The username for the self-hosted Ntfy instance.
- `Ntfy Password`: (For self-hosted Ntfy) The password for the self-hosted Ntfy instance.
- `Ntfy Subcription Topic`: Required. The topic to which the notification will be sent.

## Connecting with the Provider

Obtain Ntfy Access Token (For Ntfy.sh only)

1. Create an account on [Ntfy.sh](https://ntfy.sh/).
2. After logging in, go to the [Access token](https://ntfy.sh/account) page.
3. Click on the `CREATE ACCESS TOKEN`. Give it a label and select token expiration time and click on the `CREATE TOKEN` button.
4. Copy the generated token. This will be used as the `Ntfy Access Token` in the provider settings.

Self-Hosted Ntfy

1. To self-host Ntfy, you can follow the instructions [here](https://docs.ntfy.sh/install/).
2. For self-hosted Ntfy, you will need to provide the `Ntfy Host URL`, `Ntfy Username`, and `Ntfy Password` in the provider settings instead of the `Ntfy Access Token`.
3. Create a new user for the self-hosted Ntfy instance and use the generated username and password in the provider settings.

Subscribing to a Topic (For Ntfy.sh and self-hosted Ntfy)

1. Login to your Ntfy.sh account.
2. Click on `Subscribe to a topic` button and generate name for the topic and subscribe to it.
3. Copy the generated topic name. This will be used as the `Ntfy Subcription Topic` in the provider settings.
4. Reserve the topic and confiure access (Requires ntfy Pro)

## Usefull Links

- [Ntfy.sh](https://ntfy.sh/)
- [To self-host Ntfy](https://docs.ntfy.sh/install/)
Binary file added keep-ui/public/icons/ntfy-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions keep/api/routes/alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,7 @@ def handle_formatted_events(
async def receive_generic_event(
event: AlertDto | list[AlertDto] | dict,
bg_tasks: BackgroundTasks,
fingerprint: str | None = None,
authenticated_entity: AuthenticatedEntity = Depends(AuthVerifier(["write:alert"])),
session: Session = Depends(get_session),
pusher_client: Pusher = Depends(get_pusher_client),
Expand Down Expand Up @@ -674,6 +675,9 @@ async def receive_generic_event(
if not _alert.source:
_alert.source = ["keep"]

if fingerprint:
_alert.fingerprint = fingerprint

if authenticated_entity.api_key_name:
_alert.apiKeyRef = authenticated_entity.api_key_name

Expand All @@ -698,6 +702,7 @@ async def receive_event(
request: Request,
bg_tasks: BackgroundTasks,
provider_id: str | None = None,
fingerprint: str | None = None,
authenticated_entity: AuthenticatedEntity = Depends(AuthVerifier(["write:alert"])),
session: Session = Depends(get_session),
pusher_client: Pusher = Depends(get_pusher_client),
Expand Down Expand Up @@ -765,6 +770,9 @@ async def receive_event(
formatted_events = provider_class.format_alert(event, provider_instance)

if isinstance(formatted_events, AlertDto):
# override the fingerprint if it's provided
if fingerprint:
formatted_events.fingerprint = fingerprint
formatted_events = [formatted_events]

logger.info(
Expand Down
9 changes: 9 additions & 0 deletions keep/providers/ntfy_provider/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## Change the variables

1. Change the UID:GID in the docker-compose file to match your user and group id.

## Run the docker-compose file

```bash
docker-compose up -d
```
Empty file.
27 changes: 27 additions & 0 deletions keep/providers/ntfy_provider/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
version: '2.3'

services:
ntfy:
image: binwiederhier/ntfy
container_name: ntfy
command:
- serve
environment:
- TZ=UTC # optional: set desired timezone
user: UID:GID # optional: replace with your own user/group or uid/gid
volumes:
- /var/cache/ntfy:/var/cache/ntfy
- /etc/ntfy:/etc/ntfy
ports:
- 80:80
healthcheck: # optional: remember to adapt the host:port to your environment
test:
[
'CMD-SHELL',
"wget -q --tries=1 http://localhost:80/v1/health -O - | grep -Eo '\"healthy\"\\s*:\\s*true' || exit 1",
]
interval: 60s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
219 changes: 219 additions & 0 deletions keep/providers/ntfy_provider/ntfy_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
"""
NtfyProvider is a class that provides a way to send notifications to the user.
"""

import dataclasses

import pydantic
import base64
import requests
from urllib.parse import urljoin

from keep.contextmanager.contextmanager import ContextManager
from keep.exceptions.provider_exception import ProviderException
from keep.providers.base.base_provider import BaseProvider
from keep.providers.models.provider_config import ProviderConfig, ProviderScope


@pydantic.dataclasses.dataclass
class NtfyProviderAuthConfig:
"""
NtfyProviderAuthConfig is a class that holds the authentication information for the NtfyProvider.
"""

access_token: str = dataclasses.field(
metadata={
"required": False,
"description": "Ntfy Access Token",
"sensitive": True,
},
default=None,
)

host: str = dataclasses.field(
metadata={
"required": False,
"description": "Ntfy Host URL (For self-hosted Ntfy only)",
"sensitive": False,
},
default=None,
)

username: str = dataclasses.field(
metadata={
"required": False,
"description": "Ntfy Username (For self-hosted Ntfy only)",
"sensitive": False,
},
default=None,
)

password: str = dataclasses.field(
metadata={
"required": False,
"description": "Ntfy Password (For self-hosted Ntfy only)",
"sensitive": True,
},
default=None,
)

subcription_topic: str = dataclasses.field(
metadata={
"required": True,
"description": "Ntfy Subcription Topic",
"sensitive": False,
},
default=None,
)


class NtfyProvider(BaseProvider):
PROVIDER_DISPLAY_NAME = "Ntfy.sh"

PROVIDER_SCOPES = [
ProviderScope(
name="send_alert",
mandatory=True,
alias="Send Alert",
)
]

def __init__(
self, context_manager: ContextManager, provider_id: str, config: ProviderConfig
):
super().__init__(context_manager, provider_id, config)

def dispose(self):
pass

def validate_scopes(self) -> dict[str, bool | str]:
validated_scopes = {}
validated_scopes["send_alert"] = True
return validated_scopes

def validate_config(self):
self.authentication_config = NtfyProviderAuthConfig(
**self.config.authentication
)
if self.authentication_config.access_token is None and self.authentication_config.host is None:
raise ProviderException(
"Either Access Token or Host is required"
)
if self.authentication_config.subcription_topic is None:
raise ProviderException(
"Subcription Topic is required"
)
if self.authentication_config.host is not None:
if self.authentication_config.host.startswith("https://") or self.authentication_config.host.startswith("http://"):
raise ProviderException(
"Host should not start with https:// or http://"
)
if self.authentication_config.username is None:
raise ProviderException(
"Username is required when host is provided"
)
if self.authentication_config.password is None:
raise ProviderException(
"Password is required when host is provided"
)

def __get_auth_headers(self):
if self.authentication_config.access_token is not None:
return {
"Authorization": f"Bearer {self.authentication_config.access_token}"
}

else:
username = self.authentication_config.username
password = self.authentication_config.password
token = base64.b64encode(f"{username}:{password}".encode("utf-8")).decode("utf-8")

return {
"Authorization": f"Basic {token}"
}

def __send_alert(self, message=""):
self.logger.debug(f"Sending notification to {self.authentication_config.subcription_topic}")

NTFY_SUBSCRIPTION_TOPIC = self.authentication_config.subcription_topic

if self.authentication_config.host is not None:
base_url = self.authentication_config.host
if not base_url.endswith("/"):
base_url += "/"
NTFY_URL = urljoin(base=base_url, url=NTFY_SUBSCRIPTION_TOPIC)
else:
NTFY_URL = urljoin(base="https://ntfy.sh/", url=NTFY_SUBSCRIPTION_TOPIC)

try:
response = requests.post(NTFY_URL, headers=self.__get_auth_headers(), data=message)

if response.status_code == 401:
raise ProviderException(
f"Failed to send notification to {NTFY_URL}. Error: Unauthorized"
)

response.raise_for_status()
return response.json()

except Exception as e:
raise ProviderException(
f"Failed to send notification to {NTFY_URL}. Error: {e}"
)

def _notify(self, message=""):
return self.__send_alert(message)

if __name__ == "__main__":
import logging

logging.basicConfig(level=logging.DEBUG, handlers=[logging.StreamHandler()])
context_manager = ContextManager(
tenant_id="singletenant",
workflow_id="test",
)

import os

ntfy_access_token = os.environ.get("NTFY_ACCESS_TOKEN")
ntfy_host = os.environ.get("NTFY_HOST")
ntfy_username = os.environ.get("NTFY_USERNAME")
ntfy_password = os.environ.get("NTFY_PASSWORD")
ntfy_subscription_topic = os.environ.get("NTFY_SUBSCRIPTION_TOPIC")

if ntfy_access_token is None and ntfy_host is None:
raise Exception("NTFY_ACCESS_TOKEN or NTFY_HOST is required")

if ntfy_host is not None:
if ntfy_username is None:
raise Exception("NTFY_USERNAME is required")
if ntfy_password is None:
raise Exception("NTFY_PASSWORD is required")

if ntfy_access_token is not None:
config = ProviderConfig(
description="Ntfy Provider",
authentication={
"access_token": ntfy_access_token,
"subcription_topic": ntfy_subscription_topic,
},
)

else:
config = ProviderConfig(
description="Ntfy Provider",
authentication={
"host": ntfy_host,
"username": ntfy_username,
"password": ntfy_password,
"subcription_topic": ntfy_subscription_topic,
},
)

provider = NtfyProvider(
context_manager,
provider_id="ntfy-keephq",
config=config,
)

provider.notify(message="Test message from Keephq")
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.4.0"
version = "0.5.0"
description = "Alerting. for developers, by developers."
authors = ["Keep Alerting LTD"]
readme = "README.md"
Expand Down

0 comments on commit fa43730

Please sign in to comment.