Skip to content

Commit f9f7b06

Browse files
authored
ROB-1033 custom owners (#1798)
* support custom crds in a more generic way. add ExecutionContext, cnpgcluter and StrimziPodSet
1 parent edc11dd commit f9f7b06

14 files changed

Lines changed: 397 additions & 94 deletions

File tree

helm/robusta/templates/runner-service-account.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,39 @@ rules:
286286
- patch
287287
- update
288288
{{- end }}
289+
{{- if has "StrimziPodSet" .Values.runner.customCRD }}
290+
- apiGroups:
291+
- core.strimzi.io
292+
resources:
293+
- strimzipodsets
294+
verbs:
295+
- get
296+
- list
297+
- patch
298+
- update
299+
{{- end }}
300+
{{- if has "CNPGCluster" .Values.runner.customCRD }}
301+
- apiGroups:
302+
- postgresql.cnpg.io
303+
resources:
304+
- clusters
305+
verbs:
306+
- get
307+
- list
308+
- patch
309+
- update
310+
{{- end }}
311+
{{- if has "ExecutionContext" .Values.runner.customCRD }}
312+
- apiGroups:
313+
- hub.knime.com
314+
resources:
315+
- executioncontexts
316+
verbs:
317+
- get
318+
- list
319+
- patch
320+
- update
321+
{{- end }}
289322

290323
---
291324
apiVersion: v1

helm/robusta/templates/runner.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ spec:
106106
- name: ARGO_ROLLOUTS
107107
value: "True"
108108
{{- end }}
109+
{{- if .Values.runner.customCRD }}
110+
- name: CUSTOM_CRD
111+
value: {{ .Values.runner.customCRD | toJson }}
112+
{{- end -}}
109113
{{- if kindIs "string" .Values.runner.additional_env_vars }}
110114
{{- fail "The `additional_env_vars` string value is deprecated. Change the `additional_env_vars` value to an array" -}}
111115
{{- end }}

helm/robusta/values.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,7 @@ runner:
672672
cpu: ~
673673
additional_env_vars: []
674674
additional_env_froms: []
675+
customCRD: []
675676
priorityClassName: ""
676677
tolerations: []
677678
annotations: {}

playbooks/robusta_playbooks/event_enrichments.py

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,21 @@
4545
from robusta.core.reporting.base import EnrichmentType
4646
from robusta.core.reporting.custom_rendering import render_value
4747
from robusta.utils.parsing import format_event_templated_string
48+
from robusta.core.model.env_vars import CUSTOM_CRD
4849

50+
events_supported_types = [
51+
"Pod",
52+
"Deployment",
53+
"DaemonSet",
54+
"ReplicaSet",
55+
"StatefulSet",
56+
"Job",
57+
"Node",
58+
"DeploymentConfig",
59+
"Rollout",
60+
]
61+
62+
events_supported_types.extend(CUSTOM_CRD)
4963

5064
class ExtendedEventEnricherParams(EventEnricherParams):
5165
"""
@@ -155,17 +169,7 @@ def resource_events_enricher(event: KubernetesResourceEvent, params: ExtendedEve
155169
"""
156170

157171
resource = event.get_resource()
158-
if resource.kind not in [
159-
"Pod",
160-
"Deployment",
161-
"DaemonSet",
162-
"ReplicaSet",
163-
"StatefulSet",
164-
"Job",
165-
"Node",
166-
"DeploymentConfig",
167-
"Rollout",
168-
]:
172+
if resource.kind not in events_supported_types:
169173
raise ActionException(
170174
ErrorCodes.RESOURCE_NOT_SUPPORTED, f"Resource events enricher is not supported for resource {resource.kind}"
171175
)
@@ -180,20 +184,13 @@ def resource_events_enricher(event: KubernetesResourceEvent, params: ExtendedEve
180184
)
181185

182186
# append related pod data as well
183-
if params.dependent_pod_mode and kind in [
184-
"Deployment",
185-
"DaemonSet",
186-
"ReplicaSet",
187-
"StatefulSet",
188-
"Job",
189-
"DeploymentConfig",
190-
"Rollout",
191-
]:
187+
if params.dependent_pod_mode and kind not in ["Pod", "Node"]:
192188
pods = []
193189
if kind == "Job":
194190
pods = get_job_all_pods(resource) or []
195191
else:
196-
pods = list_pods_using_selector(resource.metadata.namespace, resource.spec.selector, "")
192+
selector = resource.spec.get("selector", {}) if isinstance(resource.spec, dict) else resource.spec.selector
193+
pods = list_pods_using_selector(resource.metadata.namespace, selector, "")
197194

198195
selected_pods = pods[: min(len(pods), params.max_pods)]
199196
for pod in selected_pods:

playbooks/robusta_playbooks/k8s_resource_enrichments.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
pod_restarts,
4545
)
4646
from robusta.core.discovery import utils
47-
from robusta.core.model.env_vars import RESOURCE_YAML_BLOCK_LIST
47+
from robusta.core.model.env_vars import RESOURCE_YAML_BLOCK_LIST, CUSTOM_CRD
4848
from robusta.core.model.pods import ResourceAttributes
4949

