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 }