1
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
40 type Options struct {
41
42 HTTPClient *http.Client
43
44
45 Scheme *runtime.Scheme
46
47
48 Mapper meta.RESTMapper
49
50
51 Cache *CacheOptions
52
53
54
55 WarningHandler WarningHandlerOptions
56
57
58 DryRun *bool
59 }
60
61
62
63
64 type WarningHandlerOptions struct {
65
66
67 SuppressWarnings bool
68
69
70
71
72 AllowDuplicateLogs bool
73 }
74
75
76 type CacheOptions struct {
77
78
79 Reader Reader
80
81
82 DisableFor []Object
83
84
85
86 Unstructured bool
87 }
88
89
90 type NewClientFunc func(config *rest.Config, options Options) (Client, error)
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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
129 logger := log.Log.WithName("KubeAPIWarningLogger")
130
131
132
133
134 config.WarningHandler = log.NewKubeAPIWarningLogger(
135 logger,
136 log.KubeAPIWarningLoggerOptions{
137 Deduplicate: !options.WarningHandler.AllowDuplicateLogs,
138 },
139 )
140 }
141
142
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
152 if options.Scheme == nil {
153 options.Scheme = scheme.Scheme
154 }
155
156
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
202
203 c.cache = options.Cache.Reader
204
205
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
221
222
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
245
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
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
269 func (c *client) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) {
270 return apiutil.GVKForObject(obj, c.scheme)
271 }
272
273
274 func (c *client) IsObjectNamespaced(obj runtime.Object) (bool, error) {
275 return apiutil.IsObjectNamespaced(obj, c.scheme, c.mapper)
276 }
277
278
279 func (c *client) Scheme() *runtime.Scheme {
280 return c.scheme
281 }
282
283
284 func (c *client) RESTMapper() meta.RESTMapper {
285 return c.mapper
286 }
287
288
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
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
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
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
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
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
356 return c.cache.Get(ctx, key, obj, opts...)
357 }
358
359
360 switch obj.(type) {
361 case runtime.Unstructured:
362 return c.unstructuredClient.Get(ctx, key, obj, opts...)
363 case *metav1.PartialObjectMetadata:
364
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
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
378 return c.cache.List(ctx, obj, opts...)
379 }
380
381
382 switch x := obj.(type) {
383 case runtime.Unstructured:
384 return c.unstructuredClient.List(ctx, obj, opts...)
385 case *metav1.PartialObjectMetadataList:
386
387 gvk := obj.GetObjectKind().GroupVersionKind()
388 defer c.resetGroupVersionKind(obj, gvk)
389
390
391 if err := c.metadataClient.List(ctx, obj, opts...); err != nil {
392 return err
393 }
394
395
396 itemGVK := schema.GroupVersionKind{
397 Group: gvk.Group,
398 Version: gvk.Version,
399
400
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
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
424 type subResourceClient struct {
425 client *client
426 subResource string
427 }
428
429
430 var _ SubResourceClient = &subResourceClient{}
431
432
433
434 type SubResourceGetOptions struct {
435 Raw *metav1.GetOptions
436 }
437
438
439 func (getOpt *SubResourceGetOptions) ApplyToSubResourceGet(o *SubResourceGetOptions) {
440 if getOpt.Raw != nil {
441 o.Raw = getOpt.Raw
442 }
443 }
444
445
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
455 func (getOpt *SubResourceGetOptions) AsGetOptions() *metav1.GetOptions {
456 if getOpt.Raw == nil {
457 return &metav1.GetOptions{}
458 }
459 return getOpt.Raw
460 }
461
462
463
464 type SubResourceUpdateOptions struct {
465 UpdateOptions
466 SubResourceBody Object
467 }
468
469
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
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
487
488 type SubResourceUpdateAndPatchOption interface {
489 SubResourceUpdateOption
490 SubResourcePatchOption
491 }
492
493
494
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
512
513 type SubResourceCreateOptions struct {
514 CreateOptions
515 }
516
517
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
527 func (co *SubResourceCreateOptions) ApplyToSubResourceCreate(o *SubResourceCreateOptions) {
528 co.CreateOptions.ApplyToCreate(&co.CreateOptions)
529 }
530
531
532
533 type SubResourcePatchOptions struct {
534 PatchOptions
535 SubResourceBody Object
536 }
537
538
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
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
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
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
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