5050

@@ -96,7 +96,7 @@ class RelatedPod(BaseModel):
9696

9797

9898
supported_resources = ["Deployment", "DaemonSet", "ReplicaSet", "Pod", "StatefulSet", "Job", "Node", "DeploymentConfig", "Rollout"]
99-
99+
supported_resources.extend(CUSTOM_CRD)
100100

101101
def to_pod_row(pod: V1Pod, cluster_name: str) -> List:
102102
resource_requests = k8s_pod_requests(pod)
@@ -153,7 +153,8 @@ def get_related_pods_with_extra_info(resource, limit: Optional[int]=None, _conti
153153
_continue=_continue,
154154
)
155155
else:
156-
selector = build_selector_query(resource.spec.selector)
156+
selector = resource.spec.get("selector", {}) if isinstance(resource.spec, dict) else resource.spec.selector
157+
selector = build_selector_query(selector)
157158
result = client.CoreV1Api().list_namespaced_pod(
158159
namespace=resource.metadata.namespace,
159160
label_selector=selector,

playbooks/robusta_playbooks/workload_actions.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
DaemonSet,
1717
)
1818
from robusta.integrations.kubernetes.custom_models import DeploymentConfig, Rollout
19-
from kubernetes.client import AppsV1Api
19+
from robusta.integrations.kubernetes.custom_crds import CRDBase
20+
21+
from kubernetes.client import AppsV1Api, CustomObjectsApi
2022

2123

2224
@action
@@ -44,12 +46,7 @@ def rollout_restart(event: KubernetesResourceEvent):
4446
"""
4547
resource = event.get_resource()
4648
if not resource:
47-
raise ActionException(ErrorCodes.RESOURCE_NOT_FOUND, f"Couldn't found resource")
48-
49-
if resource.kind not in ["Deployment", "DaemonSet", "StatefulSet", "DeploymentConfig", "Rollout"]:
50-
raise ActionException(
51-
ErrorCodes.RESOURCE_NOT_SUPPORTED, f"Rollout restart is not supported for resource {resource.kind}"
52-
)
49+
raise ActionException(ErrorCodes.RESOURCE_NOT_FOUND, "Couldn't found resource")
5350

5451
name = resource.metadata.name
5552
namespace = resource.metadata.namespace
@@ -72,6 +69,17 @@ def rollout_restart(event: KubernetesResourceEvent):
7269
resource.update()
7370
return
7471

72+
if isinstance(resource, (CRDBase)):
73+
CustomObjectsApi().patch_namespaced_custom_object(
74+
group=resource.group,
75+
version=resource.version,
76+
namespace=namespace,
77+
plural=resource.plural,
78+
name=name,
79+
body=resource.rollout_restart_patch()
80+
)
81+
return
82+
7583
if isinstance(resource, Deployment):
7684
func_name = "patch_namespaced_deployment"
7785
elif isinstance(resource, StatefulSet):

scripts/generate_kubernetes_code.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def autogenerate_events(f: TextIO):
8181
from ....core.reporting.consts import FindingSubjectType, FindingSource
8282
from ....core.reporting.finding_subjects import KubeObjFindingSubject
8383
from robusta.integrations.kubernetes.custom_models import {CUSTOM_MODELS_IMPORTS}
84+
from robusta.integrations.kubernetes.custom_crds import CRDS_map
8485
"""
8586
)
8687
)
@@ -137,6 +138,16 @@ def autogenerate_events(f: TextIO):
137138

138139
f.write(f"{'}'}\n\n\n")
139140

