k8s.io/client-go@v0.31.1/gentype/type.go (about)

     1  /*
     2  Copyright 2024 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 gentype
    18  
    19  import (
    20  	"context"
    21  	json "encoding/json"
    22  	"fmt"
    23  	"time"
    24  
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	types "k8s.io/apimachinery/pkg/types"
    28  	watch "k8s.io/apimachinery/pkg/watch"
    29  	rest "k8s.io/client-go/rest"
    30  	"k8s.io/client-go/util/consistencydetector"
    31  	"k8s.io/client-go/util/watchlist"
    32  	"k8s.io/klog/v2"
    33  )
    34  
    35  // objectWithMeta matches objects implementing both runtime.Object and metav1.Object.
    36  type objectWithMeta interface {
    37  	runtime.Object
    38  	metav1.Object
    39  }
    40  
    41  // namedObject matches comparable objects implementing GetName(); it is intended for use with apply declarative configurations.
    42  type namedObject interface {
    43  	comparable
    44  	GetName() *string
    45  }
    46  
    47  // Client represents a client, optionally namespaced, with no support for lists or apply declarative configurations.
    48  type Client[T objectWithMeta] struct {
    49  	resource       string
    50  	client         rest.Interface
    51  	namespace      string // "" for non-namespaced clients
    52  	newObject      func() T
    53  	parameterCodec runtime.ParameterCodec
    54  }
    55  
    56  // ClientWithList represents a client with support for lists.
    57  type ClientWithList[T objectWithMeta, L runtime.Object] struct {
    58  	*Client[T]
    59  	alsoLister[T, L]
    60  }
    61  
    62  // ClientWithApply represents a client with support for apply declarative configurations.
    63  type ClientWithApply[T objectWithMeta, C namedObject] struct {
    64  	*Client[T]
    65  	alsoApplier[T, C]
    66  }
    67  
    68  // ClientWithListAndApply represents a client with support for lists and apply declarative configurations.
    69  type ClientWithListAndApply[T objectWithMeta, L runtime.Object, C namedObject] struct {
    70  	*Client[T]
    71  	alsoLister[T, L]
    72  	alsoApplier[T, C]
    73  }
    74  
    75  // Helper types for composition
    76  type alsoLister[T objectWithMeta, L runtime.Object] struct {
    77  	client  *Client[T]
    78  	newList func() L
    79  }
    80  
    81  type alsoApplier[T objectWithMeta, C namedObject] struct {
    82  	client *Client[T]
    83  }
    84  
    85  // NewClient constructs a client, namespaced or not, with no support for lists or apply.
    86  // Non-namespaced clients are constructed by passing an empty namespace ("").
    87  func NewClient[T objectWithMeta](
    88  	resource string, client rest.Interface, parameterCodec runtime.ParameterCodec, namespace string, emptyObjectCreator func() T,
    89  ) *Client[T] {
    90  	return &Client[T]{
    91  		resource:       resource,
    92  		client:         client,
    93  		parameterCodec: parameterCodec,
    94  		namespace:      namespace,
    95  		newObject:      emptyObjectCreator,
    96  	}
    97  }
    98  
    99  // NewClientWithList constructs a namespaced client with support for lists.
   100  func NewClientWithList[T objectWithMeta, L runtime.Object](
   101  	resource string, client rest.Interface, parameterCodec runtime.ParameterCodec, namespace string, emptyObjectCreator func() T,
   102  	emptyListCreator func() L,
   103  ) *ClientWithList[T, L] {
   104  	typeClient := NewClient[T](resource, client, parameterCodec, namespace, emptyObjectCreator)
   105  	return &ClientWithList[T, L]{
   106  		typeClient,
   107  		alsoLister[T, L]{typeClient, emptyListCreator},
   108  	}
   109  }
   110  
   111  // NewClientWithApply constructs a namespaced client with support for apply declarative configurations.
   112  func NewClientWithApply[T objectWithMeta, C namedObject](
   113  	resource string, client rest.Interface, parameterCodec runtime.ParameterCodec, namespace string, emptyObjectCreator func() T,
   114  ) *ClientWithApply[T, C] {
   115  	typeClient := NewClient[T](resource, client, parameterCodec, namespace, emptyObjectCreator)
   116  	return &ClientWithApply[T, C]{
   117  		typeClient,
   118  		alsoApplier[T, C]{typeClient},
   119  	}
   120  }
   121  
   122  // NewClientWithListAndApply constructs a client with support for lists and applying declarative configurations.
   123  func NewClientWithListAndApply[T objectWithMeta, L runtime.Object, C namedObject](
   124  	resource string, client rest.Interface, parameterCodec runtime.ParameterCodec, namespace string, emptyObjectCreator func() T,
   125  	emptyListCreator func() L,
   126  ) *ClientWithListAndApply[T, L, C] {
   127  	typeClient := NewClient[T](resource, client, parameterCodec, namespace, emptyObjectCreator)
   128  	return &ClientWithListAndApply[T, L, C]{
   129  		typeClient,
   130  		alsoLister[T, L]{typeClient, emptyListCreator},
   131  		alsoApplier[T, C]{typeClient},
   132  	}
   133  }
   134  
   135  // GetClient returns the REST interface.
   136  func (c *Client[T]) GetClient() rest.Interface {
   137  	return c.client
   138  }
   139  
   140  // GetNamespace returns the client's namespace, if any.
   141  func (c *Client[T]) GetNamespace() string {
   142  	return c.namespace
   143  }
   144  
   145  // Get takes name of the resource, and returns the corresponding object, and an error if there is any.
   146  func (c *Client[T]) Get(ctx context.Context, name string, options metav1.GetOptions) (T, error) {
   147  	result := c.newObject()
   148  	err := c.client.Get().
   149  		NamespaceIfScoped(c.namespace, c.namespace != "").
   150  		Resource(c.resource).
   151  		Name(name).
   152  		VersionedParams(&options, c.parameterCodec).
   153  		Do(ctx).
   154  		Into(result)
   155  	return result, err
   156  }
   157  
   158  // List takes label and field selectors, and returns the list of resources that match those selectors.
   159  func (l *alsoLister[T, L]) List(ctx context.Context, opts metav1.ListOptions) (L, error) {
   160  	if watchListOptions, hasWatchListOptionsPrepared, watchListOptionsErr := watchlist.PrepareWatchListOptionsFromListOptions(opts); watchListOptionsErr != nil {
   161  		klog.Warningf("Failed preparing watchlist options for $.type|resource$, falling back to the standard LIST semantics, err = %v", watchListOptionsErr)
   162  	} else if hasWatchListOptionsPrepared {
   163  		result, err := l.watchList(ctx, watchListOptions)
   164  		if err == nil {
   165  			consistencydetector.CheckWatchListFromCacheDataConsistencyIfRequested(ctx, "watchlist request for "+l.client.resource, l.list, opts, result)
   166  			return result, nil
   167  		}
   168  		klog.Warningf("The watchlist request for %s ended with an error, falling back to the standard LIST semantics, err = %v", l.client.resource, err)
   169  	}
   170  	result, err := l.list(ctx, opts)
   171  	if err == nil {
   172  		consistencydetector.CheckListFromCacheDataConsistencyIfRequested(ctx, "list request for "+l.client.resource, l.list, opts, result)
   173  	}
   174  	return result, err
   175  }
   176  
   177  func (l *alsoLister[T, L]) list(ctx context.Context, opts metav1.ListOptions) (L, error) {
   178  	list := l.newList()
   179  	var timeout time.Duration
   180  	if opts.TimeoutSeconds != nil {
   181  		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
   182  	}
   183  	err := l.client.client.Get().
   184  		NamespaceIfScoped(l.client.namespace, l.client.namespace != "").
   185  		Resource(l.client.resource).
   186  		VersionedParams(&opts, l.client.parameterCodec).
   187  		Timeout(timeout).
   188  		Do(ctx).
   189  		Into(list)
   190  	return list, err
   191  }
   192  
   193  // watchList establishes a watch stream with the server and returns the list of resources.
   194  func (l *alsoLister[T, L]) watchList(ctx context.Context, opts metav1.ListOptions) (result L, err error) {
   195  	var timeout time.Duration
   196  	if opts.TimeoutSeconds != nil {
   197  		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
   198  	}
   199  	result = l.newList()
   200  	err = l.client.client.Get().
   201  		NamespaceIfScoped(l.client.namespace, l.client.namespace != "").
   202  		Resource(l.client.resource).
   203  		VersionedParams(&opts, l.client.parameterCodec).
   204  		Timeout(timeout).
   205  		WatchList(ctx).
   206  		Into(result)
   207  	return
   208  }
   209  
   210  // Watch returns a watch.Interface that watches the requested resources.
   211  func (c *Client[T]) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
   212  	var timeout time.Duration
   213  	if opts.TimeoutSeconds != nil {
   214  		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
   215  	}
   216  	opts.Watch = true
   217  	return c.client.Get().
   218  		NamespaceIfScoped(c.namespace, c.namespace != "").
   219  		Resource(c.resource).
   220  		VersionedParams(&opts, c.parameterCodec).
   221  		Timeout(timeout).
   222  		Watch(ctx)
   223  }
   224  
   225  // Create takes the representation of a resource and creates it.  Returns the server's representation of the resource, and an error, if there is any.
   226  func (c *Client[T]) Create(ctx context.Context, obj T, opts metav1.CreateOptions) (T, error) {
   227  	result := c.newObject()
   228  	err := c.client.Post().
   229  		NamespaceIfScoped(c.namespace, c.namespace != "").
   230  		Resource(c.resource).
   231  		VersionedParams(&opts, c.parameterCodec).
   232  		Body(obj).
   233  		Do(ctx).
   234  		Into(result)
   235  	return result, err
   236  }
   237  
   238  // Update takes the representation of a resource and updates it. Returns the server's representation of the resource, and an error, if there is any.
   239  func (c *Client[T]) Update(ctx context.Context, obj T, opts metav1.UpdateOptions) (T, error) {
   240  	result := c.newObject()
   241  	err := c.client.Put().
   242  		NamespaceIfScoped(c.namespace, c.namespace != "").
   243  		Resource(c.resource).
   244  		Name(obj.GetName()).
   245  		VersionedParams(&opts, c.parameterCodec).
   246  		Body(obj).
   247  		Do(ctx).
   248  		Into(result)
   249  	return result, err
   250  }
   251  
   252  // UpdateStatus updates the status subresource of a resource. Returns the server's representation of the resource, and an error, if there is any.
   253  func (c *Client[T]) UpdateStatus(ctx context.Context, obj T, opts metav1.UpdateOptions) (T, error) {
   254  	result := c.newObject()
   255  	err := c.client.Put().
   256  		NamespaceIfScoped(c.namespace, c.namespace != "").
   257  		Resource(c.resource).
   258  		Name(obj.GetName()).
   259  		SubResource("status").
   260  		VersionedParams(&opts, c.parameterCodec).
   261  		Body(obj).
   262  		Do(ctx).
   263  		Into(result)
   264  	return result, err
   265  }
   266  
   267  // Delete takes name of the resource and deletes it. Returns an error if one occurs.
   268  func (c *Client[T]) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
   269  	return c.client.Delete().
   270  		NamespaceIfScoped(c.namespace, c.namespace != "").
   271  		Resource(c.resource).
   272  		Name(name).
   273  		Body(&opts).
   274  		Do(ctx).
   275  		Error()
   276  }
   277  
   278  // DeleteCollection deletes a collection of objects.
   279  func (l *alsoLister[T, L]) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error {
   280  	var timeout time.Duration
   281  	if listOpts.TimeoutSeconds != nil {
   282  		timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
   283  	}
   284  	return l.client.client.Delete().
   285  		NamespaceIfScoped(l.client.namespace, l.client.namespace != "").
   286  		Resource(l.client.resource).
   287  		VersionedParams(&listOpts, l.client.parameterCodec).
   288  		Timeout(timeout).
   289  		Body(&opts).
   290  		Do(ctx).
   291  		Error()
   292  }
   293  
   294  // Patch applies the patch and returns the patched resource.
   295  func (c *Client[T]) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (T, error) {
   296  	result := c.newObject()
   297  	err := c.client.Patch(pt).
   298  		NamespaceIfScoped(c.namespace, c.namespace != "").
   299  		Resource(c.resource).
   300  		Name(name).
   301  		SubResource(subresources...).
   302  		VersionedParams(&opts, c.parameterCodec).
   303  		Body(data).
   304  		Do(ctx).
   305  		Into(result)
   306  	return result, err
   307  }
   308  
   309  // Apply takes the given apply declarative configuration, applies it and returns the applied resource.
   310  func (a *alsoApplier[T, C]) Apply(ctx context.Context, obj C, opts metav1.ApplyOptions) (T, error) {
   311  	result := a.client.newObject()
   312  	if obj == *new(C) {
   313  		return *new(T), fmt.Errorf("object provided to Apply must not be nil")
   314  	}
   315  	patchOpts := opts.ToPatchOptions()
   316  	data, err := json.Marshal(obj)
   317  	if err != nil {
   318  		return *new(T), err
   319  	}
   320  	if obj.GetName() == nil {
   321  		return *new(T), fmt.Errorf("obj.Name must be provided to Apply")
   322  	}
   323  	err = a.client.client.Patch(types.ApplyPatchType).
   324  		NamespaceIfScoped(a.client.namespace, a.client.namespace != "").
   325  		Resource(a.client.resource).
   326  		Name(*obj.GetName()).
   327  		VersionedParams(&patchOpts, a.client.parameterCodec).
   328  		Body(data).
   329  		Do(ctx).
   330  		Into(result)
   331  	return result, err
   332  }
   333  
   334  // Apply takes the given apply declarative configuration, applies it to the status subresource and returns the applied resource.
   335  func (a *alsoApplier[T, C]) ApplyStatus(ctx context.Context, obj C, opts metav1.ApplyOptions) (T, error) {
   336  	if obj == *new(C) {
   337  		return *new(T), fmt.Errorf("object provided to Apply must not be nil")
   338  	}
   339  	patchOpts := opts.ToPatchOptions()
   340  	data, err := json.Marshal(obj)
   341  	if err != nil {
   342  		return *new(T), err
   343  	}
   344  
   345  	if obj.GetName() == nil {
   346  		return *new(T), fmt.Errorf("obj.Name must be provided to Apply")
   347  	}
   348  
   349  	result := a.client.newObject()
   350  	err = a.client.client.Patch(types.ApplyPatchType).
   351  		NamespaceIfScoped(a.client.namespace, a.client.namespace != "").
   352  		Resource(a.client.resource).
   353  		Name(*obj.GetName()).
   354  		SubResource("status").
   355  		VersionedParams(&patchOpts, a.client.parameterCodec).
   356  		Body(data).
   357  		Do(ctx).
   358  		Into(result)
   359  	return result, err
   360  }