...

Source file src/sigs.k8s.io/controller-runtime/pkg/client/apiutil/apimachinery.go

Documentation: sigs.k8s.io/controller-runtime/pkg/client/apiutil

     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  }
   226  

View as plain text