11import json
22import logging
3+ import re
34import subprocess
45from typing import Optional , List
56
@@ -179,7 +180,7 @@ def fetch_resource_events(event: ExecutionBaseEvent, params: ResourceParams):
179180def 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