Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion keep/api/bl/enrichments_bl.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import uuid
from uuid import UUID

import celpy
import CelPy as celpy
import chevron
import json5
from elasticsearch import NotFoundError
Expand Down
4 changes: 4 additions & 0 deletions keep/providers/base/base_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ def notify(self, **kwargs):
Args:
**kwargs (dict): The provider context (with statement)
"""
# Clear results to avoid accumulation when provider is reused across actions/steps
self.results = []
# Pop Keep-internal fields before passing kwargs to the provider
enrich_alert = kwargs.pop("enrich_alert", [])
enrich_incident = kwargs.pop("enrich_incident", [])
Expand Down Expand Up @@ -387,6 +389,8 @@ def _query(self, **kwargs: dict):
raise NotImplementedError("query() method not implemented")

def query(self, **kwargs: dict):
# Clear results to avoid accumulation when provider is reused across actions/steps
self.results = []
# Pop Keep-internal fields before passing kwargs to the provider
enrich_alert = kwargs.pop("enrich_alert", [])
audit_enabled = bool(kwargs.pop("audit_enabled", True))
Expand Down
3 changes: 3 additions & 0 deletions keep/providers/cisco_webex_provider/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .cisco_webex_provider import CiscoWebexProvider

__all__ = ["CiscoWebexProvider"]
178 changes: 178 additions & 0 deletions keep/providers/cisco_webex_provider/cisco_webex_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
"""
CiscoWebexProvider is a notification provider for Cisco Webex.
API: https://developer.webex.com/docs/api/v1/messages
"""

import dataclasses
from typing import Optional

import pydantic
import requests

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


@pydantic.dataclasses.dataclass
class CiscoWebexProviderAuthConfig:
"""Cisco Webex authentication configuration."""

access_token: str = dataclasses.field(
metadata={
"required": True,
"description": "Cisco Webex Bot Access Token (Bearer token)",
"sensitive": True,
}
)
room_id: str = dataclasses.field(
metadata={
"required": True,
"description": "Cisco Webex Room ID (e.g., YJIxqqqBRfwL9k3n8z3k9z3k9z3k9z3k)",
}
)


class CiscoWebexProvider(BaseProvider):
"""Send alert messages to Cisco Webex rooms via bot API."""

PROVIDER_DISPLAY_NAME = "Cisco Webex"
PROVIDER_CATEGORY = ["Collaboration"]
PROVIDER_LINK = "https://webex.com/"
PROVIDER_DESCRIPTION = "Send Keep alerts to Cisco Webex rooms via bot API"
IS_TESTABLE = True
WEBEX_API = "https://webexapis.com/v1/messages"

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

def validate_config(self):
self.authentication_config = CiscoWebexProviderAuthConfig(
**self.config.authentication
)

def dispose(self):
"""No cleanup needed."""
pass

def _notify(
self,
message: str = "",
title: str = "",
**kwargs: dict,
):
"""Send notification to Cisco Webex room."""
self.logger.debug("Sending message to Cisco Webex")

if not message:
raise ProviderException(
f"{self.__class__.__name__} failed to send message: message body is required"
)

display_message = message
if title:
display_message = f"## {title}\n\n{message}"

url = CiscoWebexProvider.WEBEX_API
payload = {
"roomId": self.authentication_config.room_id,
"text": display_message,
"markdown": display_message,
}

headers = {
"Authorization": f"Bearer {self.authentication_config.access_token}",
"Content-Type": "application/json",
}

try:
response = requests.post(
url,
json=payload,
headers=headers,
timeout=30,
)

if response.status_code not in (200, 201):
error_msg = response.text
raise ProviderException(
f"{self.__class__.__name__} failed to send Webex message: "
f"HTTP {response.status_code} - {error_msg[:200]}"
)

self.logger.info(f"Message sent to Webex room {self.authentication_config.room_id}")
return True

except requests.exceptions.ConnectionError as e:
raise ProviderException(
f"{self.__class__.__name__} failed to connect to Webex: {str(e)}"
)
except requests.exceptions.Timeout:
raise ProviderException(
f"{self.__class__.__name__} connection to Webex timed out"
)
except requests.exceptions.RequestException as e:
raise ProviderException(
f"{self.__class__.__name__} failed to send Webex message: {str(e)}"
)

def test(self):
"""Test the Cisco Webex connection."""
self.logger.debug("Testing Cisco Webex connection")

if not self.authentication_config:
self.validate_config()

url = CiscoWebexProvider.WEBEX_API
payload = {
"roomId": self.authentication_config.room_id,
"text": "KeepHQ test notification - connection successful!",
}

headers = {
"Authorization": f"Bearer {self.authentication_config.access_token}",
"Content-Type": "application/json",
}

try:
response = requests.post(
url,
json=payload,
headers=headers,
timeout=30,
)

if response.status_code in (200, 201):
return {
"ok": True,
"message": "Webex connection successful - test message sent",
}
elif response.status_code == 401:
return {
"ok": False,
"message": "Webex connection test failed: Invalid token (401 Unauthorized)",
}
elif response.status_code == 404:
return {
"ok": False,
"message": "Webex connection test failed: Room not found",
}
else:
return {
"ok": False,
"message": f"Webex connection test failed: HTTP {response.status_code} - {response.text[:200]}",
}

except requests.exceptions.ConnectionError:
return {
"ok": False,
"message": f"Cannot connect to Webex API",
}
except Exception as e:
return {
"ok": False,
"message": f"Webex connection test failed: {str(e)}",
}
1 change: 1 addition & 0 deletions keep/providers/flock_provider/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .flock_provider import FlockProvider
4 changes: 4 additions & 0 deletions keep/providers/flock_provider/assets/flock.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading