k8s.io/client-go@v0.31.1/dynamic/simple.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 dynamic
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net/http"
    23  	"time"
    24  
    25  	"k8s.io/apimachinery/pkg/api/meta"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/runtime/schema"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	"k8s.io/apimachinery/pkg/watch"
    32  	"k8s.io/client-go/rest"
    33  	"k8s.io/client-go/util/consistencydetector"
    34  	"k8s.io/client-go/util/watchlist"
    35  	"k8s.io/klog/v2"
    36  )
    37  
    38  type DynamicClient struct {
    39  	client rest.Interface
    40  }
    41  
    42  var _ Interface = &DynamicClient{}
    43  
    44  // ConfigFor returns a copy of the provided config with the
    45  // appropriate dynamic client defaults set.
    46  func ConfigFor(inConfig *rest.Config) *rest.Config {
    47  	config := rest.CopyConfig(inConfig)
    48  	config.AcceptContentTypes = "application/json"
    49  	config.ContentType = "application/json"
    50  	config.NegotiatedSerializer = basicNegotiatedSerializer{} // this gets used for discovery and error handling types
    51  	if config.UserAgent == "" {
    52  		config.UserAgent = rest.DefaultKubernetesUserAgent()
    53  	}
    54  	return config
    55  }
    56  
    57  // New creates a new DynamicClient for the given RESTClient.
    58  func New(c rest.Interface) *DynamicClient {
    59  	return &DynamicClient{client: c}
    60  }
    61  
    62  // NewForConfigOrDie creates a new DynamicClient for the given config and
    63  // panics if there is an error in the config.
    64  func NewForConfigOrDie(c *rest.Config) *DynamicClient {
    65  	ret, err := NewForConfig(c)
    66  	if err != nil {
    67  		panic(err)
    68  	}
    69  	return ret
    70  }
    71  
    72  // NewForConfig creates a new dynamic client or returns an error.
    73  // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
    74  // where httpClient was generated with rest.HTTPClientFor(c).
    75  func NewForConfig(inConfig *rest.Config) (*DynamicClient, error) {
    76  	config := ConfigFor(inConfig)
    77  
    78  	httpClient, err := rest.HTTPClientFor(config)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	return NewForConfigAndClient(config, httpClient)
    83  }
    84  
    85  // NewForConfigAndClient creates a new dynamic client for the given config and http client.
    86  // Note the http client provided takes precedence over the configured transport values.
    87  func NewForConfigAndClient(inConfig *rest.Config, h *http.Client) (*DynamicClient, error) {
    88  	config := ConfigFor(inConfig)
    89  	// for serializing the options
    90  	config.GroupVersion = &schema.GroupVersion{}
    91  	config.APIPath = "/if-you-see-this-search-for-the-break"
    92  
    93  	restClient, err := rest.RESTClientForConfigAndClient(config, h)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	return &DynamicClient{client: restClient}, nil
    98  }
    99  
   100  type dynamicResourceClient struct {
   101  	client    *DynamicClient
   102  	namespace string
   103  	resource  schema.GroupVersionResource
   104  }
   105  
   106  func (c *DynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface {
   107  	return &dynamicResourceClient{client: c, resource: resource}
   108  }
   109  
   110  func (c *dynamicResourceClient) Namespace(ns string) ResourceInterface {
   111  	ret := *c
   112  	ret.namespace = ns
   113  	return &ret
   114  }
   115  
   116  func (c *dynamicResourceClient) Create(ctx context.Context, obj *unstructured.Unstructured, opts metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) {
   117  	outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  	name := ""
   122  	if len(subresources) > 0 {
   123  		accessor, err := meta.Accessor(obj)
   124  		if err != nil {
   125  			return nil, err
   126  		}
   127  		name = accessor.GetName()
   128  		if len(name) == 0 {
   129  			return nil, fmt.Errorf("name is required")
   130  		}
   131  	}
   132  	if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	result := c.client.client.
   137  		Post().
   138  		AbsPath(append(c.makeURLSegments(name), subresources...)...).
   139  		SetHeader("Content-Type", runtime.ContentTypeJSON).
   140  		Body(outBytes).
   141  		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
   142  		Do(ctx)
   143  	if err := result.Error(); err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	retBytes, err := result.Raw()
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  	uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	return uncastObj.(*unstructured.Unstructured), nil
   156  }
   157  
   158  func (c *dynamicResourceClient) Update(ctx context.Context, obj *unstructured.Unstructured, opts metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) {
   159  	accessor, err := meta.Accessor(obj)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  	name := accessor.GetName()
   164  	if len(name) == 0 {
   165  		return nil, fmt.Errorf("name is required")
   166  	}
   167  	if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
   168  		return nil, err
   169  	}
   170  	outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	result := c.client.client.
   176  		Put().
   177  		AbsPath(append(c.makeURLSegments(name), subresources...)...).
   178  		SetHeader("Content-Type", runtime.ContentTypeJSON).
   179  		Body(outBytes).
   180  		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
   181  		Do(ctx)
   182  	if err := result.Error(); err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	retBytes, err := result.Raw()
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  	uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  	return uncastObj.(*unstructured.Unstructured), nil
   195  }
   196  
   197  func (c *dynamicResourceClient) UpdateStatus(ctx context.Context, obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) {
   198  	accessor, err := meta.Accessor(obj)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  	name := accessor.GetName()
   203  	if len(name) == 0 {
   204  		return nil, fmt.Errorf("name is required")
   205  	}
   206  	if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
   207  		return nil, err
   208  	}
   209  	outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  
   214  	result := c.client.client.
   215  		Put().
   216  		AbsPath(append(c.makeURLSegments(name), "status")...).
   217  		SetHeader("Content-Type", runtime.ContentTypeJSON).
   218  		Body(outBytes).
   219  		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
   220  		Do(ctx)
   221  	if err := result.Error(); err != nil {
   222  		return nil, err
   223  	}
   224  
   225  	retBytes, err := result.Raw()
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  	uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  	return uncastObj.(*unstructured.Unstructured), nil
   234  }
   235  
   236  func (c *dynamicResourceClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error {
   237  	if len(name) == 0 {
   238  		return fmt.Errorf("name is required")
   239  	}
   240  	if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
   241  		return err
   242  	}
   243  	deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts)
   244  	if err != nil {
   245  		return err
   246  	}
   247  
   248  	result := c.client.client.
   249  		Delete().
   250  		AbsPath(append(c.makeURLSegments(name), subresources...)...).
   251  		SetHeader("Content-Type", runtime.ContentTypeJSON).
   252  		Body(deleteOptionsByte).
   253  		Do(ctx)
   254  	return result.Error()
   255  }
   256  
   257  func (c *dynamicResourceClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOptions metav1.ListOptions) error {
   258  	if err := validateNamespaceWithOptionalName(c.namespace); err != nil {
   259  		return err
   260  	}
   261  
   262  	deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts)
   263  	if err != nil {
   264  		return err
   265  	}
   266  
   267  	result := c.client.client.
   268  		Delete().
   269  		AbsPath(c.makeURLSegments("")...).
   270  		SetHeader("Content-Type", runtime.ContentTypeJSON).
   271  		Body(deleteOptionsByte).
   272  		SpecificallyVersionedParams(&listOptions, dynamicParameterCodec, versionV1).
   273  		Do(ctx)
   274  	return result.Error()
   275  }
   276  
   277  func (c *dynamicResourceClient) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) {
   278  	if len(name) == 0 {
   279  		return nil, fmt.Errorf("name is required")
   280  	}
   281  	if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
   282  		return nil, err
   283  	}
   284  	result := c.client.client.Get().AbsPath(append(c.makeURLSegments(name), subresources...)...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do(ctx)
   285  	if err := result.Error(); err != nil {
   286  		return nil, err
   287  	}
   288  	retBytes, err := result.Raw()
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  	uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  	return uncastObj.(*unstructured.Unstructured), nil
   297  }
   298  
   299  func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
   300  	if watchListOptions, hasWatchListOptionsPrepared, watchListOptionsErr := watchlist.PrepareWatchListOptionsFromListOptions(opts); watchListOptionsErr != nil {
   301  		klog.Warningf("Failed preparing watchlist options for %v, falling back to the standard LIST semantics, err = %v", c.resource, watchListOptionsErr)
   302  	} else if hasWatchListOptionsPrepared {
   303  		result, err := c.watchList(ctx, watchListOptions)
   304  		if err == nil {
   305  			consistencydetector.CheckWatchListFromCacheDataConsistencyIfRequested(ctx, fmt.Sprintf("watchlist request for %v", c.resource), c.list, opts, result)
   306  			return result, nil
   307  		}
   308  		klog.Warningf("The watchlist request for %v ended with an error, falling back to the standard LIST semantics, err = %v", c.resource, err)
   309  	}
   310  	result, err := c.list(ctx, opts)
   311  	if err == nil {
   312  		consistencydetector.CheckListFromCacheDataConsistencyIfRequested(ctx, fmt.Sprintf("list request for %v", c.resource), c.list, opts, result)
   313  	}
   314  	return result, err
   315  }
   316  
   317  func (c *dynamicResourceClient) list(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
   318  	if err := validateNamespaceWithOptionalName(c.namespace); err != nil {
   319  		return nil, err
   320  	}
   321  	result := c.client.client.Get().AbsPath(c.makeURLSegments("")...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do(ctx)
   322  	if err := result.Error(); err != nil {
   323  		return nil, err
   324  	}
   325  	retBytes, err := result.Raw()
   326  	if err != nil {
   327  		return nil, err
   328  	}
   329  	uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
   330  	if err != nil {
   331  		return nil, err
   332  	}
   333  	if list, ok := uncastObj.(*unstructured.UnstructuredList); ok {
   334  		return list, nil
   335  	}
   336  
   337  	list, err := uncastObj.(*unstructured.Unstructured).ToList()
   338  	if err != nil {
   339  		return nil, err
   340  	}
   341  	return list, nil
   342  }
   343  
   344  // watchList establishes a watch stream with the server and returns an unstructured list.
   345  func (c *dynamicResourceClient) watchList(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
   346  	if err := validateNamespaceWithOptionalName(c.namespace); err != nil {
   347  		return nil, err
   348  	}
   349  
   350  	var timeout time.Duration
   351  	if opts.TimeoutSeconds != nil {
   352  		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
   353  	}
   354  
   355  	result := &unstructured.UnstructuredList{}
   356  	err := c.client.client.Get().AbsPath(c.makeURLSegments("")...).
   357  		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
   358  		Timeout(timeout).
   359  		WatchList(ctx).
   360  		Into(result)
   361  
   362  	return result, err
   363  }
   364  
   365  func (c *dynamicResourceClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
   366  	opts.Watch = true
   367  	if err := validateNamespaceWithOptionalName(c.namespace); err != nil {
   368  		return nil, err
   369  	}
   370  	return c.client.client.Get().AbsPath(c.makeURLSegments("")...).
   371  		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
   372  		Watch(ctx)
   373  }
   374  
   375  func (c *dynamicResourceClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) {
   376  	if len(name) == 0 {
   377  		return nil, fmt.Errorf("name is required")
   378  	}
   379  	if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
   380  		return nil, err
   381  	}
   382  	result := c.client.client.
   383  		Patch(pt).
   384  		AbsPath(append(c.makeURLSegments(name), subresources...)...).
   385  		Body(data).
   386  		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
   387  		Do(ctx)
   388  	if err := result.Error(); err != nil {
   389  		return nil, err
   390  	}
   391  	retBytes, err := result.Raw()
   392  	if err != nil {
   393  		return nil, err
   394  	}
   395  	uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
   396  	if err != nil {
   397  		return nil, err
   398  	}
   399  	return uncastObj.(*unstructured.Unstructured), nil
   400  }
   401  
   402  func (c *dynamicResourceClient) Apply(ctx context.Context, name string, obj *unstructured.Unstructured, opts metav1.ApplyOptions, subresources ...string) (*unstructured.Unstructured, error) {
   403  	if len(name) == 0 {
   404  		return nil, fmt.Errorf("name is required")
   405  	}
   406  	if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
   407  		return nil, err
   408  	}
   409  	outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
   410  	if err != nil {
   411  		return nil, err
   412  	}
   413  	accessor, err := meta.Accessor(obj)
   414  	if err != nil {
   415  		return nil, err
   416  	}
   417  	managedFields := accessor.GetManagedFields()
   418  	if len(managedFields) > 0 {
   419  		return nil, fmt.Errorf(`cannot apply an object with managed fields already set.
   420  		Use the client-go/applyconfigurations "UnstructructuredExtractor" to obtain the unstructured ApplyConfiguration for the given field manager that you can use/modify here to apply`)
   421  	}
   422  	patchOpts := opts.ToPatchOptions()
   423  
   424  	result := c.client.client.
   425  		Patch(types.ApplyPatchType).
   426  		AbsPath(append(c.makeURLSegments(name), subresources...)...).
   427  		Body(outBytes).
   428  		SpecificallyVersionedParams(&patchOpts, dynamicParameterCodec, versionV1).
   429  		Do(ctx)
   430  	if err := result.Error(); err != nil {
   431  		return nil, err
   432  	}
   433  	retBytes, err := result.Raw()
   434  	if err != nil {
   435  		return nil, err
   436  	}
   437  	uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
   438  	if err != nil {
   439  		return nil, err
   440  	}
   441  	return uncastObj.(*unstructured.Unstructured), nil
   442  }
   443  func (c *dynamicResourceClient) ApplyStatus(ctx context.Context, name string, obj *unstructured.Unstructured, opts metav1.ApplyOptions) (*unstructured.Unstructured, error) {
   444  	return c.Apply(ctx, name, obj, opts, "status")
   445  }
   446  
   447  func validateNamespaceWithOptionalName(namespace string, name ...string) error {
   448  	if msgs := rest.IsValidPathSegmentName(namespace); len(msgs) != 0 {
   449  		return fmt.Errorf("invalid namespace %q: %v", namespace, msgs)
   450  	}
   451  	if len(name) > 1 {
   452  		panic("Invalid number of names")
   453  	} else if len(name) == 1 {
   454  		if msgs := rest.IsValidPathSegmentName(name[0]); len(msgs) != 0 {
   455  			return fmt.Errorf("invalid resource name %q: %v", name[0], msgs)
   456  		}
   457  	}
   458  	return nil
   459  }
   460  
   461  func (c *dynamicResourceClient) makeURLSegments(name string) []string {
   462  	url := []string{}
   463  	if len(c.resource.Group) == 0 {
   464  		url = append(url, "api")
   465  	} else {
   466  		url = append(url, "apis", c.resource.Group)
   467  	}
   468  	url = append(url, c.resource.Version)
   469  
   470  	if len(c.namespace) > 0 {
   471  		url = append(url, "namespaces", c.namespace)
   472  	}
   473  	url = append(url, c.resource.Resource)
   474  
   475  	if len(name) > 0 {
   476  		url = append(url, name)
   477  	}
   478  
   479  	return url
   480  }