...

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

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

     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 client
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"net/http"
    24  	"strings"
    25  
    26  	"k8s.io/apimachinery/pkg/api/meta"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/runtime/schema"
    30  	"k8s.io/apimachinery/pkg/runtime/serializer"
    31  	"k8s.io/client-go/kubernetes/scheme"
    32  	"k8s.io/client-go/metadata"
    33  	"k8s.io/client-go/rest"
    34  
    35  	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
    36  	"sigs.k8s.io/controller-runtime/pkg/log"
    37  )
    38  
    39  // Options are creation options for a Client.
    40  type Options struct {
    41  	// HTTPClient is the HTTP client to use for requests.
    42  	HTTPClient *http.Client
    43  
    44  	// Scheme, if provided, will be used to map go structs to GroupVersionKinds
    45  	Scheme *runtime.Scheme
    46  
    47  	// Mapper, if provided, will be used to map GroupVersionKinds to Resources
    48  	Mapper meta.RESTMapper
    49  
    50  	// Cache, if provided, is used to read objects from the cache.
    51  	Cache *CacheOptions
    52  
    53  	// WarningHandler is used to configure the warning handler responsible for
    54  	// surfacing and handling warnings messages sent by the API server.
    55  	WarningHandler WarningHandlerOptions
    56  
    57  	// DryRun instructs the client to only perform dry run requests.
    58  	DryRun *bool
    59  }
    60  
    61  // WarningHandlerOptions are options for configuring a
    62  // warning handler for the client which is responsible
    63  // for surfacing API Server warnings.
    64  type WarningHandlerOptions struct {
    65  	// SuppressWarnings decides if the warnings from the
    66  	// API server are suppressed or surfaced in the client.
    67  	SuppressWarnings bool
    68  	// AllowDuplicateLogs does not deduplicate the to-be
    69  	// logged surfaced warnings messages. See
    70  	// log.WarningHandlerOptions for considerations
    71  	// regarding deduplication
    72  	AllowDuplicateLogs bool
    73  }
    74  
    75  // CacheOptions are options for creating a cache-backed client.
    76  type CacheOptions struct {
    77  	// Reader is a cache-backed reader that will be used to read objects from the cache.
    78  	// +required
    79  	Reader Reader
    80  	// DisableFor is a list of objects that should never be read from the cache.
    81  	// Objects configured here always result in a live lookup.
    82  	DisableFor []Object
    83  	// Unstructured is a flag that indicates whether the cache-backed client should
    84  	// read unstructured objects or lists from the cache.
    85  	// If false, unstructured objects will always result in a live lookup.
    86  	Unstructured bool
    87  }
    88  
    89  // NewClientFunc allows a user to define how to create a client.
    90  type NewClientFunc func(config *rest.Config, options Options) (Client, error)
    91  
    92  // New returns a new Client using the provided config and Options.
    93  //
    94  // The client's read behavior is determined by Options.Cache.
    95  // If either Options.Cache or Options.Cache.Reader is nil,
    96  // the client reads directly from the API server.
    97  // If both Options.Cache and Options.Cache.Reader are non-nil,
    98  // the client reads from a local cache. However, specific
    99  // resources can still be configured to bypass the cache based
   100  // on Options.Cache.Unstructured and Options.Cache.DisableFor.
   101  // Write operations are always performed directly on the API server.
   102  //
   103  // The client understands how to work with normal types (both custom resources
   104  // and aggregated/built-in resources), as well as unstructured types.
   105  // In the case of normal types, the scheme will be used to look up the
   106  // corresponding group, version, and kind for the given type.  In the
   107  // case of unstructured types, the group, version, and kind will be extracted
   108  // from the corresponding fields on the object.
   109  func New(config *rest.Config, options Options) (c Client, err error) {
   110  	c, err = newClient(config, options)
   111  	if err == nil && options.DryRun != nil && *options.DryRun {
   112  		c = NewDryRunClient(c)
   113  	}
   114  	return c, err
   115  }
   116  
   117  func newClient(config *rest.Config, options Options) (*client, error) {
   118  	if config == nil {
   119  		return nil, fmt.Errorf("must provide non-nil rest.Config to client.New")
   120  	}
   121  
   122  	config = rest.CopyConfig(config)
   123  	if config.UserAgent == "" {
   124  		config.UserAgent = rest.DefaultKubernetesUserAgent()
   125  	}
   126  
   127  	if !options.WarningHandler.SuppressWarnings {
   128  		// surface warnings
   129  		logger := log.Log.WithName("KubeAPIWarningLogger")
   130  		// Set a WarningHandler, the default WarningHandler
   131  		// is log.KubeAPIWarningLogger with deduplication enabled.
   132  		// See log.KubeAPIWarningLoggerOptions for considerations
   133  		// regarding deduplication.
   134  		config.WarningHandler = log.NewKubeAPIWarningLogger(
   135  			logger,
   136  			log.KubeAPIWarningLoggerOptions{
   137  				Deduplicate: !options.WarningHandler.AllowDuplicateLogs,
   138  			},
   139  		)
   140  	}
   141  
   142  	// Use the rest HTTP client for the provided config if unset
   143  	if options.HTTPClient == nil {
   144  		var err error
   145  		options.HTTPClient, err = rest.HTTPClientFor(config)
   146  		if err != nil {
   147  			return nil, err
   148  		}
   149  	}
   150  
   151  	// Init a scheme if none provided
   152  	if options.Scheme == nil {
   153  		options.Scheme = scheme.Scheme
   154  	}
   155  
   156  	// Init a Mapper if none provided
   157  	if options.Mapper == nil {
   158  		var err error
   159  		options.Mapper, err = apiutil.NewDynamicRESTMapper(config, options.HTTPClient)
   160  		if err != nil {
   161  			return nil, err
   162  		}
   163  	}
   164  
   165  	resources := &clientRestResources{
   166  		httpClient: options.HTTPClient,
   167  		config:     config,
   168  		scheme:     options.Scheme,
   169  		mapper:     options.Mapper,
   170  		codecs:     serializer.NewCodecFactory(options.Scheme),
   171  
   172  		structuredResourceByType:   make(map[schema.GroupVersionKind]*resourceMeta),
   173  		unstructuredResourceByType: make(map[schema.GroupVersionKind]*resourceMeta),
   174  	}
   175  
   176  	rawMetaClient, err := metadata.NewForConfigAndClient(metadata.ConfigFor(config), options.HTTPClient)
   177  	if err != nil {
   178  		return nil, fmt.Errorf("unable to construct metadata-only client for use as part of client: %w", err)
   179  	}
   180  
   181  	c := &client{
   182  		typedClient: typedClient{
   183  			resources:  resources,
   184  			paramCodec: runtime.NewParameterCodec(options.Scheme),
   185  		},
   186  		unstructuredClient: unstructuredClient{
   187  			resources:  resources,
   188  			paramCodec: noConversionParamCodec{},
   189  		},
   190  		metadataClient: metadataClient{
   191  			client:     rawMetaClient,
   192  			restMapper: options.Mapper,
   193  		},
   194  		scheme: options.Scheme,
   195  		mapper: options.Mapper,
   196  	}
   197  	if options.Cache == nil || options.Cache.Reader == nil {
   198  		return c, nil
   199  	}
   200  
   201  	// We want a cache if we're here.
   202  	// Set the cache.
   203  	c.cache = options.Cache.Reader
   204  
   205  	// Load uncached GVKs.
   206  	c.cacheUnstructured = options.Cache.Unstructured
   207  	c.uncachedGVKs = map[schema.GroupVersionKind]struct{}{}
   208  	for _, obj := range options.Cache.DisableFor {
   209  		gvk, err := c.GroupVersionKindFor(obj)
   210  		if err != nil {
   211  			return nil, err
   212  		}
   213  		c.uncachedGVKs[gvk] = struct{}{}
   214  	}
   215  	return c, nil
   216  }
   217  
   218  var _ Client = &client{}
   219  
   220  // client is a client.Client configured to either read from a local cache or directly from the API server.
   221  // Write operations are always performed directly on the API server.
   222  // It lazily initializes new clients at the time they are used.
   223  type client struct {
   224  	typedClient        typedClient
   225  	unstructuredClient unstructuredClient
   226  	metadataClient     metadataClient
   227  	scheme             *runtime.Scheme
   228  	mapper             meta.RESTMapper
   229  
   230  	cache             Reader
   231  	uncachedGVKs      map[schema.GroupVersionKind]struct{}
   232  	cacheUnstructured bool
   233  }
   234  
   235  func (c *client) shouldBypassCache(obj runtime.Object) (bool, error) {
   236  	if c.cache == nil {
   237  		return true, nil
   238  	}
   239  
   240  	gvk, err := c.GroupVersionKindFor(obj)
   241  	if err != nil {
   242  		return false, err
   243  	}
   244  	// TODO: this is producing unsafe guesses that don't actually work,
   245  	// but it matches ~99% of the cases out there.
   246  	if meta.IsListType(obj) {
   247  		gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
   248  	}
   249  	if _, isUncached := c.uncachedGVKs[gvk]; isUncached {
   250  		return true, nil
   251  	}
   252  	if !c.cacheUnstructured {
   253  		_, isUnstructured := obj.(runtime.Unstructured)
   254  		return isUnstructured, nil
   255  	}
   256  	return false, nil
   257  }
   258  
   259  // resetGroupVersionKind is a helper function to restore and preserve GroupVersionKind on an object.
   260  func (c *client) resetGroupVersionKind(obj runtime.Object, gvk schema.GroupVersionKind) {
   261  	if gvk != schema.EmptyObjectKind.GroupVersionKind() {
   262  		if v, ok := obj.(schema.ObjectKind); ok {
   263  			v.SetGroupVersionKind(gvk)
   264  		}
   265  	}
   266  }
   267  
   268  // GroupVersionKindFor returns the GroupVersionKind for the given object.
   269  func (c *client) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) {
   270  	return apiutil.GVKForObject(obj, c.scheme)
   271  }
   272  
   273  // IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
   274  func (c *client) IsObjectNamespaced(obj runtime.Object) (bool, error) {
   275  	return apiutil.IsObjectNamespaced(obj, c.scheme, c.mapper)
   276  }
   277  
   278  // Scheme returns the scheme this client is using.
   279  func (c *client) Scheme() *runtime.Scheme {
   280  	return c.scheme
   281  }
   282  
   283  // RESTMapper returns the scheme this client is using.
   284  func (c *client) RESTMapper() meta.RESTMapper {
   285  	return c.mapper
   286  }
   287  
   288  // Create implements client.Client.
   289  func (c *client) Create(ctx context.Context, obj Object, opts ...CreateOption) error {
   290  	switch obj.(type) {
   291  	case runtime.Unstructured:
   292  		return c.unstructuredClient.Create(ctx, obj, opts...)
   293  	case *metav1.PartialObjectMetadata:
   294  		return fmt.Errorf("cannot create using only metadata")
   295  	default:
   296  		return c.typedClient.Create(ctx, obj, opts...)
   297  	}
   298  }
   299  
   300  // Update implements client.Client.
   301  func (c *client) Update(ctx context.Context, obj Object, opts ...UpdateOption) error {
   302  	defer c.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
   303  	switch obj.(type) {
   304  	case runtime.Unstructured:
   305  		return c.unstructuredClient.Update(ctx, obj, opts...)
   306  	case *metav1.PartialObjectMetadata:
   307  		return fmt.Errorf("cannot update using only metadata -- did you mean to patch?")
   308  	default:
   309  		return c.typedClient.Update(ctx, obj, opts...)
   310  	}
   311  }
   312  
   313  // Delete implements client.Client.
   314  func (c *client) Delete(ctx context.Context, obj Object, opts ...DeleteOption) error {
   315  	switch obj.(type) {
   316  	case runtime.Unstructured:
   317  		return c.unstructuredClient.Delete(ctx, obj, opts...)
   318  	case *metav1.PartialObjectMetadata:
   319  		return c.metadataClient.Delete(ctx, obj, opts...)
   320  	default:
   321  		return c.typedClient.Delete(ctx, obj, opts...)
   322  	}
   323  }
   324  
   325  // DeleteAllOf implements client.Client.
   326  func (c *client) DeleteAllOf(ctx context.Context, obj Object, opts ...DeleteAllOfOption) error {
   327  	switch obj.(type) {
   328  	case runtime.Unstructured:
   329  		return c.unstructuredClient.DeleteAllOf(ctx, obj, opts...)
   330  	case *metav1.PartialObjectMetadata:
   331  		return c.metadataClient.DeleteAllOf(ctx, obj, opts...)
   332  	default:
   333  		return c.typedClient.DeleteAllOf(ctx, obj, opts...)
   334  	}
   335  }
   336  
   337  // Patch implements client.Client.
   338  func (c *client) Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error {
   339  	defer c.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
   340  	switch obj.(type) {
   341  	case runtime.Unstructured:
   342  		return c.unstructuredClient.Patch(ctx, obj, patch, opts...)
   343  	case *metav1.PartialObjectMetadata:
   344  		return c.metadataClient.Patch(ctx, obj, patch, opts...)
   345  	default:
   346  		return c.typedClient.Patch(ctx, obj, patch, opts...)
   347  	}
   348  }
   349  
   350  // Get implements client.Client.
   351  func (c *client) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error {
   352  	if isUncached, err := c.shouldBypassCache(obj); err != nil {
   353  		return err
   354  	} else if !isUncached {
   355  		// Attempt to get from the cache.
   356  		return c.cache.Get(ctx, key, obj, opts...)
   357  	}
   358  
   359  	// Perform a live lookup.
   360  	switch obj.(type) {
   361  	case runtime.Unstructured:
   362  		return c.unstructuredClient.Get(ctx, key, obj, opts...)
   363  	case *metav1.PartialObjectMetadata:
   364  		// Metadata only object should always preserve the GVK coming in from the caller.
   365  		defer c.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
   366  		return c.metadataClient.Get(ctx, key, obj, opts...)
   367  	default:
   368  		return c.typedClient.Get(ctx, key, obj, opts...)
   369  	}
   370  }
   371  
   372  // List implements client.Client.
   373  func (c *client) List(ctx context.Context, obj ObjectList, opts ...ListOption) error {
   374  	if isUncached, err := c.shouldBypassCache(obj); err != nil {
   375  		return err
   376  	} else if !isUncached {
   377  		// Attempt to get from the cache.
   378  		return c.cache.List(ctx, obj, opts...)
   379  	}
   380  
   381  	// Perform a live lookup.
   382  	switch x := obj.(type) {
   383  	case runtime.Unstructured:
   384  		return c.unstructuredClient.List(ctx, obj, opts...)
   385  	case *metav1.PartialObjectMetadataList:
   386  		// Metadata only object should always preserve the GVK.
   387  		gvk := obj.GetObjectKind().GroupVersionKind()
   388  		defer c.resetGroupVersionKind(obj, gvk)
   389  
   390  		// Call the list client.
   391  		if err := c.metadataClient.List(ctx, obj, opts...); err != nil {
   392  			return err
   393  		}
   394  
   395  		// Restore the GVK for each item in the list.
   396  		itemGVK := schema.GroupVersionKind{
   397  			Group:   gvk.Group,
   398  			Version: gvk.Version,
   399  			// TODO: this is producing unsafe guesses that don't actually work,
   400  			// but it matches ~99% of the cases out there.
   401  			Kind: strings.TrimSuffix(gvk.Kind, "List"),
   402  		}
   403  		for i := range x.Items {
   404  			item := &x.Items[i]
   405  			item.SetGroupVersionKind(itemGVK)
   406  		}
   407  
   408  		return nil
   409  	default:
   410  		return c.typedClient.List(ctx, obj, opts...)
   411  	}
   412  }
   413  
   414  // Status implements client.StatusClient.
   415  func (c *client) Status() SubResourceWriter {
   416  	return c.SubResource("status")
   417  }
   418  
   419  func (c *client) SubResource(subResource string) SubResourceClient {
   420  	return &subResourceClient{client: c, subResource: subResource}
   421  }
   422  
   423  // subResourceClient is client.SubResourceWriter that writes to subresources.
   424  type subResourceClient struct {
   425  	client      *client
   426  	subResource string
   427  }
   428  
   429  // ensure subResourceClient implements client.SubResourceClient.
   430  var _ SubResourceClient = &subResourceClient{}
   431  
   432  // SubResourceGetOptions holds all the possible configuration
   433  // for a subresource Get request.
   434  type SubResourceGetOptions struct {
   435  	Raw *metav1.GetOptions
   436  }
   437  
   438  // ApplyToSubResourceGet updates the configuaration to the given get options.
   439  func (getOpt *SubResourceGetOptions) ApplyToSubResourceGet(o *SubResourceGetOptions) {
   440  	if getOpt.Raw != nil {
   441  		o.Raw = getOpt.Raw
   442  	}
   443  }
   444  
   445  // ApplyOptions applues the given options.
   446  func (getOpt *SubResourceGetOptions) ApplyOptions(opts []SubResourceGetOption) *SubResourceGetOptions {
   447  	for _, o := range opts {
   448  		o.ApplyToSubResourceGet(getOpt)
   449  	}
   450  
   451  	return getOpt
   452  }
   453  
   454  // AsGetOptions returns the configured options as *metav1.GetOptions.
   455  func (getOpt *SubResourceGetOptions) AsGetOptions() *metav1.GetOptions {
   456  	if getOpt.Raw == nil {
   457  		return &metav1.GetOptions{}
   458  	}
   459  	return getOpt.Raw
   460  }
   461  
   462  // SubResourceUpdateOptions holds all the possible configuration
   463  // for a subresource update request.
   464  type SubResourceUpdateOptions struct {
   465  	UpdateOptions
   466  	SubResourceBody Object
   467  }
   468  
   469  // ApplyToSubResourceUpdate updates the configuration on the given create options
   470  func (uo *SubResourceUpdateOptions) ApplyToSubResourceUpdate(o *SubResourceUpdateOptions) {
   471  	uo.UpdateOptions.ApplyToUpdate(&o.UpdateOptions)
   472  	if uo.SubResourceBody != nil {
   473  		o.SubResourceBody = uo.SubResourceBody
   474  	}
   475  }
   476  
   477  // ApplyOptions applies the given options.
   478  func (uo *SubResourceUpdateOptions) ApplyOptions(opts []SubResourceUpdateOption) *SubResourceUpdateOptions {
   479  	for _, o := range opts {
   480  		o.ApplyToSubResourceUpdate(uo)
   481  	}
   482  
   483  	return uo
   484  }
   485  
   486  // SubResourceUpdateAndPatchOption is an option that can be used for either
   487  // a subresource update or patch request.
   488  type SubResourceUpdateAndPatchOption interface {
   489  	SubResourceUpdateOption
   490  	SubResourcePatchOption
   491  }
   492  
   493  // WithSubResourceBody returns an option that uses the given body
   494  // for a subresource Update or Patch operation.
   495  func WithSubResourceBody(body Object) SubResourceUpdateAndPatchOption {
   496  	return &withSubresourceBody{body: body}
   497  }
   498  
   499  type withSubresourceBody struct {
   500  	body Object
   501  }
   502  
   503  func (wsr *withSubresourceBody) ApplyToSubResourceUpdate(o *SubResourceUpdateOptions) {
   504  	o.SubResourceBody = wsr.body
   505  }
   506  
   507  func (wsr *withSubresourceBody) ApplyToSubResourcePatch(o *SubResourcePatchOptions) {
   508  	o.SubResourceBody = wsr.body
   509  }
   510  
   511  // SubResourceCreateOptions are all the possible configurations for a subresource
   512  // create request.
   513  type SubResourceCreateOptions struct {
   514  	CreateOptions
   515  }
   516  
   517  // ApplyOptions applies the given options.
   518  func (co *SubResourceCreateOptions) ApplyOptions(opts []SubResourceCreateOption) *SubResourceCreateOptions {
   519  	for _, o := range opts {
   520  		o.ApplyToSubResourceCreate(co)
   521  	}
   522  
   523  	return co
   524  }
   525  
   526  // ApplyToSubResourceCreate applies the the configuration on the given create options.
   527  func (co *SubResourceCreateOptions) ApplyToSubResourceCreate(o *SubResourceCreateOptions) {
   528  	co.CreateOptions.ApplyToCreate(&co.CreateOptions)
   529  }
   530  
   531  // SubResourcePatchOptions holds all possible configurations for a subresource patch
   532  // request.
   533  type SubResourcePatchOptions struct {
   534  	PatchOptions
   535  	SubResourceBody Object
   536  }
   537  
   538  // ApplyOptions applies the given options.
   539  func (po *SubResourcePatchOptions) ApplyOptions(opts []SubResourcePatchOption) *SubResourcePatchOptions {
   540  	for _, o := range opts {
   541  		o.ApplyToSubResourcePatch(po)
   542  	}
   543  
   544  	return po
   545  }
   546  
   547  // ApplyToSubResourcePatch applies the configuration on the given patch options.
   548  func (po *SubResourcePatchOptions) ApplyToSubResourcePatch(o *SubResourcePatchOptions) {
   549  	po.PatchOptions.ApplyToPatch(&o.PatchOptions)
   550  	if po.SubResourceBody != nil {
   551  		o.SubResourceBody = po.SubResourceBody
   552  	}
   553  }
   554  
   555  func (sc *subResourceClient) Get(ctx context.Context, obj Object, subResource Object, opts ...SubResourceGetOption) error {
   556  	switch obj.(type) {
   557  	case runtime.Unstructured:
   558  		return sc.client.unstructuredClient.GetSubResource(ctx, obj, subResource, sc.subResource, opts...)
   559  	case *metav1.PartialObjectMetadata:
   560  		return errors.New("can not get subresource using only metadata")
   561  	default:
   562  		return sc.client.typedClient.GetSubResource(ctx, obj, subResource, sc.subResource, opts...)
   563  	}
   564  }
   565  
   566  // Create implements client.SubResourceClient
   567  func (sc *subResourceClient) Create(ctx context.Context, obj Object, subResource Object, opts ...SubResourceCreateOption) error {
   568  	defer sc.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
   569  	defer sc.client.resetGroupVersionKind(subResource, subResource.GetObjectKind().GroupVersionKind())
   570  
   571  	switch obj.(type) {
   572  	case runtime.Unstructured:
   573  		return sc.client.unstructuredClient.CreateSubResource(ctx, obj, subResource, sc.subResource, opts...)
   574  	case *metav1.PartialObjectMetadata:
   575  		return fmt.Errorf("cannot update status using only metadata -- did you mean to patch?")
   576  	default:
   577  		return sc.client.typedClient.CreateSubResource(ctx, obj, subResource, sc.subResource, opts...)
   578  	}
   579  }
   580  
   581  // Update implements client.SubResourceClient
   582  func (sc *subResourceClient) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error {
   583  	defer sc.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
   584  	switch obj.(type) {
   585  	case runtime.Unstructured:
   586  		return sc.client.unstructuredClient.UpdateSubResource(ctx, obj, sc.subResource, opts...)
   587  	case *metav1.PartialObjectMetadata:
   588  		return fmt.Errorf("cannot update status using only metadata -- did you mean to patch?")
   589  	default:
   590  		return sc.client.typedClient.UpdateSubResource(ctx, obj, sc.subResource, opts...)
   591  	}
   592  }
   593  
   594  // Patch implements client.SubResourceWriter.
   595  func (sc *subResourceClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error {
   596  	defer sc.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
   597  	switch obj.(type) {
   598  	case runtime.Unstructured:
   599  		return sc.client.unstructuredClient.PatchSubResource(ctx, obj, sc.subResource, patch, opts...)
   600  	case *metav1.PartialObjectMetadata:
   601  		return sc.client.metadataClient.PatchSubResource(ctx, obj, sc.subResource, patch, opts...)
   602  	default:
   603  		return sc.client.typedClient.PatchSubResource(ctx, obj, sc.subResource, patch, opts...)
   604  	}
   605  }
   606  

View as plain text