sigs.k8s.io/controller-runtime@v0.18.2/pkg/client/client.go (about) 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 }