11package resources
22
33import (
4+ "math"
45 "reflect"
56 "strings"
67
78 appsv1 "k8s.io/api/apps/v1"
89 corev1 "k8s.io/api/core/v1"
10+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
11+ "k8s.io/apimachinery/pkg/runtime"
12+ "k8s.io/apimachinery/pkg/runtime/schema"
913 "sigs.k8s.io/controller-runtime/pkg/client"
1014 "sigs.k8s.io/controller-runtime/pkg/event"
1115 "sigs.k8s.io/controller-runtime/pkg/predicate"
1216 gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
1317
1418 dscv2 "github.com/opendatahub-io/opendatahub-operator/v2/api/datasciencecluster/v2"
15- dsciv2 "github.com/opendatahub-io/opendatahub-operator/v2/api/dscinitialization/v2 "
19+ "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster/gvk "
1620 "github.com/opendatahub-io/opendatahub-operator/v2/pkg/resources"
1721)
1822
1923var _ predicate.Predicate = DeploymentPredicate {}
2024
25+ // hasGVK checks if an unstructured object matches the expected GroupVersionKind.
26+ func hasGVK (u * unstructured.Unstructured , expected schema.GroupVersionKind ) bool {
27+ objGVK := u .GetObjectKind ().GroupVersionKind ()
28+ return objGVK == expected
29+ }
30+
2131type DeploymentPredicate struct {
2232 predicate.Funcs
2333}
2434
2535// Update implements default UpdateEvent filter for validating generation change.
36+ // Works with both typed *appsv1.Deployment and *unstructured.Unstructured objects.
2637func (DeploymentPredicate ) Update (e event.UpdateEvent ) bool {
2738 if e .ObjectOld == nil || e .ObjectNew == nil {
2839 return false
2940 }
3041
31- oldDeployment , ok := e .ObjectOld .(* appsv1.Deployment )
32- if ! ok {
42+ oldReplicas , oldReadyReplicas , oldOk := getDeploymentStatus (e .ObjectOld )
43+ newReplicas , newReadyReplicas , newOk := getDeploymentStatus (e .ObjectNew )
44+
45+ if ! oldOk || ! newOk {
3346 return false
3447 }
3548
36- newDeployment , ok := e .ObjectNew .(* appsv1.Deployment )
37- if ! ok {
38- return false
49+ return e .ObjectOld .GetGeneration () != e .ObjectNew .GetGeneration () ||
50+ oldReplicas != newReplicas ||
51+ oldReadyReplicas != newReadyReplicas
52+ }
53+
54+ // getDeploymentStatus extracts replicas and readyReplicas from a Deployment object.
55+ // Supports both typed *appsv1.Deployment and *unstructured.Unstructured.
56+ func getDeploymentStatus (obj client.Object ) (int32 , int32 , bool ) {
57+ // Try typed Deployment first
58+ if deploy , isTyped := obj .(* appsv1.Deployment ); isTyped {
59+ return deploy .Status .Replicas , deploy .Status .ReadyReplicas , true
60+ }
61+
62+ u , ok := obj .(* unstructured.Unstructured )
63+ if ! ok || ! hasGVK (u , gvk .Deployment ) {
64+ return 0 , 0 , false
65+ }
66+
67+ status , found , err := unstructured .NestedMap (u .Object , "status" )
68+ if err != nil || ! found {
69+ return 0 , 0 , true // No status yet, but valid object
3970 }
4071
41- return oldDeployment .Generation != newDeployment .Generation ||
42- oldDeployment .Status .Replicas != newDeployment .Status .Replicas ||
43- oldDeployment .Status .ReadyReplicas != newDeployment .Status .ReadyReplicas
72+ return getInt32Field (status , "replicas" ), getInt32Field (status , "readyReplicas" ), true
73+ }
74+
75+ func getInt32Field (m map [string ]interface {}, field string ) int32 {
76+ val , found , err := unstructured .NestedInt64 (m , field )
77+ if err != nil || ! found {
78+ return 0
79+ }
80+
81+ if val < math .MinInt32 {
82+ return math .MinInt32
83+ }
84+ if val > math .MaxInt32 {
85+ return math .MaxInt32
86+ }
87+
88+ return int32 (val )
4489}
4590
4691func NewDeploymentPredicate () * DeploymentPredicate {
@@ -64,36 +109,96 @@ func Deleted() predicate.Funcs {
64109 }
65110}
66111
112+ func getConfigMapData (obj client.Object ) (any , bool ) {
113+ cm , ok := obj .(* corev1.ConfigMap )
114+ if ok {
115+ return cm .Data , true
116+ }
117+
118+ u , ok := obj .(* unstructured.Unstructured )
119+ if ! ok || ! hasGVK (u , gvk .ConfigMap ) {
120+ return nil , false
121+ }
122+
123+ data , _ , err := unstructured .NestedFieldNoCopy (u .Object , "data" )
124+ if err != nil {
125+ return nil , false
126+ }
127+ return data , true
128+ }
129+
67130// Content predicates moved from original controller.
68131var CMContentChangedPredicate = predicate.Funcs {
69132 UpdateFunc : func (e event.UpdateEvent ) bool {
70- oldCM , _ := e .ObjectOld .(* corev1.ConfigMap )
71- newCM , _ := e .ObjectNew .(* corev1.ConfigMap )
72- return ! reflect .DeepEqual (oldCM .Data , newCM .Data )
133+ oldData , oldOk := getConfigMapData (e .ObjectOld )
134+ newData , newOk := getConfigMapData (e .ObjectNew )
135+ if ! oldOk || ! newOk {
136+ return false
137+ }
138+ return ! reflect .DeepEqual (oldData , newData )
73139 },
74140}
75141
142+ func getSecretData (obj client.Object ) (any , bool ) {
143+ secret , ok := obj .(* corev1.Secret )
144+ if ok {
145+ return secret .Data , true
146+ }
147+
148+ u , ok := obj .(* unstructured.Unstructured )
149+ if ! ok || ! hasGVK (u , gvk .Secret ) {
150+ return nil , false
151+ }
152+
153+ data , _ , err := unstructured .NestedFieldNoCopy (u .Object , "data" )
154+ if err != nil {
155+ return nil , false
156+ }
157+ return data , true
158+ }
159+
76160var SecretContentChangedPredicate = predicate.Funcs {
77161 UpdateFunc : func (e event.UpdateEvent ) bool {
78- oldSecret , _ := e .ObjectOld .(* corev1.Secret )
79- newSecret , _ := e .ObjectNew .(* corev1.Secret )
80- return ! reflect .DeepEqual (oldSecret .Data , newSecret .Data )
162+ oldData , oldOk := getSecretData (e .ObjectOld )
163+ newData , newOk := getSecretData (e .ObjectNew )
164+ if ! oldOk || ! newOk {
165+ return false
166+ }
167+ return ! reflect .DeepEqual (oldData , newData )
81168 },
82169}
83170
171+ // FIXME: is this function correct? By default CreateFunc and UpdateFunc are true.
84172var DSCDeletionPredicate = predicate.Funcs {
85173 DeleteFunc : func (e event.DeleteEvent ) bool {
86174 return true
87175 },
88176}
89177
178+ func getDSC (obj client.Object ) (* dscv2.DataScienceCluster , bool ) {
179+ if dsc , ok := obj .(* dscv2.DataScienceCluster ); ok {
180+ return dsc , true
181+ }
182+
183+ u , err := resources .ToUnstructured (obj )
184+ if err != nil {
185+ return nil , false
186+ }
187+ dsc := & dscv2.DataScienceCluster {}
188+ err = runtime .DefaultUnstructuredConverter .FromUnstructured (u .Object , dsc )
189+ if err != nil {
190+ return nil , false
191+ }
192+ return dsc , true
193+ }
194+
90195var DSCComponentUpdatePredicate = predicate.Funcs {
91196 UpdateFunc : func (e event.UpdateEvent ) bool {
92- oldDSC , ok := e .ObjectOld .( * dscv2. DataScienceCluster )
197+ oldDSC , ok := getDSC ( e .ObjectOld )
93198 if ! ok {
94199 return false
95200 }
96- newDSC , ok := e .ObjectNew .( * dscv2. DataScienceCluster )
201+ newDSC , ok := getDSC ( e .ObjectNew )
97202 if ! ok {
98203 return false
99204 }
@@ -123,30 +228,6 @@ var DSCComponentUpdatePredicate = predicate.Funcs{
123228 },
124229}
125230
126- var DSCIReadiness = predicate.Funcs {
127- UpdateFunc : func (e event.UpdateEvent ) bool {
128- oldObj , ok := e .ObjectOld .(* dsciv2.DSCInitialization )
129- if ! ok {
130- return false
131- }
132- newObj , ok := e .ObjectNew .(* dsciv2.DSCInitialization )
133- if ! ok {
134- return false
135- }
136-
137- return oldObj .Status .Phase != newObj .Status .Phase
138- },
139- CreateFunc : func (e event.CreateEvent ) bool {
140- return false
141- },
142- DeleteFunc : func (e event.DeleteEvent ) bool {
143- return false
144- },
145- GenericFunc : func (e event.GenericEvent ) bool {
146- return false
147- },
148- }
149-
150231func AnnotationChanged (name string ) predicate.Funcs {
151232 return predicate.Funcs {
152233 CreateFunc : func (e event.CreateEvent ) bool {
@@ -236,7 +317,28 @@ func GatewayStatusChanged() predicate.Predicate {
236317
237318// HTTPRouteReferencesGateway returns a predicate that filters HTTPRoutes referencing the specified gateway.
238319func HTTPRouteReferencesGateway (gatewayName , gatewayNamespace string ) predicate.Predicate {
239- referencesGateway := func (httpRoute * gwapiv1.HTTPRoute ) bool {
320+ getHTTPRoute := func (obj client.Object ) (* gwapiv1.HTTPRoute , bool ) {
321+ httpRoute , ok := obj .(* gwapiv1.HTTPRoute )
322+ if ok {
323+ return httpRoute , true
324+ }
325+ u , err := resources .ToUnstructured (obj )
326+ if err != nil {
327+ return nil , false
328+ }
329+ httpRoute = & gwapiv1.HTTPRoute {}
330+ err = runtime .DefaultUnstructuredConverter .FromUnstructured (u .Object , httpRoute )
331+ if err != nil {
332+ return nil , false
333+ }
334+ return httpRoute , true
335+ }
336+
337+ referencesGateway := func (obj client.Object ) bool {
338+ httpRoute , ok := getHTTPRoute (obj )
339+ if ! ok {
340+ return false
341+ }
240342 for _ , ref := range httpRoute .Spec .ParentRefs {
241343 refNamespace := gatewayNamespace
242344 if ref .Namespace != nil {
@@ -251,32 +353,16 @@ func HTTPRouteReferencesGateway(gatewayName, gatewayNamespace string) predicate.
251353
252354 return predicate.Funcs {
253355 CreateFunc : func (e event.CreateEvent ) bool {
254- httpRoute , ok := e .Object .(* gwapiv1.HTTPRoute )
255- if ! ok {
256- return false
257- }
258- return referencesGateway (httpRoute )
356+ return referencesGateway (e .Object )
259357 },
260358 UpdateFunc : func (e event.UpdateEvent ) bool {
261- httpRoute , ok := e .ObjectNew .(* gwapiv1.HTTPRoute )
262- if ! ok {
263- return false
264- }
265- return referencesGateway (httpRoute )
359+ return referencesGateway (e .ObjectNew )
266360 },
267361 DeleteFunc : func (e event.DeleteEvent ) bool {
268- httpRoute , ok := e .Object .(* gwapiv1.HTTPRoute )
269- if ! ok {
270- return false
271- }
272- return referencesGateway (httpRoute )
362+ return referencesGateway (e .Object )
273363 },
274364 GenericFunc : func (e event.GenericEvent ) bool {
275- httpRoute , ok := e .Object .(* gwapiv1.HTTPRoute )
276- if ! ok {
277- return false
278- }
279- return referencesGateway (httpRoute )
365+ return referencesGateway (e .Object )
280366 },
281367 }
282368}
0 commit comments