istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/kube/krt/internal.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 krt 16 17 import ( 18 "fmt" 19 "reflect" 20 21 "go.uber.org/atomic" 22 "google.golang.org/protobuf/proto" 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 "k8s.io/client-go/tools/cache" 25 26 "istio.io/api/type/v1beta1" 27 "istio.io/istio/pkg/config" 28 "istio.io/istio/pkg/kube/controllers" 29 "istio.io/istio/pkg/ptr" 30 ) 31 32 // registerHandlerAsBatched is a helper to register the provided handler as a batched handler. This allows collections to 33 // only implement RegisterBatch. 34 func registerHandlerAsBatched[T any](c internalCollection[T], f func(o Event[T])) Syncer { 35 return c.RegisterBatch(func(events []Event[T], initialSync bool) { 36 for _, o := range events { 37 f(o) 38 } 39 }, true) 40 } 41 42 // castEvent converts an Event[I] to Event[O]. 43 // Caller is responsible for making sure these can be type converted. 44 // Typically this is converting to or from `any`. 45 func castEvent[I, O any](o Event[I]) Event[O] { 46 e := Event[O]{ 47 Event: o.Event, 48 } 49 if o.Old != nil { 50 e.Old = ptr.Of(any(*o.Old).(O)) 51 } 52 if o.New != nil { 53 e.New = ptr.Of(any(*o.New).(O)) 54 } 55 return e 56 } 57 58 func buildCollectionOptions(opts ...CollectionOption) collectionOptions { 59 c := &collectionOptions{} 60 for _, o := range opts { 61 o(c) 62 } 63 if c.stop == nil { 64 c.stop = make(chan struct{}) 65 } 66 return *c 67 } 68 69 // collectionOptions tracks options for a collection 70 type collectionOptions struct { 71 name string 72 augmentation func(o any) any 73 stop <-chan struct{} 74 } 75 76 // dependency is a specific thing that can be depended on 77 type dependency struct { 78 id collectionUID 79 collectionName string 80 // Filter over the collection 81 filter *filter 82 } 83 84 type erasedEventHandler = func(o []Event[any], initialSync bool) 85 86 // registerDependency is an internal interface for things that can register dependencies. 87 // This is called from Fetch to Collections, generally. 88 type registerDependency interface { 89 // Registers a dependency, returning true if it is finalized 90 registerDependency(*dependency, Syncer, func(f erasedEventHandler)) 91 name() string 92 } 93 94 // tryGetKey returns the Key for an object. If not possible, returns false 95 func tryGetKey[O any](a O) (Key[O], bool) { 96 as, ok := any(a).(string) 97 if ok { 98 return Key[O](as), true 99 } 100 ao, ok := any(a).(controllers.Object) 101 if ok { 102 k, _ := cache.MetaNamespaceKeyFunc(ao) 103 return Key[O](k), true 104 } 105 ac, ok := any(a).(config.Config) 106 if ok { 107 return Key[O](keyFunc(ac.Name, ac.Namespace)), true 108 } 109 arn, ok := any(a).(ResourceNamer) 110 if ok { 111 return Key[O](arn.ResourceName()), true 112 } 113 ack := GetApplyConfigKey(a) 114 if ack != nil { 115 return *ack, true 116 } 117 return "", false 118 } 119 120 // getLabels returns the labels for an object, if possible. 121 // Warning: this will panic if the labels is not available. 122 func getLabels(a any) map[string]string { 123 al, ok := a.(Labeler) 124 if ok { 125 return al.GetLabels() 126 } 127 pal, ok := any(&a).(Labeler) 128 if ok { 129 return pal.GetLabels() 130 } 131 ak, ok := a.(metav1.Object) 132 if ok { 133 return ak.GetLabels() 134 } 135 ac, ok := a.(config.Config) 136 if ok { 137 return ac.Labels 138 } 139 panic(fmt.Sprintf("No Labels, got %T", a)) 140 } 141 142 // getLabelSelector returns the labels for an object, if possible. 143 // Warning: this will panic if the labelSelectors is not available. 144 func getLabelSelector(a any) map[string]string { 145 ak, ok := a.(LabelSelectorer) 146 if ok { 147 return ak.GetLabelSelector() 148 } 149 val := reflect.ValueOf(a) 150 151 if val.Kind() == reflect.Ptr { 152 val = val.Elem() 153 } 154 155 specField := val.FieldByName("Spec") 156 if !specField.IsValid() { 157 panic(fmt.Sprintf("obj %T has no Spec", a)) 158 } 159 160 labelsField := specField.FieldByName("Selector") 161 if !labelsField.IsValid() { 162 panic(fmt.Sprintf("obj %T has no Selector", a)) 163 } 164 165 switch s := labelsField.Interface().(type) { 166 case *v1beta1.WorkloadSelector: 167 return s.GetMatchLabels() 168 case map[string]string: 169 return s 170 default: 171 panic(fmt.Sprintf("obj %T has unknown Selector", s)) 172 } 173 } 174 175 // equal checks if two objects are equal. This is done through a variety of different methods, depending on the input type. 176 func equal[O any](a, b O) bool { 177 ak, ok := any(a).(Equaler[O]) 178 if ok { 179 return ak.Equals(b) 180 } 181 pk, ok := any(&a).(Equaler[O]) 182 if ok { 183 return pk.Equals(b) 184 } 185 // Future improvement: add a default Kubernetes object implementation 186 // ResourceVersion is tempting but probably not safe. If we are comparing objects from the API server its fine, 187 // but often we will be operating on types generated by the controller itself. 188 // We should have a way to opt-in to RV comparison, but not default to it. 189 190 ap, ok := any(a).(proto.Message) 191 if ok { 192 if reflect.TypeOf(ap.ProtoReflect().Interface()) == reflect.TypeOf(ap) { 193 return proto.Equal(ap, any(b).(proto.Message)) 194 } 195 // If not, this is an embedded proto most likely... Sneaky. 196 // DeepEqual on proto is broken, so fail fast to avoid subtle errors. 197 panic(fmt.Sprintf("unable to compare object %T; perhaps it is embedding a protobuf? Provide an Equaler implementation", a)) 198 } 199 return reflect.DeepEqual(a, b) 200 } 201 202 type collectionUID uint64 203 204 var globalUIDCounter = atomic.NewUint64(1) 205 206 func nextUID() collectionUID { 207 return collectionUID(globalUIDCounter.Inc()) 208 }