diff --git a/playbooks/robusta_playbooks/babysitter.py b/playbooks/robusta_playbooks/babysitter.py index 50b85cfc8..c52a866ff 100644 --- a/playbooks/robusta_playbooks/babysitter.py +++ b/playbooks/robusta_playbooks/babysitter.py @@ -21,6 +21,7 @@ FindingAggregationKey, ) from robusta.core.reporting.base import EnrichmentType +from robusta.core.reporting.findings import FindingOwner class BabysitterConfig(ActionParams): diff --git a/playbooks/robusta_playbooks/event_enrichments.py b/playbooks/robusta_playbooks/event_enrichments.py index 24b608c33..82ed31114 100644 --- a/playbooks/robusta_playbooks/event_enrichments.py +++ b/playbooks/robusta_playbooks/event_enrichments.py @@ -40,6 +40,7 @@ ) from robusta.core.reporting import EventsBlock, EventRow from robusta.core.reporting.base import EnrichmentType +from robusta.core.reporting.findings import FindingOwner from robusta.core.reporting.custom_rendering import render_value @@ -72,6 +73,7 @@ def event_report(event: EventChangeEvent): subject_type=FindingSubjectType.from_kind(k8s_obj.kind), namespace=k8s_obj.namespace, node=KubeObjFindingSubject.get_node_name(k8s_obj), + owner=FindingOwner(owner_references=event.obj.metadata.ownerReferences) ), ) event.add_finding(finding) diff --git a/scripts/generate_kubernetes_code.py b/scripts/generate_kubernetes_code.py index 3dc4cc232..08d712eb2 100755 --- a/scripts/generate_kubernetes_code.py +++ b/scripts/generate_kubernetes_code.py @@ -77,6 +77,7 @@ def autogenerate_events(f: TextIO): from ..base_event import K8sBaseChangeEvent from ....core.model.events import ExecutionBaseEvent, ExecutionEventBaseParams from ....core.reporting.base import FindingSubject + from ....core.reporting.findings import FindingOwner from ....core.reporting.consts import FindingSubjectType, FindingSource from ....core.reporting.finding_subjects import KubeObjFindingSubject from robusta.integrations.kubernetes.custom_models import {CUSTOM_MODELS_IMPORTS} @@ -185,6 +186,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @classmethod @@ -268,6 +270,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -291,6 +294,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) diff --git a/src/robusta/core/discovery/top_service_resolver.py b/src/robusta/core/discovery/top_service_resolver.py index f073958f4..dd17fcb92 100644 --- a/src/robusta/core/discovery/top_service_resolver.py +++ b/src/robusta/core/discovery/top_service_resolver.py @@ -3,9 +3,12 @@ from collections import defaultdict from typing import Dict, List, Optional +from hikaru.model.rel_1_26 import OwnerReference from pydantic.main import BaseModel from robusta.core.model.env_vars import RESOURCE_UPDATES_CACHE_TTL_SEC +from robusta.core.reporting.findings import FindingOwner +from robusta.integrations.kubernetes.custom_models import RobustaPod class TopLevelResource(BaseModel): @@ -26,6 +29,7 @@ class TopServiceResolver: __recent_resource_updates: Dict[str, CachedResourceInfo] = {} __namespace_to_resource: Dict[str, List[TopLevelResource]] = defaultdict(list) __cached_updates_lock = threading.Lock() + __cached_owner_references: Dict[str, OwnerReference] = {} @classmethod def store_cached_resources(cls, resources: List[TopLevelResource]): @@ -51,20 +55,62 @@ def store_cached_resources(cls, resources: List[TopLevelResource]): # TODO remove this guess function # temporary try to guess who the owner service is. @classmethod - def guess_service_key(cls, name: str, namespace: str) -> str: - resource = cls.guess_cached_resource(name, namespace) + def guess_service_key(cls, name: str, namespace: str, kind: str, owner: Optional[FindingOwner]) -> str: + resource = cls.guess_cached_resource(name, namespace, kind=kind, owner=owner) return resource.get_resource_key() if resource else "" - # TODO remove this guess function - # temporary try to guess who the owner service is. @classmethod - def guess_cached_resource(cls, name: str, namespace: str) -> Optional[TopLevelResource]: + def get_pod_owner_reference(cls, name: str, namespace: str) -> Optional[OwnerReference]: + key = f"{namespace}/{name}" + if key in cls.__cached_owner_references: + return cls.__cached_owner_references[key] + + robusta_pod = RobustaPod.find_pod(name, namespace) + if robusta_pod.metadata.ownerReferences: + cls.__cached_owner_references[key] = robusta_pod.metadata.ownerReferences[0] + return robusta_pod.metadata.ownerReferences[0] + + return None + + @classmethod + def guess_cached_resource(cls, name: str, namespace: str, kind: str, owner: Optional[FindingOwner]) \ + -> Optional[TopLevelResource]: if name is None or namespace is None: return None - for cached_resource in cls.__namespace_to_resource[namespace]: - if name.startswith(cached_resource.name): - return cached_resource + kind = kind.lower() + + # owner references available + if owner and owner.owner_references: + owner_kind = owner.owner_references[0].kind.lower() + owner_reference = owner.owner_references[0] + + if owner_kind in ["deployment", "statefulset", "daemonset", "job", "deploymentconfig", + "argorollout"]: + return TopLevelResource(name=owner_reference.name, resource_type=owner_reference.kind, + namespace=namespace) + + # replicset + if owner_kind == "replicaset": + new_owner_reference = cls.get_pod_owner_reference(name=owner_reference.name, namespace=namespace) + return TopLevelResource(name=new_owner_reference.name, resource_type=new_owner_reference.kind, + namespace=namespace) + + # crd + if owner_kind not in ["deployment", "statefulset", "daemonset", "job", "deploymentconfig", + "argorollout", "pod"]: + return TopLevelResource(name=name, resource_type=kind, namespace=namespace) + + # owner references NOT available + if owner is None or not owner.owner_references: + return TopLevelResource(name=name, resource_type=kind, namespace=namespace) + + # unknown owner + if owner.unknown_owner: + for cached_resource in cls.__namespace_to_resource[namespace]: + if name.startswith(cached_resource.name): + return cached_resource + return None @classmethod diff --git a/src/robusta/core/playbooks/internal/discovery_events.py b/src/robusta/core/playbooks/internal/discovery_events.py index 6f7ac7714..2477a2801 100644 --- a/src/robusta/core/playbooks/internal/discovery_events.py +++ b/src/robusta/core/playbooks/internal/discovery_events.py @@ -16,6 +16,7 @@ from robusta.core.discovery.top_service_resolver import TopLevelResource, TopServiceResolver from robusta.core.playbooks.common import get_event_timestamp, get_events_list from robusta.core.reporting.base import EnrichmentType +from robusta.core.reporting.findings import FindingOwner @action @@ -65,6 +66,7 @@ def create_debug_event_finding(event: Event): """ k8s_obj = event.regarding subject_type = FindingSubjectType.from_kind(k8s_obj.kind.lower()) if k8s_obj.kind else FindingSubjectType.TYPE_NONE + finding = Finding( title=f"{event.reason} {event.type} for {k8s_obj.kind} {k8s_obj.namespace}/{k8s_obj.name}", description=event.note, @@ -76,10 +78,13 @@ def create_debug_event_finding(event: Event): k8s_obj.name, subject_type, k8s_obj.namespace, + owner=FindingOwner(owner_references=event.metadata.ownerReferences) ), creation_date=get_event_timestamp(event), ) - finding.service_key = TopServiceResolver.guess_service_key(name=k8s_obj.name, namespace=k8s_obj.namespace) + finding.service_key = TopServiceResolver.guess_service_key(name=k8s_obj.name, namespace=k8s_obj.namespace, + kind=k8s_obj.kind, + owner=finding.subject.owner) return finding diff --git a/src/robusta/core/reporting/base.py b/src/robusta/core/reporting/base.py index 2e2a985b5..36fd8e25b 100644 --- a/src/robusta/core/reporting/base.py +++ b/src/robusta/core/reporting/base.py @@ -14,6 +14,7 @@ from robusta.core.discovery.top_service_resolver import TopServiceResolver from robusta.core.model.env_vars import ROBUSTA_UI_DOMAIN from robusta.core.reporting.consts import FindingSource, FindingSubjectType, FindingType +from robusta.core.reporting.findings import FindingOwner from robusta.utils.scope import BaseScopeMatcher @@ -201,6 +202,7 @@ def __init__( container: Optional[str] = None, labels: Optional[Dict[str, str]] = None, annotations: Optional[Dict[str, str]] = None, + owner: Optional[FindingOwner] = None, ): self.name = name self.subject_type = subject_type @@ -209,6 +211,7 @@ def __init__( self.container = container self.labels = labels or {} self.annotations = annotations or {} + self.owner = owner def __str__(self): if self.namespace is not None: @@ -251,7 +254,9 @@ def __init__( self.subject = subject self.enrichments: List[Enrichment] = [] self.video_links: List[VideoLink] = [] - self.service = TopServiceResolver.guess_cached_resource(name=subject.name, namespace=subject.namespace) + self.service = TopServiceResolver.guess_cached_resource(name=subject.name, namespace=subject.namespace, + kind=subject.subject_type.value, + owner=subject.owner) 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" self.investigate_uri = f"{ROBUSTA_UI_DOMAIN}/{uri_path}" diff --git a/src/robusta/core/reporting/consts.py b/src/robusta/core/reporting/consts.py index 11e4a6b85..c234bbd42 100644 --- a/src/robusta/core/reporting/consts.py +++ b/src/robusta/core/reporting/consts.py @@ -47,6 +47,7 @@ class FindingSubjectType(Enum): TYPE_DAEMONSET = "daemonset" TYPE_STATEFULSET = "statefulset" TYPE_HPA = "horizontalpodautoscaler" + TYPE_REPLICASET = "replicaset" TYPE_HELM_RELEASES = "helmreleases" @staticmethod @@ -62,6 +63,8 @@ def from_kind(kind: str): return FindingSubjectType.TYPE_JOB elif kind == "daemonset": return FindingSubjectType.TYPE_DAEMONSET + elif kind == "replicaset": + return FindingSubjectType.TYPE_REPLICASET elif kind == "statefulset": return FindingSubjectType.TYPE_STATEFULSET elif kind == "horizontalpodautoscaler": diff --git a/src/robusta/core/reporting/finding_subjects.py b/src/robusta/core/reporting/finding_subjects.py index d7672f542..793a380c0 100644 --- a/src/robusta/core/reporting/finding_subjects.py +++ b/src/robusta/core/reporting/finding_subjects.py @@ -1,6 +1,7 @@ from hikaru.model.rel_1_26 import ObjectReference, Pod from robusta.core.reporting.base import FindingSubject +from robusta.core.reporting.findings import FindingOwner from robusta.core.reporting.consts import FindingSubjectType @@ -23,6 +24,7 @@ def __init__( node=node_name, labels=obj.metadata.labels, annotations=obj.metadata.annotations, + owner=FindingOwner(owner_references=obj.metadata.ownerReferences) ) @staticmethod @@ -49,4 +51,5 @@ def __init__(self, pod: Pod = None): node=pod.spec.nodeName, labels=pod.metadata.labels, annotations=pod.metadata.annotations, + owner=FindingOwner(owner_references=pod.metadata.ownerReferences) ) diff --git a/src/robusta/core/reporting/findings.py b/src/robusta/core/reporting/findings.py new file mode 100644 index 000000000..f94786747 --- /dev/null +++ b/src/robusta/core/reporting/findings.py @@ -0,0 +1,9 @@ +from typing import Optional, List + +from hikaru.model.rel_1_26 import OwnerReference +from pydantic import BaseModel + + +class FindingOwner(BaseModel): + owner_references: Optional[List[OwnerReference]] = None + unknown_owner: bool = False diff --git a/src/robusta/core/triggers/error_event_trigger.py b/src/robusta/core/triggers/error_event_trigger.py index 095251dce..f2f2587ab 100644 --- a/src/robusta/core/triggers/error_event_trigger.py +++ b/src/robusta/core/triggers/error_event_trigger.py @@ -2,6 +2,7 @@ from robusta.core.discovery.top_service_resolver import TopServiceResolver from robusta.core.playbooks.base_trigger import TriggerEvent +from robusta.core.reporting.findings import FindingOwner from robusta.integrations.kubernetes.autogenerated.triggers import EventAllChangesTrigger, EventChangeEvent from robusta.integrations.kubernetes.base_triggers import K8sTriggerEvent from robusta.utils.rate_limiter import RateLimiter @@ -70,7 +71,10 @@ def should_fire(self, event: TriggerEvent, playbook_id: str, build_context: Dict # Perform a rate limit for this service key according to the rate_limit parameter name = exec_event.obj.regarding.name if exec_event.obj.regarding.name else "" namespace = exec_event.obj.regarding.namespace if exec_event.obj.regarding.namespace else "" - service_key = TopServiceResolver.guess_service_key(name=name, namespace=namespace) + kind = exec_event.obj.regarding.kind if exec_event.obj.regarding.kind else "" + service_key = (TopServiceResolver.guess_service_key + (name=name, namespace=namespace, kind=kind, + owner=FindingOwner(owner_references=exec_event.obj.metadata.ownerReferences))) return RateLimiter.mark_and_test( f"WarningEventTrigger_{playbook_id}_{exec_event.obj.reason}", service_key if service_key else namespace + ":" + name, diff --git a/src/robusta/integrations/kubernetes/autogenerated/events.py b/src/robusta/integrations/kubernetes/autogenerated/events.py index 01bb69bda..eb3763f19 100644 --- a/src/robusta/integrations/kubernetes/autogenerated/events.py +++ b/src/robusta/integrations/kubernetes/autogenerated/events.py @@ -99,6 +99,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @classmethod @@ -161,6 +162,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -184,6 +186,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -217,6 +220,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -240,6 +244,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -273,6 +278,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -296,6 +302,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -329,6 +336,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -352,6 +360,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -385,6 +394,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -408,6 +418,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -441,6 +452,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -464,6 +476,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -497,6 +510,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -520,6 +534,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -553,6 +568,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -576,6 +592,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -609,6 +626,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -632,6 +650,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -665,6 +684,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -688,6 +708,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -721,6 +742,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -744,6 +766,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -777,6 +800,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -800,6 +824,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -833,6 +858,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -856,6 +882,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -889,6 +916,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -912,6 +940,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -945,6 +974,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -968,6 +998,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -1001,6 +1032,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -1024,6 +1056,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -1057,6 +1090,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -1080,6 +1114,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -1113,6 +1148,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -1136,6 +1172,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -1169,6 +1206,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) @@ -1192,6 +1230,7 @@ def get_subject(self) -> FindingSubject: node=KubeObjFindingSubject.get_node_name(self.obj), labels=self.obj.metadata.labels, annotations=self.obj.metadata.annotations, + owner=FindingOwner(owner_references=self.obj.metadata.ownerReferences) ) diff --git a/src/robusta/integrations/prometheus/models.py b/src/robusta/integrations/prometheus/models.py index 2e32798e1..7d8152ff4 100644 --- a/src/robusta/integrations/prometheus/models.py +++ b/src/robusta/integrations/prometheus/models.py @@ -6,10 +6,11 @@ from typing import Any, Dict, List, Optional, Union from urllib.parse import parse_qs, unquote, urlparse -from hikaru.model.rel_1_26 import DaemonSet, HorizontalPodAutoscaler, Node, StatefulSet +from hikaru.model.rel_1_26 import DaemonSet, HorizontalPodAutoscaler, Node, StatefulSet, OwnerReference from pydantic import BaseModel from robusta.core.reporting import Finding, FindingSeverity, FindingSource, FindingSubject, FindingSubjectType +from robusta.core.reporting.findings import FindingOwner from robusta.integrations.kubernetes.autogenerated.events import ( DaemonSetEvent, DeploymentEvent, @@ -139,30 +140,36 @@ def get_alert_subject(self) -> FindingSubject: container: Optional[str] = self.alert.labels.get("container") labels = {} annotations = {} + owner_references: Optional[List[OwnerReference]] = None + unknown_owner = False if self.deployment: subject_type = FindingSubjectType.TYPE_DEPLOYMENT name = self.deployment.metadata.name namespace = self.deployment.metadata.namespace labels = self.deployment.metadata.labels annotations = self.deployment.metadata.annotations + owner_references = self.deployment.metadata.ownerReferences elif self.daemonset: subject_type = FindingSubjectType.TYPE_DAEMONSET name = self.daemonset.metadata.name namespace = self.daemonset.metadata.namespace labels = self.daemonset.metadata.labels annotations = self.daemonset.metadata.annotations + owner_references = self.daemonset.metadata.ownerReferences elif self.statefulset: subject_type = FindingSubjectType.TYPE_STATEFULSET name = self.statefulset.metadata.name namespace = self.statefulset.metadata.namespace labels = self.statefulset.metadata.labels annotations = self.statefulset.metadata.annotations + owner_references = self.statefulset.metadata.ownerReferences elif self.node: subject_type = FindingSubjectType.TYPE_NODE name = self.node.metadata.name node_name = self.node.metadata.name labels = self.node.metadata.labels annotations = self.node.metadata.annotations + owner_references = self.node.metadata.ownerReferences elif self.pod: subject_type = FindingSubjectType.TYPE_POD name = self.pod.metadata.name @@ -170,23 +177,30 @@ def get_alert_subject(self) -> FindingSubject: node_name = self.pod.spec.nodeName labels = self.pod.metadata.labels annotations = self.pod.metadata.annotations + owner_references = self.pod.metadata.ownerReferences elif self.job: subject_type = FindingSubjectType.TYPE_JOB name = self.job.metadata.name namespace = self.job.metadata.namespace labels = self.job.metadata.labels annotations = self.job.metadata.annotations + owner_references = self.job.metadata.ownerReferences elif self.hpa: subject_type = FindingSubjectType.TYPE_HPA name = self.hpa.metadata.name labels = self.hpa.metadata.labels annotations = self.hpa.metadata.annotations + owner_references = self.hpa.metadata.ownerReferences + + else: + unknown_owner = True # Add alert labels and annotations. On duplicates, alert labels/annotations are taken labels = {**labels, **self.alert.labels} annotations = {**annotations, **self.alert.annotations} - return FindingSubject(name, subject_type, namespace, node_name, container, labels, annotations) + return FindingSubject(name, subject_type, namespace, node_name, container, labels, annotations, + owner=FindingOwner(owner_references=owner_references, unknown_owner=unknown_owner)) def create_default_finding(self) -> Finding: alert_subject = self.get_alert_subject()