Skip to content

Commit

Permalink
Support prometheus generator url (#1634)
Browse files Browse the repository at this point in the history
* Support promethues generator url

* Add enrichment type

* Use links instead of enrichment

* Some fixes

* Add video links

* Fix

* PR Comments

* PR Comments
  • Loading branch information
moshemorad authored Nov 25, 2024
1 parent c3fcaae commit dd2b6df
Show file tree
Hide file tree
Showing 30 changed files with 419 additions and 212 deletions.
6 changes: 5 additions & 1 deletion playbooks/robusta_playbooks/alerts_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
)
from robusta.core.playbooks.oom_killer_utils import logs_enricher, start_log_enrichment
from robusta.core.reporting import FindingSubject
from robusta.core.reporting.base import Link, LinkType
from robusta.core.reporting.blocks import TableBlockFormat
from robusta.utils.parsing import format_event_templated_string

Expand Down Expand Up @@ -214,6 +215,8 @@ def default_enricher(alert: PrometheusKubernetesAlert, params: DefaultEnricherPa
By default, this enricher is last in the processing order, so it will be added to all alerts, that aren't silenced.
"""
alert.add_link(Link(url=alert.alert.generatorURL, name="View Graph", type=LinkType.PROMETHEUS_GENERATOR_URL))

labels = alert.alert.labels
alert.add_enrichment(
[
Expand Down Expand Up @@ -441,7 +444,7 @@ def format_pod_templated_string(pod: RobustaPod, template: Optional[str]) -> Opt
subject_type=FindingSubjectType.from_kind("pod"),
namespace=pod.metadata.namespace,
labels=pod.metadata.labels,
annotations=pod.metadata.annotations
annotations=pod.metadata.annotations,
)
return format_event_templated_string(subject, template)

Expand Down Expand Up @@ -469,6 +472,7 @@ def alert_foreign_logs_enricher(event: PrometheusKubernetesAlert, params: Foreig
params.label_selectors = [format_event_templated_string(subject, selector) for selector in params.label_selectors]
return foreign_logs_enricher(event, params)


@action
def foreign_logs_enricher(event: ExecutionBaseEvent, params: ForeignLogParams):
"""
Expand Down
4 changes: 2 additions & 2 deletions playbooks/robusta_playbooks/event_enrichments.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
SlackAnnotations,
StatefulSet,
VideoEnricherParams,
VideoLink,
Link,
action,
get_event_timestamp,
get_job_all_pods,
Expand Down Expand Up @@ -367,7 +367,7 @@ def external_video_enricher(event: ExecutionBaseEvent, params: VideoEnricherPara
"""
Attaches a video links to the finding
"""
event.add_video_link(VideoLink(url=params.url, name=params.name))
event.add_video_link(Link(url=params.url, name=params.name))


@action
Expand Down
2 changes: 1 addition & 1 deletion src/robusta/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@
ScanReportBlock,
ScanReportRow,
TableBlock,
VideoLink,
Link,
)
from robusta.core.reporting.action_requests import (
ActionRequestBody,
Expand Down
12 changes: 9 additions & 3 deletions src/robusta/core/model/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
FindingSource,
FindingSubject,
FindingSubjectType,
VideoLink,
Link,
LinkType,
)
from robusta.core.sinks import SinkBase
from robusta.integrations.scheduled.playbook_scheduler import PlaybooksScheduler
Expand Down Expand Up @@ -95,10 +96,15 @@ def __prepare_sinks_findings(self):
sink_finding.id = finding_id # share the same finding id between different sinks
self.sink_findings[sink].append(sink_finding)

def add_video_link(self, video_link: VideoLink):
def add_link(self, link: Link, suppress_warning: bool = False) -> None:
self.__prepare_sinks_findings()
for sink in self.named_sinks:
self.sink_findings[sink][0].add_video_link(video_link, True)
self.sink_findings[sink][0].add_link(link, suppress_warning)

def add_video_link(self, video_link: Link) -> None:
# For backward compatability
video_link.type = LinkType.VIDEO
self.add_link(video_link, True)

def emit_event(self, event_name: str, **kwargs):
"""Publish an event to the pubsub. It will be processed by the sinks during the execution of the playbook."""
Expand Down
4 changes: 2 additions & 2 deletions src/robusta/core/reporting/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
FindingStatus,
FindingSubject,
FindingSubjectType,
VideoLink,
Link,
)
from robusta.core.reporting.blocks import (
CallbackBlock,
Expand Down Expand Up @@ -39,7 +39,7 @@
"Emojis",
"FindingSeverity",
"FindingStatus",
"VideoLink",
"Link",
"FindingSource",
"Enrichment",
"Filterable",
Expand Down
33 changes: 28 additions & 5 deletions src/robusta/core/reporting/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from abc import ABC, abstractmethod
from datetime import datetime
from enum import Enum
from strenum import StrEnum
from typing import Any, Dict, List, Optional, Union
from urllib.parse import urlencode

Expand All @@ -23,11 +24,13 @@ class BaseBlock(BaseModel):
html_class: str = None


class Emojis(Enum):
class Emojis(StrEnum):
Explain = "📘"
Recommend = "🛠"
Alert = "🚨"
K8Notification = "👀"
Video = "🎬"
Graph = "📈"


class FindingSeverity(Enum):
Expand Down Expand Up @@ -88,9 +91,25 @@ def to_emoji(self) -> str:
return "🔥"


class VideoLink(BaseModel):
class LinkType(StrEnum):
VIDEO = "video"
PROMETHEUS_GENERATOR_URL = "prometheus_generator_url"


class Link(BaseModel):
url: str
name: str = "See more"
type: Optional[LinkType] = None

@property
def link_text(self):
if self.type == LinkType.PROMETHEUS_GENERATOR_URL:
return f"{Emojis.Graph} {self.name}"

if self.type == LinkType.VIDEO:
return f"{Emojis.Video} {self.name}"

return self.name


class EnrichmentType(Enum):
Expand Down Expand Up @@ -260,7 +279,7 @@ def __init__(
self.category = None # TODO fill real category
self.subject = subject
self.enrichments: List[Enrichment] = []
self.video_links: List[VideoLink] = []
self.links: List[Link] = []
self.service = TopServiceResolver.guess_cached_resource(name=subject.name, namespace=subject.namespace)
self.service_key = self.service.get_resource_key() if self.service else ""
uri_path = f"services/{self.service_key}?tab=grouped" if self.service_key else "graphs"
Expand Down Expand Up @@ -341,11 +360,15 @@ def add_enrichment(
Enrichment(blocks=enrichment_blocks, annotations=annotations, enrichment_type=enrichment_type, title=title)
)

def add_video_link(self, video_link: VideoLink, suppress_warning: bool = False):
def add_link(self, link: Link, suppress_warning: bool = False) -> None:
if self.dirty and not suppress_warning:
logging.warning("Updating a finding after it was added to the event is not allowed!")
self.links.append(link)

self.video_links.append(video_link)
def add_video_link(self, video_link: Link, suppress_warning: bool = False) -> None:
# For backward compatability
video_link.type = LinkType.VIDEO
self.add_link(video_link, suppress_warning)

def __str__(self):
return f"title: {self.title} desc: {self.description} severity: {self.severity} sub-name: {self.subject.name} sub-type:{self.subject.subject_type.value} enrich: {self.enrichments}"
Expand Down
1 change: 0 additions & 1 deletion src/robusta/core/reporting/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,6 @@ class PrometheusBlock(BaseBlock):
class Config:
arbitrary_types_allowed = True


def __init__(
self,
data: PrometheusQueryResult,
Expand Down
31 changes: 31 additions & 0 deletions src/robusta/core/reporting/url_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import logging
from typing import Optional
from urllib.parse import ParseResult, parse_qs, urlencode, urlparse

from robusta.core.model.env_vars import ROBUSTA_UI_DOMAIN


PROM_GRAPH_PATH_URL: str = "/graph"
PROM_GRAPH_URL_EXPR_PARAM: str = "g0.expr"


def convert_prom_graph_url_to_robusta_metrics_explorer(prom_url: str, cluster_name: str, account_id: str) -> str:
try:
parsed_url: ParseResult = urlparse(prom_url)
if parsed_url.path != PROM_GRAPH_PATH_URL:
logging.warning("Failed to convert to robusta metric explorer url, url: %s not seems to be graph url", prom_url)
return prom_url

query_string = parse_qs(parsed_url.query)
expr_params: Optional[list[str]] = query_string.get(PROM_GRAPH_URL_EXPR_PARAM)
if not expr_params:
logging.warning("Failed to get expr params, url: %s not seems to be graph url", prom_url)
return prom_url

expr = expr_params[0]
params: dict[str, str] = {"query": expr, "cluster": cluster_name, "account": account_id}
robusta_metrics_url: str = f"{ROBUSTA_UI_DOMAIN}/metrics-explorer?{urlencode(params)}"
return robusta_metrics_url
except Exception:
logging.warning('Failed to convert prom url: %s to robusta url', prom_url, exc_info=True)
return prom_url
36 changes: 22 additions & 14 deletions src/robusta/core/sinks/common/html_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
Some base code for handling HTML-outputting sinks. Currently used
by the mail and servicenow sinks.
"""
from typing import List

from robusta.core.reporting.base import BaseBlock, Finding
from typing import List, Optional

from robusta.core.reporting.base import BaseBlock, Emojis, Finding, LinkType
from robusta.core.reporting.blocks import LinksBlock, LinkProp
from robusta.core.reporting.blocks import FileBlock
from robusta.core.sinks.transformer import Transformer
Expand Down Expand Up @@ -88,21 +89,28 @@ def get_css(self):
}
"""

def create_links(self, finding: Finding, html_class: str):
links: List[LinkProp] = [LinkProp(
text="Investigate 🔎",
url=finding.get_investigate_uri(self.account_id, self.cluster_name),
)]

if finding.add_silence_url:
def create_links(self, finding: Finding, html_class: str, platform_enabled: bool) -> Optional[LinksBlock]:
links: List[LinkProp] = []
if platform_enabled:
links.append(
LinkProp(
text="Configure Silences 🔕",
url=finding.get_prometheus_silence_url(self.account_id, self.cluster_name),
text="Investigate 🔎",
url=finding.get_investigate_uri(self.account_id, self.cluster_name),
)
)

for video_link in finding.video_links:
links.append(LinkProp(text=f"{video_link.name} 🎬", url=video_link.url))
if finding.add_silence_url:
links.append(
LinkProp(
text="Configure Silences 🔕",
url=finding.get_prometheus_silence_url(self.account_id, self.cluster_name),
)
)

for link in finding.links:
links.append(LinkProp(text=link.link_text, url=link.url))

return with_attr(LinksBlock(links=links), "html_class", html_class)
if links:
return with_attr(LinksBlock(links=links), "html_class", html_class)

return None
9 changes: 8 additions & 1 deletion src/robusta/core/sinks/msteams/msteams_sink.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,15 @@ def __init__(self, sink_config: MsTeamsSinkConfigWrapper, registry):
super().__init__(sink_config.ms_teams_sink, registry)
self.webhook_url = sink_config.ms_teams_sink.webhook_url
self.webhook_override = sink_config.ms_teams_sink.webhook_override
self.sink_config = sink_config.ms_teams_sink

def write_finding(self, finding: Finding, platform_enabled: bool):
MsTeamsSender.send_finding_to_ms_teams(
self.webhook_url, finding, platform_enabled, self.cluster_name, self.account_id, self.webhook_override
self.webhook_url,
finding,
platform_enabled,
self.cluster_name,
self.account_id,
self.webhook_override,
self.sink_config.prefer_redirect_to_platform,
)
20 changes: 13 additions & 7 deletions src/robusta/core/sinks/opsgenie/opsgenie_sink.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,25 @@ def write_finding(self, finding: Finding, platform_enabled: bool):
self.__open_alert(finding, platform_enabled)

def __to_description(self, finding: Finding, platform_enabled: bool) -> str:
description = ""
actions_block: list[str] = []
if platform_enabled:
description = (
actions_block.append(
f'<a href="{finding.get_investigate_uri(self.account_id, self.cluster_name)}">🔎 Investigate</a>'
)
if finding.add_silence_url:
description = f'{description} <a href="{finding.get_prometheus_silence_url(self.account_id, self.cluster_name)}">🔕 Silence</a>'
actions_block.append(
f'<a href="{finding.get_prometheus_silence_url(self.account_id, self.cluster_name)}">🔕 Silence</a>'
)

for video_link in finding.video_links:
description = f'{description} <a href="{video_link.url}">🎬 {video_link.name}</a>'
description = f"{description}\n"
for link in finding.links:
actions_block.append(f'<a href="{link.url}">{link.link_text}</a>')

return f"{description}{self.__enrichments_as_text(finding.enrichments)}"
if actions_block:
actions = f"{' '.join(actions_block)}\n"
else:
actions = ""

return f"{actions}{self.__enrichments_as_text(finding.enrichments)}"

def __to_details(self, finding: Finding) -> dict:
details = {
Expand Down
Loading

0 comments on commit dd2b6df

Please # to comment.