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