Skip to content

Commit effae32

Browse files
committed
Backend support for CRDs visualization
1 parent 4d718f6 commit effae32

4 files changed

Lines changed: 98 additions & 35 deletions

File tree

docs/setup-robusta/crds.rst

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,16 @@ To enable CRD monitoring, add the ``customClusterRoleRules`` section to your Rob
2828

2929
.. code-block:: yaml
3030
31-
customClusterRoleRules:
32-
- apiGroups:
33-
- "*"
34-
resources:
35-
- "*"
36-
verbs:
37-
- "list"
38-
- "get"
39-
- "watch"
31+
runner:
32+
customClusterRoleRules:
33+
- apiGroups:
34+
- "*"
35+
resources:
36+
- "*"
37+
verbs:
38+
- "list"
39+
- "get"
40+
- "watch"
4041
4142
.. warning::
4243
The above configuration grants read access to all resources. For production environments, it's recommended to limit access to specific CRDs only.
@@ -50,7 +51,8 @@ For better security, specify only the CRDs you need to monitor:
5051

5152
.. code-block:: yaml
5253
53-
customClusterRoleRules:
54+
runner:
55+
customClusterRoleRules:
5456
- apiGroups:
5557
- "cert-manager.io"
5658
resources:
@@ -98,14 +100,15 @@ Using Holmes to Generate Configuration
98100
99101
I want to add read only cluster roles for all the crds in my cluster.
100102
This is the format for adding one:
101-
customClusterRoleRules:
102-
- apiGroups:
103-
- "storage.k8s.io"
104-
resources:
105-
- "storageclasses"
106-
verbs:
107-
- "list"
108-
- "get"
103+
runner:
104+
customClusterRoleRules:
105+
- apiGroups:
106+
- "storage.k8s.io"
107+
resources:
108+
- "storageclasses"
109+
verbs:
110+
- "list"
111+
- "get"
109112
Prepare my config
110113
111114
3. Holmes will analyze your cluster and generate a complete configuration including all CRDs

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ rules:
77
{{- if .Values.runner.customClusterRoleRules }}
88
{{ toYaml .Values.runner.customClusterRoleRules | indent 2 }}
99
{{- end }}
10+
- apiGroups:
11+
- "apiextensions.k8s.io"
12+
resources:
13+
- "customresourcedefinitions"
14+
verbs:
15+
- "list"
16+
- "get"
17+
1018
- apiGroups:
1119
- ""
1220
resources:

src/robusta/core/playbooks/internal/CRD_API_DOCUMENTATION.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ Lists all Custom Resource Definitions available in the cluster.
131131
132132
#### Response
133133
- Returns a **JsonBlock** with detailed CRD information
134-
- Also returns a **TableBlock** with summary (Kind, API Version, Scope, Plural)
134+
- Also returns a **TableBlock** with summary (Kind, API Version, Scope, Plural, Created At)
135135
136136
#### Example Usage
137137
```yaml
@@ -148,6 +148,7 @@ action: fetch_crds
148148
"kind": "Certificate",
149149
"plural": "certificates",
150150
"scope": "Namespaced",
151+
"createdAt": "2024-03-15T10:23:45Z",
151152
"additionalPrinterColumns": [
152153
{
153154
"name": "READY",
@@ -171,6 +172,7 @@ action: fetch_crds
171172
"kind": "Prometheus",
172173
"plural": "prometheuses",
173174
"scope": "Namespaced",
175+
"createdAt": "2024-02-10T14:52:18Z",
174176
"additionalPrinterColumns": [
175177
{
176178
"name": "VERSION",
@@ -188,11 +190,11 @@ action: fetch_crds
188190
```
189191

190192
**Table Summary:**
191-
| Kind | API Version | Scope | Plural |
192-
|------|------------|-------|--------|
193-
| Certificate | cert-manager.io/v1 | Namespaced | certificates |
194-
| Prometheus | monitoring.coreos.com/v1 | Namespaced | prometheuses |
195-
| ServiceMonitor | monitoring.coreos.com/v1 | Namespaced | servicemonitors |
193+
| Kind | API Version | Scope | Plural | Created At |
194+
|------|------------|-------|--------|------------|
195+
| Certificate | cert-manager.io/v1 | Namespaced | certificates | 2024-03-15T10:23:45Z |
196+
| Prometheus | monitoring.coreos.com/v1 | Namespaced | prometheuses | 2024-02-10T14:52:18Z |
197+
| ServiceMonitor | monitoring.coreos.com/v1 | Namespaced | servicemonitors | 2024-02-10T14:53:01Z |
196198

