Skip to content

Commit 64b24ba

Browse files
authored
Merge branch 'master' into krr_push
2 parents ca015f1 + 8aafd9f commit 64b24ba

13 files changed

Lines changed: 106 additions & 47 deletions

File tree

docs/configuration/holmesgpt/index.rst

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,38 @@ Reading the Robusta UI Token from a secret in HolmesGPT
311311
312312
Run a :ref:`Helm Upgrade <Simple Upgrade>` to apply the configuration.
313313

314+
Enable Holmes in Slack in the Platform
315+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
316+
317+
1. **Go to** https://platform.robusta.dev/
318+
319+
2. **Navigate to:**
320+
**Settings** → **AI Assistant**
321+
322+
.. image:: /images/Enabling_AI_in_slack.png
323+
:width: 1000px
324+
325+
3. **Enable Holmes** using the toggle.
326+
327+
4. **Click** **Connect Slack Workspace** to authorize Holmes in your Slack workspace.
328+
329+
5. **Use Holmes in Slack**
330+
331+
In any Slack channel or thread, tag Holmes using `@holmes` like::
332+
333+
@holmes can you look into this
334+
335+
Or ask natural language questions about a specific cluster. Examples::
336+
337+
.. code-block:: bash
338+
@holmes what apps are crashing in my `prod-cluster`
339+
@holmes show me the CPU usage for the frontend deployment in `staging-cluster`
340+
@holmes why is my alert firing on `eu-prod-atc-eks`?
341+
@holmes investigate high memory usage in `dev-cluster`
342+
343+
Holmes will respond in the thread with insights and troubleshooting steps based on the specified cluster.
344+
345+
314346
Test Holmes Integration
315347
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
316348
284 KB
Loading

helm/robusta/Chart.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
dependencies:
22
- name: kube-prometheus-stack
33
repository: https://prometheus-community.github.io/helm-charts
4-
version: 55.7.0
4+
version: 72.0.1
55
- name: holmes
66
repository: https://robusta-charts.storage.googleapis.com
77
version: 0.11.5
8-
digest: sha256:af349cab87675093f9ad81330f889c31298bc0e9f918108a83702f33ae43ea41
9-
generated: "2025-06-10T09:36:25.85188+03:00"
8+
digest: sha256:3e3d5f742ecee2e068c88557aec4abca395ef1e9e90faabceb2671f27fe8ce52
9+
generated: "2025-06-22T16:35:28.432498+03:00"

helm/robusta/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ appVersion: 0.0.0
1111

1212
dependencies:
1313
- name: kube-prometheus-stack
14-
version: 55.7.0
14+
version: 72.0.1
1515
condition: enablePrometheusStack
1616
repository: "https://prometheus-community.github.io/helm-charts"
1717
- name: holmes
-497 KB
Binary file not shown.
792 KB
Binary file not shown.

src/robusta/core/sinks/robusta/robusta_sink.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,14 @@
4242
from robusta.integrations.receiver import ActionRequestReceiver
4343
from robusta.runner.web_api import WebApi
4444
from robusta.utils.stack_tracer import StackTracer
45+
from robusta.core.model.env_vars import ROBUSTA_API_ENDPOINT
46+
from cachetools import TTLCache
4547

48+
RUNNER_GET_HOLMES_SLACKBOT_INFO = f"{ROBUSTA_API_ENDPOINT}/api/holmes/integrations/slack/runner"
49+
HOLMES_SLACKBOT_CACHE_TTL = int(os.getenv("HOLMES_SLACKBOT_CACHE_TTL", 15 * 60))
50+
51+
# Define the cache with a single slot and the configured TTL
52+
_holmes_slackbot_cache = TTLCache(maxsize=1, ttl=HOLMES_SLACKBOT_CACHE_TTL)
4653

