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  }