197199
---
198200

src/robusta/core/playbooks/internal/crds.py

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import logging
3+
import re
34
import subprocess
45
from typing import Optional, List
56

@@ -179,7 +180,7 @@ def fetch_resource_events(event: ExecutionBaseEvent, params: ResourceParams):
179180
def fetch_crds(event: ExecutionBaseEvent):
180181
"""
181182
Fetch all custom resource definitions.
182-
Returns a JsonBlock with apiVersion, kind, scope, and additionalPrinterColumns for each CRD.
183+
Returns a JsonBlock with apiVersion, kind, scope, createdAt, and additionalPrinterColumns for each CRD.
183184
"""
184185
finding = Finding(
185186
title="Custom Resource Definitions",
@@ -195,6 +196,7 @@ def fetch_crds(event: ExecutionBaseEvent):
195196

196197
crd_list = []
197198
for item in items:
199+
metadata = item.get("metadata", {})
198200
spec = item.get("spec", {})
199201
versions = spec.get("versions", [])
200202

@@ -222,6 +224,7 @@ def fetch_crds(event: ExecutionBaseEvent):
222224
"kind": spec.get("names", {}).get("kind", ""),
223225
"plural": spec.get("names", {}).get("plural", ""),
224226
"scope": spec.get("scope", ""),
227+
"createdAt": metadata.get("creationTimestamp", ""),
225228
"additionalPrinterColumns": additional_columns
226229
}
227230
crd_list.append(crd_info)
@@ -281,19 +284,66 @@ def fetch_cr_instances(event: ExecutionBaseEvent, params: CRInstancesParams):
281284
for field in params.fields:
282285
# Support nested field paths using dot notation. Remove leading dot if present (JSONPath style)
283286
clean_field = field.lstrip('.')
284-
field_parts = clean_field.split(".")
285-
value = item
286287

287-
try:
288-
for part in field_parts:
289-
if isinstance(value, dict):
290-
value = value.get(part, None)
288+
# Check if this is a JSONPath filter pattern like "status.conditions[?(@.type == 'Reconciled')].status"
289+
# or "spec.containers[?(@.name == 'main')].image"
290+
# Pattern: (path).(array)[?(@.field == 'value')](.afterField)
291+
filter_pattern = r"^((?:.*?\.)?)([^.\[]+)\[\?\(@\.(\w+)\s*==\s*['\"]([^'\"]+)['\"]\)\](?:\.(.+))?$"
292+
match = re.match(filter_pattern, clean_field)
293+
294+
if match:
295+
path_to_array = match.group(1).rstrip('.') # e.g., "status" or "" (empty for root-level)
296+
array_name = match.group(2) # e.g., "conditions", "containers", "volumes"
297+
filter_field = match.group(3) # e.g., "type", "name", "id"
298+
filter_value = match.group(4) # e.g., "Reconciled", "main", "data"
299+
field_after = match.group(5) # e.g., "status", "image", None
300+
301+
try:
302+
value = item
303+
if path_to_array:
304+
for part in path_to_array.split("."):
305+
if isinstance(value, dict):
306+
value = value.get(part, None)
307+
else:
308+
value = None
309+
break
310+
311+
if value and isinstance(value, dict):
312+
array_items = value.get(array_name, [])
313+
else:
314+
array_items = None
315+
316+
if array_items and isinstance(array_items, list):
317+
for array_item in array_items:
318+
if isinstance(array_item, dict) and array_item.get(filter_field) == filter_value:
319+
if field_after:
320+
value = array_item.get(field_after)
321+
else:
322+
value = array_item
323+
break
324+
else:
325+
value = None
291326
else:
292327
value = None
293-
break
294-
instance_info[field] = value
295-
except (KeyError, TypeError):
296-
instance_info[field] = None
328+
329+
instance_info[field] = value
330+
except (KeyError, TypeError, AttributeError):
331+
instance_info[field] = None
332+
else:
333+
# Regular dot notation path
334+
field_parts = clean_field.split(".")
335+
value = item
336+
337+
try:
338+
for part in field_parts:
339+
if isinstance(value, dict):
340+
value = value.get(part, None)
341+
else:
342+
value = None
343+
break
344+
instance_info[field] = value
345+
except (KeyError, TypeError):
346+
instance_info[field] = None
297347

298348
instance_list.append(instance_info)
299349

0 commit comments

Comments
 (0)