...

Source file src/k8s.io/client-go/dynamic/simple.go

Documentation: k8s.io/client-go/dynamic

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

View as plain text