istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/kube/controllers/common.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package controllers 16 17 import ( 18 "fmt" 19 20 kerrors "k8s.io/apimachinery/pkg/api/errors" 21 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 23 "k8s.io/apimachinery/pkg/runtime" 24 "k8s.io/apimachinery/pkg/runtime/schema" 25 "k8s.io/apimachinery/pkg/types" 26 "k8s.io/client-go/tools/cache" 27 28 "istio.io/istio/pkg/config" 29 "istio.io/istio/pkg/config/schema/gvk" 30 istiolog "istio.io/istio/pkg/log" 31 ) 32 33 var log = istiolog.RegisterScope("controllers", "common controller logic") 34 35 // Object is a union of runtime + meta objects. Essentially every k8s object meets this interface. 36 // and certainly all that we care about. 37 type Object interface { 38 metav1.Object 39 runtime.Object 40 } 41 42 type ComparableObject interface { 43 comparable 44 Object 45 } 46 47 // IsNil works around comparing generic types 48 func IsNil[O ComparableObject](o O) bool { 49 var t O 50 return o == t 51 } 52 53 // UnstructuredToGVR extracts the GVR of an unstructured resource. This is useful when using dynamic 54 // clients. 55 func UnstructuredToGVR(u unstructured.Unstructured) (schema.GroupVersionResource, error) { 56 res := schema.GroupVersionResource{} 57 gv, err := schema.ParseGroupVersion(u.GetAPIVersion()) 58 if err != nil { 59 return res, err 60 } 61 62 gk := config.GroupVersionKind{ 63 Group: gv.Group, 64 Version: gv.Version, 65 Kind: u.GetKind(), 66 } 67 found, ok := gvk.ToGVR(gk) 68 if !ok { 69 return res, fmt.Errorf("unknown gvk: %v", gk) 70 } 71 return found, nil 72 } 73 74 // ObjectToGVR extracts the GVR of an unstructured resource. This is useful when using dynamic 75 // clients. 76 func ObjectToGVR(u Object) (schema.GroupVersionResource, error) { 77 g := u.GetObjectKind().GroupVersionKind() 78 79 gk := config.GroupVersionKind{ 80 Group: g.Group, 81 Version: g.Version, 82 Kind: g.Kind, 83 } 84 found, ok := gvk.ToGVR(gk) 85 if !ok { 86 return schema.GroupVersionResource{}, fmt.Errorf("unknown gvk: %v", gk) 87 } 88 return found, nil 89 } 90 91 // EnqueueForParentHandler returns a handler that will enqueue the parent (by ownerRef) resource 92 func EnqueueForParentHandler(q Queue, kind config.GroupVersionKind) func(obj Object) { 93 handler := func(obj Object) { 94 for _, ref := range obj.GetOwnerReferences() { 95 refGV, err := schema.ParseGroupVersion(ref.APIVersion) 96 if err != nil { 97 log.Errorf("could not parse OwnerReference api version %q: %v", ref.APIVersion, err) 98 continue 99 } 100 if refGV.Group == kind.Group && ref.Kind == kind.Kind { 101 // We found a parent we care about, add it to the queue 102 q.Add(types.NamespacedName{ 103 // Reference doesn't have namespace, but its always same-namespace, so use objects 104 Namespace: obj.GetNamespace(), 105 Name: ref.Name, 106 }) 107 } 108 } 109 } 110 return handler 111 } 112 113 // EventType represents a registry update event 114 type EventType int 115 116 const ( 117 // EventAdd is sent when an object is added 118 EventAdd EventType = iota 119 120 // EventUpdate is sent when an object is modified 121 // Captures the modified object 122 EventUpdate 123 124 // EventDelete is sent when an object is deleted 125 // Captures the object at the last known state 126 EventDelete 127 ) 128 129 func (event EventType) String() string { 130 out := "unknown" 131 switch event { 132 case EventAdd: 133 out = "add" 134 case EventUpdate: 135 out = "update" 136 case EventDelete: 137 out = "delete" 138 } 139 return out 140 } 141 142 type Event struct { 143 Old Object 144 New Object 145 Event EventType 146 } 147 148 func (e Event) Latest() Object { 149 if e.New != nil { 150 return e.New 151 } 152 return e.Old 153 } 154 155 func FromEventHandler(handler func(o Event)) cache.ResourceEventHandler { 156 return cache.ResourceEventHandlerFuncs{ 157 AddFunc: func(obj any) { 158 o := ExtractObject(obj) 159 if o == nil { 160 return 161 } 162 handler(Event{ 163 New: o, 164 Event: EventAdd, 165 }) 166 }, 167 UpdateFunc: func(oldInterface, newInterface any) { 168 oldObj := ExtractObject(oldInterface) 169 if oldObj == nil { 170 return 171 } 172 newObj := ExtractObject(newInterface) 173 if newObj == nil { 174 return 175 } 176 handler(Event{ 177 Old: oldObj, 178 New: newObj, 179 Event: EventUpdate, 180 }) 181 }, 182 DeleteFunc: func(obj any) { 183 o := ExtractObject(obj) 184 if o == nil { 185 return 186 } 187 handler(Event{ 188 Old: o, 189 Event: EventDelete, 190 }) 191 }, 192 } 193 } 194 195 // ObjectHandler returns a handler that will act on the latest version of an object 196 // This means Add/Update/Delete are all handled the same and are just used to trigger reconciling. 197 func ObjectHandler(handler func(o Object)) cache.ResourceEventHandler { 198 h := func(obj any) { 199 o := ExtractObject(obj) 200 if o == nil { 201 return 202 } 203 handler(o) 204 } 205 return cache.ResourceEventHandlerFuncs{ 206 AddFunc: h, 207 UpdateFunc: func(oldObj, newObj any) { 208 h(newObj) 209 }, 210 DeleteFunc: h, 211 } 212 } 213 214 // FilteredObjectHandler returns a handler that will act on the latest version of an object 215 // This means Add/Update/Delete are all handled the same and are just used to trigger reconciling. 216 // If filters are set, returning 'false' will exclude the event. For Add and Deletes, the filter will be based 217 // on the new or old item. For updates, the item will be handled if either the new or the old object is updated. 218 func FilteredObjectHandler(handler func(o Object), filter func(o Object) bool) cache.ResourceEventHandler { 219 return filteredObjectHandler(handler, false, filter) 220 } 221 222 // FilteredObjectSpecHandler returns a handler that will act on the latest version of an object 223 // This means Add/Update/Delete are all handled the same and are just used to trigger reconciling. 224 // Unlike FilteredObjectHandler, the handler is only trigger when the resource spec changes (ie resourceVersion) 225 // If filters are set, returning 'false' will exclude the event. For Add and Deletes, the filter will be based 226 // on the new or old item. For updates, the item will be handled if either the new or the old object is updated. 227 func FilteredObjectSpecHandler(handler func(o Object), filter func(o Object) bool) cache.ResourceEventHandler { 228 return filteredObjectHandler(handler, true, filter) 229 } 230 231 func filteredObjectHandler(handler func(o Object), onlyIncludeSpecChanges bool, filter func(o Object) bool) cache.ResourceEventHandler { 232 single := func(obj any) { 233 o := ExtractObject(obj) 234 if o == nil { 235 return 236 } 237 if !filter(o) { 238 return 239 } 240 handler(o) 241 } 242 return cache.ResourceEventHandlerFuncs{ 243 AddFunc: single, 244 UpdateFunc: func(oldInterface, newInterface any) { 245 oldObj := ExtractObject(oldInterface) 246 if oldObj == nil { 247 return 248 } 249 newObj := ExtractObject(newInterface) 250 if newObj == nil { 251 return 252 } 253 if onlyIncludeSpecChanges && oldObj.GetResourceVersion() == newObj.GetResourceVersion() { 254 return 255 } 256 newer := filter(newObj) 257 older := filter(oldObj) 258 if !newer && !older { 259 return 260 } 261 handler(newObj) 262 }, 263 DeleteFunc: single, 264 } 265 } 266 267 // Extract pulls a T from obj, handling tombstones. 268 // This will return nil if the object cannot be extracted. 269 func Extract[T Object](obj any) T { 270 var empty T 271 if obj == nil { 272 return empty 273 } 274 o, ok := obj.(T) 275 if !ok { 276 tombstone, ok := obj.(cache.DeletedFinalStateUnknown) 277 if !ok { 278 log.Errorf("couldn't get object from tombstone: %+v", obj) 279 return empty 280 } 281 o, ok = tombstone.Obj.(T) 282 if !ok { 283 log.Errorf("tombstone contained object that is not an object (key:%v, obj:%T)", tombstone.Key, tombstone.Obj) 284 return empty 285 } 286 } 287 return o 288 } 289 290 func ExtractObject(obj any) Object { 291 return Extract[Object](obj) 292 } 293 294 // IgnoreNotFound returns nil on NotFound errors. 295 // All other values that are not NotFound errors or nil are returned unmodified. 296 func IgnoreNotFound(err error) error { 297 if kerrors.IsNotFound(err) { 298 return nil 299 } 300 return err 301 } 302 303 // EventHandler mirrors ResourceEventHandlerFuncs, but takes typed T objects instead of any. 304 type EventHandler[T Object] struct { 305 AddFunc func(obj T) 306 AddExtendedFunc func(obj T, initialSync bool) 307 UpdateFunc func(oldObj, newObj T) 308 DeleteFunc func(obj T) 309 } 310 311 func (e EventHandler[T]) OnAdd(obj interface{}, initialSync bool) { 312 if e.AddExtendedFunc != nil { 313 e.AddExtendedFunc(Extract[T](obj), initialSync) 314 } else if e.AddFunc != nil { 315 e.AddFunc(Extract[T](obj)) 316 } 317 } 318 319 func (e EventHandler[T]) OnUpdate(oldObj, newObj interface{}) { 320 if e.UpdateFunc != nil { 321 e.UpdateFunc(Extract[T](oldObj), Extract[T](newObj)) 322 } 323 } 324 325 func (e EventHandler[T]) OnDelete(obj interface{}) { 326 if e.DeleteFunc != nil { 327 e.DeleteFunc(Extract[T](obj)) 328 } 329 } 330 331 var _ cache.ResourceEventHandler = EventHandler[Object]{} 332 333 type Shutdowner interface { 334 ShutdownHandlers() 335 } 336 337 // ShutdownAll is a simple helper to shutdown all informers 338 func ShutdownAll(s ...Shutdowner) { 339 for _, h := range s { 340 h.ShutdownHandlers() 341 } 342 }