diff --git a/docs/configuration/sinks/index.rst b/docs/configuration/sinks/index.rst index 3d9be6734..4bdabf6e6 100644 --- a/docs/configuration/sinks/index.rst +++ b/docs/configuration/sinks/index.rst @@ -129,6 +129,11 @@ Click a sink for setup instructions. :link: pushover :link-type: doc + .. grid-item-card:: :octicon:`cpu;1em;` Plivo + :class-card: sd-bg-light sd-bg-text-light + :link: plivo + :link-type: doc + .. grid-item-card:: :octicon:`cpu;1em;` ServiceNow :class-card: sd-bg-light sd-bg-text-light :link: ServiceNow diff --git a/docs/configuration/sinks/plivo.rst b/docs/configuration/sinks/plivo.rst new file mode 100644 index 000000000..768596f90 --- /dev/null +++ b/docs/configuration/sinks/plivo.rst @@ -0,0 +1,51 @@ +Plivo +################# + +.. admonition:: This page documents a legacy sink in Robusta classic + :class: warning + + For new setups, we recommend `HolmesGPT `_ instead. + + HolmesGPT triages your alerts instead of just forwarding them. Sinks are deterministic: they send every notification, unchanged, to a fixed destination, leaving you to read and prioritize each one yourself. + + HolmesGPT instead uses AI to investigate each alert, surface the likely root cause, and escalate only what needs attention — so you get fewer, more actionable notifications. Set this up with `Alerts Triage `_ for alerts, or :ref:`Triggered Workflows ` for custom events. + + +Robusta can send SMS notifications about issues and events in your Kubernetes cluster to a phone number using Plivo. + +Getting your Auth ID and Auth Token +------------------------------------------------ +Log in to the Plivo console at `cx.plivo.com `_. Your Auth ID and Auth Token are shown on the dashboard. + +Getting a sender number +------------------------------------------------ +You need an SMS enabled Plivo phone number to send from. Buy or view your numbers in the Plivo console under Phone Numbers. + +Configuring the Plivo sink +------------------------------------------------ +Now we're ready to configure the Plivo sink. + +.. admonition:: Add this to your generated_values.yaml + + .. code-block:: yaml + + sinksConfig: + - plivo_sink: + name: plivo_sink + auth_id: + auth_token: + from_number: # E.164 format, e.g. +14155551234 + to_number: # E.164 format; for several recipients join them with '<' + +.. note:: + + Numbers use E.164 format. To send to more than one recipient, join the numbers with ``<`` (for example ``+14155551234<+14155555678``). + +Save the file and run + +.. code-block:: bash + :name: cb-add-plivo-sink + + helm upgrade robusta robusta/robusta --values=generated_values.yaml + +You should now get playbook results as SMS messages from Plivo. diff --git a/src/robusta/core/model/runner_config.py b/src/robusta/core/model/runner_config.py index 5edb08b8e..db5ca0b83 100644 --- a/src/robusta/core/model/runner_config.py +++ b/src/robusta/core/model/runner_config.py @@ -25,6 +25,7 @@ from robusta.core.sinks.webex.webex_sink_params import WebexSinkConfigWrapper from robusta.core.sinks.webhook.webhook_sink_params import WebhookSinkConfigWrapper from robusta.core.sinks.yamessenger.yamessenger_sink_params import YaMessengerSinkConfigWrapper +from robusta.core.sinks.plivo.plivo_sink_params import PlivoSinkConfigWrapper from robusta.core.sinks.pushover.pushover_sink_params import PushoverSinkConfigWrapper from robusta.core.sinks.zulip.zulip_sink_params import ZulipSinkConfigWrapper from robusta.model.alert_relabel_config import AlertRelabel @@ -74,6 +75,7 @@ class RunnerConfig(BaseModel): FileSinkConfigWrapper, MailSinkConfigWrapper, PushoverSinkConfigWrapper, + PlivoSinkConfigWrapper, GoogleChatSinkConfigWrapper, ServiceNowSinkConfigWrapper, ZulipSinkConfigWrapper, diff --git a/src/robusta/core/sinks/plivo/__init__.py b/src/robusta/core/sinks/plivo/__init__.py new file mode 100644 index 000000000..efbe7af25 --- /dev/null +++ b/src/robusta/core/sinks/plivo/__init__.py @@ -0,0 +1,2 @@ +from robusta.core.sinks.plivo.plivo_sink import PlivoSink +from robusta.core.sinks.plivo.plivo_sink_params import PlivoSinkConfigWrapper, PlivoSinkParams diff --git a/src/robusta/core/sinks/plivo/plivo_client.py b/src/robusta/core/sinks/plivo/plivo_client.py new file mode 100644 index 000000000..d77960f58 --- /dev/null +++ b/src/robusta/core/sinks/plivo/plivo_client.py @@ -0,0 +1,26 @@ +import logging + +import requests + +PLIVO_API_BASE = "https://api.plivo.com/v1/Account" + + +class PlivoClient: + def __init__(self, auth_id: str, auth_token: str): + self.auth_id = str(auth_id) + self.auth_token = str(auth_token) + + def send_message(self, src: str, dst: str, text: str): + url = f"{PLIVO_API_BASE}/{self.auth_id}/Message/" + payload = {"src": src, "dst": dst, "text": text, "type": "sms"} + + try: + response = requests.post( + url, json=payload, auth=(self.auth_id, self.auth_token), timeout=(5, 15) + ) + if not response.ok: + logging.error( + f"Failed to send Plivo SMS to {dst}: {response.status_code} {response.reason} {response.text}" + ) + except Exception as e: + logging.error(f"Error sending Plivo SMS to {dst}: {e}", exc_info=True) diff --git a/src/robusta/core/sinks/plivo/plivo_sink.py b/src/robusta/core/sinks/plivo/plivo_sink.py new file mode 100644 index 000000000..e73306d49 --- /dev/null +++ b/src/robusta/core/sinks/plivo/plivo_sink.py @@ -0,0 +1,38 @@ +from typing import List + +from robusta.core.reporting.base import Finding +from robusta.core.sinks.plivo.plivo_client import PlivoClient +from robusta.core.sinks.plivo.plivo_sink_params import PlivoSinkConfigWrapper +from robusta.core.sinks.sink_base import SinkBase + +MAX_SMS_LENGTH = 1600 + + +class PlivoSink(SinkBase): + def __init__(self, sink_config: PlivoSinkConfigWrapper, registry): + super().__init__(sink_config.plivo_sink, registry) + + self.client = PlivoClient( + sink_config.plivo_sink.auth_id, + sink_config.plivo_sink.auth_token.get_secret_value(), + ) + self.from_number = sink_config.plivo_sink.from_number + self.to_number = sink_config.plivo_sink.to_number + + def write_finding(self, finding: Finding, platform_enabled: bool): + message = self.__build_message(finding, platform_enabled) + self.client.send_message(src=self.from_number, dst=self.to_number, text=message) + + def __build_message(self, finding: Finding, platform_enabled: bool) -> str: + lines: List[str] = [f"{finding.severity.name} - {finding.title}"] + + if platform_enabled: + lines.append(f"Investigate: {finding.get_investigate_uri(self.account_id, self.cluster_name)}") + + lines.append(f"Source: {self.cluster_name}") + + if finding.description: + lines.append(finding.description) + + message = "\n".join(line for line in lines if line) + return message[:MAX_SMS_LENGTH] diff --git a/src/robusta/core/sinks/plivo/plivo_sink_params.py b/src/robusta/core/sinks/plivo/plivo_sink_params.py new file mode 100644 index 000000000..1f39e54c5 --- /dev/null +++ b/src/robusta/core/sinks/plivo/plivo_sink_params.py @@ -0,0 +1,22 @@ +from pydantic import SecretStr + +from robusta.core.sinks.sink_base_params import SinkBaseParams +from robusta.core.sinks.sink_config import SinkConfigBase + + +class PlivoSinkParams(SinkBaseParams): + auth_id: str + auth_token: SecretStr + from_number: str + to_number: str + + @classmethod + def _get_sink_type(cls): + return "plivo" + + +class PlivoSinkConfigWrapper(SinkConfigBase): + plivo_sink: PlivoSinkParams + + def get_params(self) -> SinkBaseParams: + return self.plivo_sink diff --git a/src/robusta/core/sinks/sink_factory.py b/src/robusta/core/sinks/sink_factory.py index 96ec8f3d3..76375b0ef 100644 --- a/src/robusta/core/sinks/sink_factory.py +++ b/src/robusta/core/sinks/sink_factory.py @@ -29,6 +29,7 @@ from robusta.core.sinks.webex import WebexSink, WebexSinkConfigWrapper from robusta.core.sinks.webhook import WebhookSink, WebhookSinkConfigWrapper from robusta.core.sinks.yamessenger import YaMessengerSink, YaMessengerSinkConfigWrapper +from robusta.core.sinks.plivo import PlivoSink, PlivoSinkConfigWrapper from robusta.core.sinks.pushover import PushoverSink, PushoverSinkConfigWrapper from robusta.core.sinks.zulip import ZulipSink, ZulipSinkConfigWrapper from robusta.core.sinks.incidentio.incidentio_sink import IncidentioSink @@ -56,6 +57,7 @@ class SinkFactory: FileSinkConfigWrapper: FileSink, MailSinkConfigWrapper: MailSink, PushoverSinkConfigWrapper: PushoverSink, + PlivoSinkConfigWrapper: PlivoSink, GoogleChatSinkConfigWrapper: GoogleChatSink, ServiceNowSinkConfigWrapper: ServiceNowSink, ZulipSinkConfigWrapper: ZulipSink,