istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/schema/kubeclient/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 kubeclient
    16  
    17  import (
    18  	"context"
    19  
    20  	kubeext "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    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/watch"
    26  	"k8s.io/client-go/dynamic"
    27  	"k8s.io/client-go/kubernetes"
    28  	"k8s.io/client-go/metadata"
    29  	"k8s.io/client-go/tools/cache"
    30  	gatewayapiclient "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned"
    31  
    32  	istioclient "istio.io/client-go/pkg/clientset/versioned"
    33  	"istio.io/istio/pilot/pkg/util/informermetric"
    34  	"istio.io/istio/pkg/config"
    35  	"istio.io/istio/pkg/config/schema/kubetypes"
    36  	"istio.io/istio/pkg/kube/informerfactory"
    37  	ktypes "istio.io/istio/pkg/kube/kubetypes"
    38  	"istio.io/istio/pkg/log"
    39  	"istio.io/istio/pkg/ptr"
    40  	"istio.io/istio/pkg/typemap"
    41  )
    42  
    43  type ClientGetter interface {
    44  	// Ext returns the API extensions client.
    45  	Ext() kubeext.Interface
    46  
    47  	// Kube returns the core kube client
    48  	Kube() kubernetes.Interface
    49  
    50  	// Dynamic client.
    51  	Dynamic() dynamic.Interface
    52  
    53  	// Metadata returns the Metadata kube client.
    54  	Metadata() metadata.Interface
    55  
    56  	// Istio returns the Istio kube client.
    57  	Istio() istioclient.Interface
    58  
    59  	// GatewayAPI returns the gateway-api kube client.
    60  	GatewayAPI() gatewayapiclient.Interface
    61  
    62  	// Informers returns an informer factory.
    63  	Informers() informerfactory.InformerFactory
    64  }
    65  
    66  func GetInformerFiltered[T runtime.Object](c ClientGetter, opts ktypes.InformerOptions) informerfactory.StartableInformer {
    67  	reg := typemap.Get[TypeRegistration[T]](registerTypes)
    68  	if reg != nil {
    69  		// This is registered type
    70  		tr := *reg
    71  		return c.Informers().InformerFor(tr.GetGVR(), opts, func() cache.SharedIndexInformer {
    72  			inf := cache.NewSharedIndexInformer(
    73  				tr.ListWatch(c, opts),
    74  				tr.Object(),
    75  				0,
    76  				cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
    77  			)
    78  			setupInformer(opts, inf)
    79  			return inf
    80  		})
    81  	}
    82  	return GetInformerFilteredFromGVR(c, opts, kubetypes.MustGVRFromType[T]())
    83  }
    84  
    85  func GetInformerFilteredFromGVR(c ClientGetter, opts ktypes.InformerOptions, g schema.GroupVersionResource) informerfactory.StartableInformer {
    86  	switch opts.InformerType {
    87  	case ktypes.DynamicInformer:
    88  		return getInformerFilteredDynamic(c, opts, g)
    89  	case ktypes.MetadataInformer:
    90  		return getInformerFilteredMetadata(c, opts, g)
    91  	default:
    92  		return getInformerFiltered(c, opts, g)
    93  	}
    94  }
    95  
    96  func getInformerFilteredDynamic(c ClientGetter, opts ktypes.InformerOptions, g schema.GroupVersionResource) informerfactory.StartableInformer {
    97  	return c.Informers().InformerFor(g, opts, func() cache.SharedIndexInformer {
    98  		inf := cache.NewSharedIndexInformerWithOptions(
    99  			&cache.ListWatch{
   100  				ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   101  					options.FieldSelector = opts.FieldSelector
   102  					options.LabelSelector = opts.LabelSelector
   103  					return c.Dynamic().Resource(g).Namespace(opts.Namespace).List(context.Background(), options)
   104  				},
   105  				WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   106  					options.FieldSelector = opts.FieldSelector
   107  					options.LabelSelector = opts.LabelSelector
   108  					return c.Dynamic().Resource(g).Namespace(opts.Namespace).Watch(context.Background(), options)
   109  				},
   110  			},
   111  			&unstructured.Unstructured{},
   112  			cache.SharedIndexInformerOptions{
   113  				ResyncPeriod:      0,
   114  				Indexers:          cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
   115  				ObjectDescription: g.String(),
   116  			},
   117  		)
   118  		setupInformer(opts, inf)
   119  		return inf
   120  	})
   121  }
   122  
   123  func getInformerFilteredMetadata(c ClientGetter, opts ktypes.InformerOptions, g schema.GroupVersionResource) informerfactory.StartableInformer {
   124  	return c.Informers().InformerFor(g, opts, func() cache.SharedIndexInformer {
   125  		inf := cache.NewSharedIndexInformerWithOptions(
   126  			&cache.ListWatch{
   127  				ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   128  					options.FieldSelector = opts.FieldSelector
   129  					options.LabelSelector = opts.LabelSelector
   130  					return c.Metadata().Resource(g).Namespace(opts.Namespace).List(context.Background(), options)
   131  				},
   132  				WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   133  					options.FieldSelector = opts.FieldSelector
   134  					options.LabelSelector = opts.LabelSelector
   135  					return c.Metadata().Resource(g).Namespace(opts.Namespace).Watch(context.Background(), options)
   136  				},
   137  			},
   138  			&metav1.PartialObjectMetadata{},
   139  			cache.SharedIndexInformerOptions{
   140  				ResyncPeriod:      0,
   141  				Indexers:          cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
   142  				ObjectDescription: g.String(),
   143  			},
   144  		)
   145  		setupInformer(opts, inf)
   146  		return inf
   147  	})
   148  }
   149  
   150  func setupInformer(opts ktypes.InformerOptions, inf cache.SharedIndexInformer) {
   151  	// It is important to set this in the newFunc rather than after InformerFor to avoid
   152  	// https://github.com/kubernetes/kubernetes/issues/117869
   153  	if opts.ObjectTransform != nil {
   154  		_ = inf.SetTransform(opts.ObjectTransform)
   155  	} else {
   156  		_ = inf.SetTransform(stripUnusedFields)
   157  	}
   158  	if err := inf.SetWatchErrorHandler(informermetric.ErrorHandlerForCluster(opts.Cluster)); err != nil {
   159  		log.Debugf("failed to set watch handler, informer may already be started: %v", err)
   160  	}
   161  }
   162  
   163  // stripUnusedFields is the transform function for shared informers,
   164  // it removes unused fields from objects before they are stored in the cache to save memory.
   165  func stripUnusedFields(obj any) (any, error) {
   166  	t, ok := obj.(metav1.ObjectMetaAccessor)
   167  	if !ok {
   168  		// shouldn't happen
   169  		return obj, nil
   170  	}
   171  	// ManagedFields is large and we never use it
   172  	t.GetObjectMeta().SetManagedFields(nil)
   173  	return obj, nil
   174  }
   175  
   176  var registerTypes = typemap.NewTypeMap()
   177  
   178  // Register provides the TypeRegistration to the underlying
   179  // store to enable dynamic object translation
   180  func Register[T runtime.Object](
   181  	gvr schema.GroupVersionResource,
   182  	gvk schema.GroupVersionKind,
   183  	list func(c ClientGetter, namespace string, o metav1.ListOptions) (runtime.Object, error),
   184  	watch func(c ClientGetter, namespace string, o metav1.ListOptions) (watch.Interface, error),
   185  ) {
   186  	reg := &internalTypeReg[T]{
   187  		gvr:   gvr,
   188  		gvk:   config.FromKubernetesGVK(gvk),
   189  		list:  list,
   190  		watch: watch,
   191  	}
   192  	kubetypes.Register[T](reg)
   193  	typemap.Set[TypeRegistration[T]](registerTypes, reg)
   194  }
   195  
   196  // TypeRegistration represents the necessary methods
   197  // to provide a custom type to the kubeclient informer mechanism
   198  type TypeRegistration[T runtime.Object] interface {
   199  	kubetypes.RegisterType[T]
   200  
   201  	// ListWatchFunc provides the necessary methods for list and
   202  	// watch for the informer
   203  	ListWatch(c ClientGetter, opts ktypes.InformerOptions) cache.ListerWatcher
   204  }
   205  
   206  type internalTypeReg[T runtime.Object] struct {
   207  	list  func(c ClientGetter, namespace string, o metav1.ListOptions) (runtime.Object, error)
   208  	watch func(c ClientGetter, namespace string, o metav1.ListOptions) (watch.Interface, error)
   209  	gvr   schema.GroupVersionResource
   210  	gvk   config.GroupVersionKind
   211  }
   212  
   213  func (t *internalTypeReg[T]) GetGVK() config.GroupVersionKind {
   214  	return t.gvk
   215  }
   216  
   217  func (t *internalTypeReg[T]) GetGVR() schema.GroupVersionResource {
   218  	return t.gvr
   219  }
   220  
   221  func (t *internalTypeReg[T]) ListWatch(c ClientGetter, o ktypes.InformerOptions) cache.ListerWatcher {
   222  	return &cache.ListWatch{
   223  		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   224  			options.FieldSelector = o.FieldSelector
   225  			options.LabelSelector = o.LabelSelector
   226  			return t.list(c, o.Namespace, options)
   227  		},
   228  		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   229  			options.FieldSelector = o.FieldSelector
   230  			options.LabelSelector = o.LabelSelector
   231  			return t.watch(c, o.Namespace, options)
   232  		},
   233  	}
   234  }
   235  
   236  func (t *internalTypeReg[T]) Object() T {
   237  	return ptr.Empty[T]()
   238  }