sigs.k8s.io/controller-runtime@v0.18.2/pkg/client/apiutil/apimachinery.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package apiutil contains utilities for working with raw Kubernetes 18 // API machinery, such as creating RESTMappers and raw REST clients, 19 // and extracting the GVK of an object. 20 package apiutil 21 22 import ( 23 "errors" 24 "fmt" 25 "net/http" 26 "reflect" 27 "sync" 28 29 "k8s.io/apimachinery/pkg/api/meta" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/runtime/schema" 33 "k8s.io/apimachinery/pkg/runtime/serializer" 34 "k8s.io/client-go/dynamic" 35 clientgoscheme "k8s.io/client-go/kubernetes/scheme" 36 "k8s.io/client-go/rest" 37 ) 38 39 var ( 40 protobufScheme = runtime.NewScheme() 41 protobufSchemeLock sync.RWMutex 42 ) 43 44 func init() { 45 // Currently only enabled for built-in resources which are guaranteed to implement Protocol Buffers. 46 // For custom resources, CRDs can not support Protocol Buffers but Aggregated API can. 47 // See doc: https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#advanced-features-and-flexibility 48 if err := clientgoscheme.AddToScheme(protobufScheme); err != nil { 49 panic(err) 50 } 51 } 52 53 // AddToProtobufScheme add the given SchemeBuilder into protobufScheme, which should 54 // be additional types that do support protobuf. 55 func AddToProtobufScheme(addToScheme func(*runtime.Scheme) error) error { 56 protobufSchemeLock.Lock() 57 defer protobufSchemeLock.Unlock() 58 return addToScheme(protobufScheme) 59 } 60 61 // IsObjectNamespaced returns true if the object is namespace scoped. 62 // For unstructured objects the gvk is found from the object itself. 63 func IsObjectNamespaced(obj runtime.Object, scheme *runtime.Scheme, restmapper meta.RESTMapper) (bool, error) { 64 gvk, err := GVKForObject(obj, scheme) 65 if err != nil { 66 return false, err 67 } 68 69 return IsGVKNamespaced(gvk, restmapper) 70 } 71 72 // IsGVKNamespaced returns true if the object having the provided 73 // GVK is namespace scoped. 74 func IsGVKNamespaced(gvk schema.GroupVersionKind, restmapper meta.RESTMapper) (bool, error) { 75 restmapping, err := restmapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}) 76 if err != nil { 77 return false, fmt.Errorf("failed to get restmapping: %w", err) 78 } 79 80 scope := restmapping.Scope.Name() 81 if scope == "" { 82 return false, errors.New("scope cannot be identified, empty scope returned") 83 } 84 85 if scope != meta.RESTScopeNameRoot { 86 return true, nil 87 } 88 return false, nil 89 } 90 91 // GVKForObject finds the GroupVersionKind associated with the given object, if there is only a single such GVK. 92 func GVKForObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionKind, error) { 93 // TODO(directxman12): do we want to generalize this to arbitrary container types? 94 // I think we'd need a generalized form of scheme or something. It's a 95 // shame there's not a reliable "GetGVK" interface that works by default 96 // for unpopulated static types and populated "dynamic" types 97 // (unstructured, partial, etc) 98 99 // check for PartialObjectMetadata, which is analogous to unstructured, but isn't handled by ObjectKinds 100 _, isPartial := obj.(*metav1.PartialObjectMetadata) 101 _, isPartialList := obj.(*metav1.PartialObjectMetadataList) 102 if isPartial || isPartialList { 103 // we require that the GVK be populated in order to recognize the object 104 gvk := obj.GetObjectKind().GroupVersionKind() 105 if len(gvk.Kind) == 0 { 106 return schema.GroupVersionKind{}, runtime.NewMissingKindErr("unstructured object has no kind") 107 } 108 if len(gvk.Version) == 0 { 109 return schema.GroupVersionKind{}, runtime.NewMissingVersionErr("unstructured object has no version") 110 } 111 return gvk, nil 112 } 113 114 // Use the given scheme to retrieve all the GVKs for the object. 115 gvks, isUnversioned, err := scheme.ObjectKinds(obj) 116 if err != nil { 117 return schema.GroupVersionKind{}, err 118 } 119 if isUnversioned { 120 return schema.GroupVersionKind{}, fmt.Errorf("cannot create group-version-kind for unversioned type %T", obj) 121 } 122 123 switch { 124 case len(gvks) < 1: 125 // If the object has no GVK, the object might not have been registered with the scheme. 126 // or it's not a valid object. 127 return schema.GroupVersionKind{}, fmt.Errorf("no GroupVersionKind associated with Go type %T, was the type registered with the Scheme?", obj) 128 case len(gvks) > 1: 129 err := fmt.Errorf("multiple GroupVersionKinds associated with Go type %T within the Scheme, this can happen when a type is registered for multiple GVKs at the same time", obj) 130 131 // We've found multiple GVKs for the object. 132 currentGVK := obj.GetObjectKind().GroupVersionKind() 133 if !currentGVK.Empty() { 134 // If the base object has a GVK, check if it's in the list of GVKs before using it. 135 for _, gvk := range gvks { 136 if gvk == currentGVK { 137 return gvk, nil 138 } 139 } 140 141 return schema.GroupVersionKind{}, fmt.Errorf( 142 "%w: the object's supplied GroupVersionKind %q was not found in the Scheme's list; refusing to guess at one: %q", err, currentGVK, gvks) 143 } 144 145 // This should only trigger for things like metav1.XYZ -- 146 // normal versioned types should be fine. 147 // 148 // See https://github.com/kubernetes-sigs/controller-runtime/issues/362 149 // for more information. 150 return schema.GroupVersionKind{}, fmt.Errorf( 151 "%w: callers can either fix their type registration to only register it once, or specify the GroupVersionKind to use for object passed in; refusing to guess at one: %q", err, gvks) 152 default: 153 // In any other case, we've found a single GVK for the object. 154 return gvks[0], nil 155 } 156 } 157 158 // RESTClientForGVK constructs a new rest.Interface capable of accessing the resource associated 159 // with the given GroupVersionKind. The REST client will be configured to use the negotiated serializer from 160 // baseConfig, if set, otherwise a default serializer will be set. 161 func RESTClientForGVK(gvk schema.GroupVersionKind, isUnstructured bool, baseConfig *rest.Config, codecs serializer.CodecFactory, httpClient *http.Client) (rest.Interface, error) { 162 if httpClient == nil { 163 return nil, fmt.Errorf("httpClient must not be nil, consider using rest.HTTPClientFor(c) to create a client") 164 } 165 return rest.RESTClientForConfigAndClient(createRestConfig(gvk, isUnstructured, baseConfig, codecs), httpClient) 166 } 167 168 // createRestConfig copies the base config and updates needed fields for a new rest config. 169 func createRestConfig(gvk schema.GroupVersionKind, isUnstructured bool, baseConfig *rest.Config, codecs serializer.CodecFactory) *rest.Config { 170 gv := gvk.GroupVersion() 171 172 cfg := rest.CopyConfig(baseConfig) 173 cfg.GroupVersion = &gv 174 if gvk.Group == "" { 175 cfg.APIPath = "/api" 176 } else { 177 cfg.APIPath = "/apis" 178 } 179 if cfg.UserAgent == "" { 180 cfg.UserAgent = rest.DefaultKubernetesUserAgent() 181 } 182 // TODO(FillZpp): In the long run, we want to check discovery or something to make sure that this is actually true. 183 if cfg.ContentType == "" && !isUnstructured { 184 protobufSchemeLock.RLock() 185 if protobufScheme.Recognizes(gvk) { 186 cfg.ContentType = runtime.ContentTypeProtobuf 187 } 188 protobufSchemeLock.RUnlock() 189 } 190 191 if isUnstructured { 192 // If the object is unstructured, we use the client-go dynamic serializer. 193 cfg = dynamic.ConfigFor(cfg) 194 } else { 195 cfg.NegotiatedSerializer = serializerWithTargetZeroingDecode{NegotiatedSerializer: serializer.WithoutConversionCodecFactory{CodecFactory: codecs}} 196 } 197 198 return cfg 199 } 200 201 type serializerWithTargetZeroingDecode struct { 202 runtime.NegotiatedSerializer 203 } 204 205 func (s serializerWithTargetZeroingDecode) DecoderToVersion(serializer runtime.Decoder, r runtime.GroupVersioner) runtime.Decoder { 206 return targetZeroingDecoder{upstream: s.NegotiatedSerializer.DecoderToVersion(serializer, r)} 207 } 208 209 type targetZeroingDecoder struct { 210 upstream runtime.Decoder 211 } 212 213 func (t targetZeroingDecoder) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { 214 zero(into) 215 return t.upstream.Decode(data, defaults, into) 216 } 217 218 // zero zeros the value of a pointer. 219 func zero(x interface{}) { 220 if x == nil { 221 return 222 } 223 res := reflect.ValueOf(x).Elem() 224 res.Set(reflect.Zero(res.Type())) 225 }