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 }