...

Source file src/k8s.io/client-go/metadata/metadata.go

Documentation: k8s.io/client-go/metadata

     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 metadata
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"net/http"
    24  	"time"
    25  
    26  	"k8s.io/klog/v2"
    27  
    28  	metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/apimachinery/pkg/runtime/schema"
    32  	"k8s.io/apimachinery/pkg/runtime/serializer"
    33  	"k8s.io/apimachinery/pkg/types"
    34  	"k8s.io/apimachinery/pkg/watch"
    35  	"k8s.io/client-go/rest"
    36  )
    37  
    38  var deleteScheme = runtime.NewScheme()
    39  var parameterScheme = runtime.NewScheme()
    40  var deleteOptionsCodec = serializer.NewCodecFactory(deleteScheme)
    41  var dynamicParameterCodec = runtime.NewParameterCodec(parameterScheme)
    42  
    43  var versionV1 = schema.GroupVersion{Version: "v1"}
    44  
    45  func init() {
    46  	metav1.AddToGroupVersion(parameterScheme, versionV1)
    47  	metav1.AddToGroupVersion(deleteScheme, versionV1)
    48  }
    49  
    50  // Client allows callers to retrieve the object metadata for any
    51  // Kubernetes-compatible API endpoint. The client uses the
    52  // meta.k8s.io/v1 PartialObjectMetadata resource to more efficiently
    53  // retrieve just the necessary metadata, but on older servers
    54  // (Kubernetes 1.14 and before) will retrieve the object and then
    55  // convert the metadata.
    56  type Client struct {
    57  	client *rest.RESTClient
    58  }
    59  
    60  var _ Interface = &Client{}
    61  
    62  // ConfigFor returns a copy of the provided config with the
    63  // appropriate metadata client defaults set.
    64  func ConfigFor(inConfig *rest.Config) *rest.Config {
    65  	config := rest.CopyConfig(inConfig)
    66  	config.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json"
    67  	config.ContentType = "application/vnd.kubernetes.protobuf"
    68  	config.NegotiatedSerializer = metainternalversionscheme.Codecs.WithoutConversion()
    69  	if config.UserAgent == "" {
    70  		config.UserAgent = rest.DefaultKubernetesUserAgent()
    71  	}
    72  	return config
    73  }
    74  
    75  // NewForConfigOrDie creates a new metadata client for the given config and
    76  // panics if there is an error in the config.
    77  func NewForConfigOrDie(c *rest.Config) Interface {
    78  	ret, err := NewForConfig(c)
    79  	if err != nil {
    80  		panic(err)
    81  	}
    82  	return ret
    83  }
    84  
    85  // NewForConfig creates a new metadata client that can retrieve object
    86  // metadata details about any Kubernetes object (core, aggregated, or custom
    87  // resource based) in the form of PartialObjectMetadata objects, or returns
    88  // an error.
    89  // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
    90  // where httpClient was generated with rest.HTTPClientFor(c).
    91  func NewForConfig(inConfig *rest.Config) (Interface, error) {
    92  	config := ConfigFor(inConfig)
    93  
    94  	httpClient, err := rest.HTTPClientFor(config)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  	return NewForConfigAndClient(config, httpClient)
    99  }
   100  
   101  // NewForConfigAndClient creates a new metadata client for the given config and http client.
   102  // Note the http client provided takes precedence over the configured transport values.
   103  func NewForConfigAndClient(inConfig *rest.Config, h *http.Client) (Interface, error) {
   104  	config := ConfigFor(inConfig)
   105  	// for serializing the options
   106  	config.GroupVersion = &schema.GroupVersion{}
   107  	config.APIPath = "/this-value-should-never-be-sent"
   108  
   109  	restClient, err := rest.RESTClientForConfigAndClient(config, h)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  
   114  	return &Client{client: restClient}, nil
   115  }
   116  
   117  type client struct {
   118  	client    *Client
   119  	namespace string
   120  	resource  schema.GroupVersionResource
   121  }
   122  
   123  // Resource returns an interface that can access cluster or namespace
   124  // scoped instances of resource.
   125  func (c *Client) Resource(resource schema.GroupVersionResource) Getter {
   126  	return &client{client: c, resource: resource}
   127  }
   128  
   129  // Namespace returns an interface that can access namespace-scoped instances of the
   130  // provided resource.
   131  func (c *client) Namespace(ns string) ResourceInterface {
   132  	ret := *c
   133  	ret.namespace = ns
   134  	return &ret
   135  }
   136  
   137  // Delete removes the provided resource from the server.
   138  func (c *client) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error {
   139  	if len(name) == 0 {
   140  		return fmt.Errorf("name is required")
   141  	}
   142  	// if DeleteOptions are delivered to Negotiator for serialization,
   143  	// HTTP-Request header will bring "Content-Type: application/vnd.kubernetes.protobuf"
   144  	// apiextensions-apiserver uses unstructuredNegotiatedSerializer to decode the input,
   145  	// server-side will reply with 406 errors.
   146  	// The special treatment here is to be compatible with CRD Handler
   147  	// see: https://github.com/kubernetes/kubernetes/blob/1a845ccd076bbf1b03420fe694c85a5cd3bd6bed/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go#L843
   148  	deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts)
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	result := c.client.client.
   154  		Delete().
   155  		AbsPath(append(c.makeURLSegments(name), subresources...)...).
   156  		SetHeader("Content-Type", runtime.ContentTypeJSON).
   157  		Body(deleteOptionsByte).
   158  		Do(ctx)
   159  	return result.Error()
   160  }
   161  
   162  // DeleteCollection triggers deletion of all resources in the specified scope (namespace or cluster).
   163  func (c *client) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOptions metav1.ListOptions) error {
   164  	// See comment on Delete
   165  	deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts)
   166  	if err != nil {
   167  		return err
   168  	}
   169  
   170  	result := c.client.client.
   171  		Delete().
   172  		AbsPath(c.makeURLSegments("")...).
   173  		SetHeader("Content-Type", runtime.ContentTypeJSON).
   174  		Body(deleteOptionsByte).
   175  		SpecificallyVersionedParams(&listOptions, dynamicParameterCodec, versionV1).
   176  		Do(ctx)
   177  	return result.Error()
   178  }
   179  
   180  // Get returns the resource with name from the specified scope (namespace or cluster).
   181  func (c *client) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
   182  	if len(name) == 0 {
   183  		return nil, fmt.Errorf("name is required")
   184  	}
   185  	result := c.client.client.Get().AbsPath(append(c.makeURLSegments(name), subresources...)...).
   186  		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
   187  		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
   188  		Do(ctx)
   189  	if err := result.Error(); err != nil {
   190  		return nil, err
   191  	}
   192  	obj, err := result.Get()
   193  	if runtime.IsNotRegisteredError(err) {
   194  		klog.FromContext(ctx).V(5).Info("Could not retrieve PartialObjectMetadata", "err", err)
   195  		rawBytes, err := result.Raw()
   196  		if err != nil {
   197  			return nil, err
   198  		}
   199  		var partial metav1.PartialObjectMetadata
   200  		if err := json.Unmarshal(rawBytes, &partial); err != nil {
   201  			return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadata: %v", err)
   202  		}
   203  		if !isLikelyObjectMetadata(&partial) {
   204  			return nil, fmt.Errorf("object does not appear to match the ObjectMeta schema: %#v", partial)
   205  		}
   206  		partial.TypeMeta = metav1.TypeMeta{}
   207  		return &partial, nil
   208  	}
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  	partial, ok := obj.(*metav1.PartialObjectMetadata)
   213  	if !ok {
   214  		return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
   215  	}
   216  	return partial, nil
   217  }
   218  
   219  // List returns all resources within the specified scope (namespace or cluster).
   220  func (c *client) List(ctx context.Context, opts metav1.ListOptions) (*metav1.PartialObjectMetadataList, error) {
   221  	result := c.client.client.Get().AbsPath(c.makeURLSegments("")...).
   222  		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json").
   223  		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
   224  		Do(ctx)
   225  	if err := result.Error(); err != nil {
   226  		return nil, err
   227  	}
   228  	obj, err := result.Get()
   229  	if runtime.IsNotRegisteredError(err) {
   230  		klog.FromContext(ctx).V(5).Info("Could not retrieve PartialObjectMetadataList", "err", err)
   231  		rawBytes, err := result.Raw()
   232  		if err != nil {
   233  			return nil, err
   234  		}
   235  		var partial metav1.PartialObjectMetadataList
   236  		if err := json.Unmarshal(rawBytes, &partial); err != nil {
   237  			return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadataList: %v", err)
   238  		}
   239  		partial.TypeMeta = metav1.TypeMeta{}
   240  		return &partial, nil
   241  	}
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  	partial, ok := obj.(*metav1.PartialObjectMetadataList)
   246  	if !ok {
   247  		return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
   248  	}
   249  	return partial, nil
   250  }
   251  
   252  // Watch finds all changes to the resources in the specified scope (namespace or cluster).
   253  func (c *client) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
   254  	var timeout time.Duration
   255  	if opts.TimeoutSeconds != nil {
   256  		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
   257  	}
   258  	opts.Watch = true
   259  	return c.client.client.Get().
   260  		AbsPath(c.makeURLSegments("")...).
   261  		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
   262  		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
   263  		Timeout(timeout).
   264  		Watch(ctx)
   265  }
   266  
   267  // Patch modifies the named resource in the specified scope (namespace or cluster).
   268  func (c *client) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
   269  	if len(name) == 0 {
   270  		return nil, fmt.Errorf("name is required")
   271  	}
   272  	result := c.client.client.
   273  		Patch(pt).
   274  		AbsPath(append(c.makeURLSegments(name), subresources...)...).
   275  		Body(data).
   276  		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
   277  		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
   278  		Do(ctx)
   279  	if err := result.Error(); err != nil {
   280  		return nil, err
   281  	}
   282  	obj, err := result.Get()
   283  	if runtime.IsNotRegisteredError(err) {
   284  		rawBytes, err := result.Raw()
   285  		if err != nil {
   286  			return nil, err
   287  		}
   288  		var partial metav1.PartialObjectMetadata
   289  		if err := json.Unmarshal(rawBytes, &partial); err != nil {
   290  			return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadata: %v", err)
   291  		}
   292  		if !isLikelyObjectMetadata(&partial) {
   293  			return nil, fmt.Errorf("object does not appear to match the ObjectMeta schema")
   294  		}
   295  		partial.TypeMeta = metav1.TypeMeta{}
   296  		return &partial, nil
   297  	}
   298  	if err != nil {
   299  		return nil, err
   300  	}
   301  	partial, ok := obj.(*metav1.PartialObjectMetadata)
   302  	if !ok {
   303  		return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
   304  	}
   305  	return partial, nil
   306  }
   307  
   308  func (c *client) makeURLSegments(name string) []string {
   309  	url := []string{}
   310  	if len(c.resource.Group) == 0 {
   311  		url = append(url, "api")
   312  	} else {
   313  		url = append(url, "apis", c.resource.Group)
   314  	}
   315  	url = append(url, c.resource.Version)
   316  
   317  	if len(c.namespace) > 0 {
   318  		url = append(url, "namespaces", c.namespace)
   319  	}
   320  	url = append(url, c.resource.Resource)
   321  
   322  	if len(name) > 0 {
   323  		url = append(url, name)
   324  	}
   325  
   326  	return url
   327  }
   328  
   329  func isLikelyObjectMetadata(meta *metav1.PartialObjectMetadata) bool {
   330  	return len(meta.UID) > 0 || !meta.CreationTimestamp.IsZero() || len(meta.Name) > 0 || len(meta.GenerateName) > 0
   331  }
   332  

View as plain text