141+
f.write(
142+
textwrap.dedent(
143+
"""\
144+
for cls in CRDS_map.values():
145+
LOADERS_MAPPINGS[cls.name.lower()] = (True, cls.readNamespaced)
146+
147+
"""
148+
)
149+
)
150+
140151
f.write(
141152
textwrap.dedent(
142153
"""\

src/robusta/core/discovery/discovery.py

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from collections import defaultdict
66
from concurrent.futures.process import BrokenProcessPool, ProcessPoolExecutor
77
from typing import Dict, List, Optional, Union
8-
8+
import dpath.util
99
import prometheus_client
1010
from hikaru.model.rel_1_26 import (
1111
Container,
@@ -50,6 +50,7 @@
5050
DISCOVERY_PROCESS_TIMEOUT_SEC,
5151
IS_OPENSHIFT,
5252
OPENSHIFT_GROUPS,
53+
CUSTOM_CRD
5354
)
5455
from robusta.core.model.helm_release import HelmRelease
5556
from robusta.core.model.jobs import JobInfo
@@ -58,6 +59,7 @@
5859
from robusta.core.model.openshift_group import OpenshiftGroup
5960
from robusta.core.model.services import ContainerInfo, ServiceConfig, ServiceInfo, VolumeInfo
6061
from robusta.integrations.kubernetes.custom_models import DeploymentConfig, DictToK8sObj, Rollout
62+
from robusta.integrations.kubernetes.custom_crds import CRDS_map
6163
from robusta.patch.patch import create_monkey_patches
6264
from robusta.utils.cluster_provider_discovery import cluster_provider
6365
from robusta.utils.stack_tracer import StackTracer
@@ -191,11 +193,53 @@ def discovery_process() -> DiscoveryResults:
191193
node_requests = defaultdict(list) # map between node name, to request of pods running on it
192194
active_services: List[ServiceInfo] = []
193195
openshift_groups: List[OpenshiftGroup] = []
194-
196+
continue_ref: Optional[str] = None
195197
# discover micro services
198+
196199
try:
200+
for cls_name in CUSTOM_CRD:
201+
if (cls := CRDS_map.get(cls_name)) is None:
202+
continue
203+
204+
for _ in range(DISCOVERY_MAX_BATCHES):
205+
try:
206+
crd_res = client.CustomObjectsApi().list_cluster_custom_object(
207+
group=cls.group,
208+
version=cls.version,
209+
plural=cls.plural,
210+
limit=DISCOVERY_BATCH_SIZE,
211+
_continue=continue_ref,
212+
)
213+
except Exception:
214+
logging.exception(msg=f"Failed to list {cls.name} from api.")
215+
break
216+
217+
for crd in crd_res.get("items", []):
218+
try:
219+
meta = DictToK8sObj(crd.get("metadata"), V1ObjectMeta)
220+
active_services.extend(
221+
[
222+
Discovery.__create_service_info(
223+
meta=meta,
224+
kind=cls.name,
225+
containers=[],
226+
volumes=[],
227+
total_pods=dpath.util.get(crd, cls.total_pods_path, default=0),
228+
ready_pods=dpath.util.get(crd, cls.ready_pods_path, default=0),
229+
is_helm_release=is_release_managed_by_helm(
230+
annotations=meta.annotations, labels=meta.labels
231+
),
232+
)
233+
]
234+
)
235+
except Exception:
236+
logging.exception(msg=f"Failed to parse {cls.name} {crd}")
237+
continue
238+
239+
continue_ref = crd_res.get("metadata", {}).get("continue")
240+
if not continue_ref:
241+
break
197242

198-
continue_ref: Optional[str] = None
199243
if IS_OPENSHIFT:
200244
for _ in range(DISCOVERY_MAX_BATCHES):
201245
try:

src/robusta/core/discovery/resource_names.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from robusta.integrations.kubernetes.custom_models import DeploymentConfig, Rollout
77
from robusta.utils.error_codes import ActionException, ErrorCodes
8-
8+
from robusta.integrations.kubernetes.custom_crds import CRDS_map
99

1010
class ResourceLister(BaseModel):
1111
list_all: Callable
@@ -66,6 +66,13 @@ class ResourceLister(BaseModel):
6666
}
6767

6868

69+
for cls in CRDS_map.values():
70+
LISTERS[cls.name.lower()] = ResourceLister(
71+
list_all=cls.list_for_all_namespaces,
72+
list_namespaced=cls.list_namespaced,
73+
)
74+
75+
6976
class ResourceNameLister:
7077
@staticmethod
7178
def list_resource_names(kind: str, namespace: str = None) -> List[str]:

src/robusta/core/model/env_vars.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,6 @@ def load_bool(env_var, default: bool):
135135

136136
# simple calculated values (not direct environment vars)
137137
SENTRY_ENABLED = SEND_ADDITIONAL_TELEMETRY and SENTRY_DSN
138+
139+
# enable custom CRDs supported by robusta "["StrimziPodSet", "Cluster"]"
140+
CUSTOM_CRD = json.loads(os.environ.get("CUSTOM_CRD", "[]"))

0 commit comments

Comments
 (0)