4754
class RobustaSink(SinkBase, EventHandler):
4855
services_publish_lock = threading.Lock()
@@ -703,3 +710,26 @@ def __update_job(self, new_job: Job, operation: K8sOperationType):
703710
self.__safe_delete_job(job_key)
704711
self.__discovery_metrics.on_jobs_updated(1)
705712
return
713+
714+
def is_holmes_slackbot_connected(self) -> bool:
715+
if 'status' in _holmes_slackbot_cache:
716+
return _holmes_slackbot_cache['status']
717+
session_token = self.dal.get_session_token()
718+
try:
719+
message_json = {
720+
"session_token": session_token,
721+
"account_id": self.account_id,
722+
}
723+
response = requests.post(
724+
RUNNER_GET_HOLMES_SLACKBOT_INFO,
725+
json=message_json,
726+
headers={"Content-Type": "application/json"}
727+
)
728+
response.raise_for_status()
729+
is_connected = bool(response.json().get("integrations"))
730+
_holmes_slackbot_cache['status'] = is_connected
731+
return is_connected
732+
except Exception as e:
733+
logging.warning(f"Failed to get holmes slackbot info {e}")
734+
_holmes_slackbot_cache['status'] = False
735+
return False

src/robusta/core/sinks/slack/slack_sink.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def __init__(self, sink_config: SlackSinkConfigWrapper, registry, is_preview=Fal
1616
self.slack_channel = slack_sink_params.slack_channel
1717
self.api_key = slack_sink_params.api_key
1818
self.slack_sender = slack_module.SlackSender(
19-
self.api_key, self.account_id, self.cluster_name, self.signing_key, self.slack_channel, is_preview
19+
self.api_key, self.account_id, self.cluster_name, self.signing_key, self.slack_channel, registry, is_preview
2020
)
2121
self.registry.subscribe("replace_callback_with_string", self)
2222

src/robusta/integrations/slack/sender.py

Lines changed: 27 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
from robusta.core.sinks.sink_base import KeyT
4949
from robusta.core.sinks.slack.slack_sink_params import SlackSinkParams
5050
from robusta.core.sinks.slack.preview.slack_sink_preview_params import SlackSinkPreviewParams
51-
5251
from robusta.core.sinks.transformer import Transformer
5352

5453
ACTION_TRIGGER_PLAYBOOK = "trigger_playbook"
@@ -62,7 +61,7 @@ class SlackSender:
6261
verified_api_tokens: Set[str] = set()
6362
channel_name_to_id = {}
6463

65-
def __init__(self, slack_token: str, account_id: str, cluster_name: str, signing_key: str, slack_channel: str, is_preview: bool = False):
64+
def __init__(self, slack_token: str, account_id: str, cluster_name: str, signing_key: str, slack_channel: str, registry, is_preview: bool = False):
6665
"""
6766
Connect to Slack and verify that the Slack token is valid.
6867
Return True on success, False on failure
@@ -80,6 +79,7 @@ def __init__(self, slack_token: str, account_id: str, cluster_name: str, signing
8079
timeout=SLACK_REQUEST_TIMEOUT,
8180
retry_handlers=all_builtin_retry_handlers(),
8281
)
82+
self.registry = registry
8383
self.signing_key = signing_key
8484
self.account_id = account_id
8585
self.cluster_name = cluster_name
@@ -394,33 +394,6 @@ def __limit_labels_size(self, labels: dict, max_size: int = 1000) -> dict:
394394

395395
return limited_labels
396396

397-
def __create_holmes_callback(self, finding: Finding) -> CallbackBlock:
398-
resource = ResourceInfo(
399-
name=finding.subject.name if finding.subject.name else "",
400-
namespace=finding.subject.namespace,
401-
kind=finding.subject.subject_type.value if finding.subject.subject_type.value else "",
402-
node=finding.subject.node,
403-
container=finding.subject.container,
404-
)
405-
406-
context: Dict[str, Any] = {
407-
"robusta_issue_id": str(finding.id),
408-
"issue_type": finding.aggregation_key,
409-
"source": finding.source.name,
410-
"labels": self.__limit_labels_size(labels=finding.subject.labels),
411-
}
412-
413-
return CallbackBlock(
414-
{
415-
"Ask HolmesGPT": CallbackChoice(
416-
action=ask_holmes,
417-
action_params=AIInvestigateParams(
418-
resource=resource, investigation_type="issue", ask="Why is this alert firing?", context=context
419-
),
420-
)
421-
}
422-
)
423-
424397
@staticmethod
425398
def extract_mentions(title) -> (str, str):
426399
mentions = MENTION_PATTERN.findall(title)
@@ -667,6 +640,16 @@ def send_holmes_analysis(
667640
except Exception:
668641
logging.exception(f"error sending message to slack. {title}")
669642

643+
def get_holmes_block(self, platform_enabled: bool, slackbot_enabled) -> Optional[MarkdownBlock]:
644+
if not platform_enabled and not slackbot_enabled:
645+
return MarkdownBlock("_Ask AI questions about this alert, by connecting <https://platform.robusta.dev/create-account|Robusta SaaS> and tagging @holmes._")
646+
elif platform_enabled and not slackbot_enabled:
647+
return MarkdownBlock("_Ask AI questions about this alert, by adding @holmes to your <https://docs.robusta.dev/master/configuration/holmesgpt/index.html#enable-holmes-in-slack-in-the-platform|Slack>._")
648+
elif platform_enabled and slackbot_enabled:
649+
return MarkdownBlock("_Ask AI questions about this alert, by tagging @holmes in a threaded reply_")
650+
return None
651+
652+
670653
def send_finding_to_slack(
671654
self,
672655
finding: Finding,
@@ -691,6 +674,15 @@ def send_finding_to_slack(
691674
thread_ts=thread_ts
692675
)
693676

677+
def __is_holmes_slackbot_enabled(self) -> bool:
678+
robusta_sinks = self.registry.get_sinks().get_robusta_sinks() if self.registry else None
679+
if not robusta_sinks:
680+
logging.debug("No robusta sinks found, holmes not connected to slackbot")
681+
return False
682+
683+
robusta_sink = robusta_sinks[0]
684+
return robusta_sink.is_holmes_slackbot_connected()
685+
694686
def __send_finding_to_slack(
695687
self,
696688
finding: Finding,
@@ -725,9 +717,6 @@ def __send_finding_to_slack(
725717
)
726718
blocks.append(links_block)
727719

728-
if HOLMES_ENABLED and HOLMES_ASK_SLACK_BUTTON_ENABLED:
729-
blocks.append(self.__create_holmes_callback(finding))
730-
731720
blocks.append(MarkdownBlock(text=f"*Source:* `{self.cluster_name}`"))
732721
if finding.description:
733722
if finding.source == FindingSource.PROMETHEUS:
@@ -753,6 +742,12 @@ def __send_finding_to_slack(
753742

754743
blocks.append(DividerBlock())
755744

745+
is_holmes_slackbot_enabled = self.__is_holmes_slackbot_enabled()
746+
holmes_block = self.get_holmes_block(platform_enabled, is_holmes_slackbot_enabled)
747+
if holmes_block:
748+
blocks.append(holmes_block)
749+
750+
756751
if len(attachment_blocks):
757752
attachment_blocks.append(DividerBlock())
758753

tests/manual_tests/test_slack_integration_manual.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,7 @@ def main():
442442
cluster_name="test-cluster",
443443
signing_key="test-signing-key",
444444
slack_channel=SLACK_CHANNEL,
445+
registry=None,
445446
is_preview=False
446447
)
447448

@@ -452,6 +453,7 @@ def main():
452453
cluster_name="test-cluster",
453454
signing_key="test-signing-key",
454455
slack_channel=SLACK_CHANNEL,
456+
registry=None,
455457
is_preview=True
456458
)
457459

0 commit comments

Comments
 (0)