k8s.io/client-go@v0.31.1/dynamic/simple.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 dynamic 18 19 import ( 20 "context" 21 "fmt" 22 "net/http" 23 "time" 24 25 "k8s.io/apimachinery/pkg/api/meta" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/runtime/schema" 30 "k8s.io/apimachinery/pkg/types" 31 "k8s.io/apimachinery/pkg/watch" 32 "k8s.io/client-go/rest" 33 "k8s.io/client-go/util/consistencydetector" 34 "k8s.io/client-go/util/watchlist" 35 "k8s.io/klog/v2" 36 ) 37 38 type DynamicClient struct { 39 client rest.Interface 40 } 41 42 var _ Interface = &DynamicClient{} 43 44 // ConfigFor returns a copy of the provided config with the 45 // appropriate dynamic client defaults set. 46 func ConfigFor(inConfig *rest.Config) *rest.Config { 47 config := rest.CopyConfig(inConfig) 48 config.AcceptContentTypes = "application/json" 49 config.ContentType = "application/json" 50 config.NegotiatedSerializer = basicNegotiatedSerializer{} // this gets used for discovery and error handling types 51 if config.UserAgent == "" { 52 config.UserAgent = rest.DefaultKubernetesUserAgent() 53 } 54 return config 55 } 56 57 // New creates a new DynamicClient for the given RESTClient. 58 func New(c rest.Interface) *DynamicClient { 59 return &DynamicClient{client: c} 60 } 61 62 // NewForConfigOrDie creates a new DynamicClient for the given config and 63 // panics if there is an error in the config. 64 func NewForConfigOrDie(c *rest.Config) *DynamicClient { 65 ret, err := NewForConfig(c) 66 if err != nil { 67 panic(err) 68 } 69 return ret 70 } 71 72 // NewForConfig creates a new dynamic client or returns an error. 73 // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), 74 // where httpClient was generated with rest.HTTPClientFor(c). 75 func NewForConfig(inConfig *rest.Config) (*DynamicClient, error) { 76 config := ConfigFor(inConfig) 77 78 httpClient, err := rest.HTTPClientFor(config) 79 if err != nil { 80 return nil, err 81 } 82 return NewForConfigAndClient(config, httpClient) 83 } 84 85 // NewForConfigAndClient creates a new dynamic client for the given config and http client. 86 // Note the http client provided takes precedence over the configured transport values. 87 func NewForConfigAndClient(inConfig *rest.Config, h *http.Client) (*DynamicClient, error) { 88 config := ConfigFor(inConfig) 89 // for serializing the options 90 config.GroupVersion = &schema.GroupVersion{} 91 config.APIPath = "/if-you-see-this-search-for-the-break" 92 93 restClient, err := rest.RESTClientForConfigAndClient(config, h) 94 if err != nil { 95 return nil, err 96 } 97 return &DynamicClient{client: restClient}, nil 98 } 99 100 type dynamicResourceClient struct { 101 client *DynamicClient 102 namespace string 103 resource schema.GroupVersionResource 104 } 105 106 func (c *DynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface { 107 return &dynamicResourceClient{client: c, resource: resource} 108 } 109 110 func (c *dynamicResourceClient) Namespace(ns string) ResourceInterface { 111 ret := *c 112 ret.namespace = ns 113 return &ret 114 } 115 116 func (c *dynamicResourceClient) Create(ctx context.Context, obj *unstructured.Unstructured, opts metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) { 117 outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) 118 if err != nil { 119 return nil, err 120 } 121 name := "" 122 if len(subresources) > 0 { 123 accessor, err := meta.Accessor(obj) 124 if err != nil { 125 return nil, err 126 } 127 name = accessor.GetName() 128 if len(name) == 0 { 129 return nil, fmt.Errorf("name is required") 130 } 131 } 132 if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil { 133 return nil, err 134 } 135 136 result := c.client.client. 137 Post(). 138 AbsPath(append(c.makeURLSegments(name), subresources...)...). 139 SetHeader("Content-Type", runtime.ContentTypeJSON). 140 Body(outBytes). 141 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1). 142 Do(ctx) 143 if err := result.Error(); err != nil { 144 return nil, err 145 } 146 147 retBytes, err := result.Raw() 148 if err != nil { 149 return nil, err 150 } 151 uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes) 152 if err != nil { 153 return nil, err 154 } 155 return uncastObj.(*unstructured.Unstructured), nil 156 } 157 158 func (c *dynamicResourceClient) Update(ctx context.Context, obj *unstructured.Unstructured, opts metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) { 159 accessor, err := meta.Accessor(obj) 160 if err != nil { 161 return nil, err 162 } 163 name := accessor.GetName() 164 if len(name) == 0 { 165 return nil, fmt.Errorf("name is required") 166 } 167 if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil { 168 return nil, err 169 } 170 outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) 171 if err != nil { 172 return nil, err 173 } 174 175 result := c.client.client. 176 Put(). 177 AbsPath(append(c.makeURLSegments(name), subresources...)...). 178 SetHeader("Content-Type", runtime.ContentTypeJSON). 179 Body(outBytes). 180 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1). 181 Do(ctx) 182 if err := result.Error(); err != nil { 183 return nil, err 184 } 185 186 retBytes, err := result.Raw() 187 if err != nil { 188 return nil, err 189 } 190 uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes) 191 if err != nil { 192 return nil, err 193 } 194 return uncastObj.(*unstructured.Unstructured), nil 195 } 196 197 func (c *dynamicResourceClient) UpdateStatus(ctx context.Context, obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) { 198 accessor, err := meta.Accessor(obj) 199 if err != nil { 200 return nil, err 201 } 202 name := accessor.GetName() 203 if len(name) == 0 { 204 return nil, fmt.Errorf("name is required") 205 } 206 if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil { 207 return nil, err 208 } 209 outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) 210 if err != nil { 211 return nil, err 212 } 213 214 result := c.client.client. 215 Put(). 216 AbsPath(append(c.makeURLSegments(name), "status")...). 217 SetHeader("Content-Type", runtime.ContentTypeJSON). 218 Body(outBytes). 219 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1). 220 Do(ctx) 221 if err := result.Error(); err != nil { 222 return nil, err 223 } 224 225 retBytes, err := result.Raw() 226 if err != nil { 227 return nil, err 228 } 229 uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes) 230 if err != nil { 231 return nil, err 232 } 233 return uncastObj.(*unstructured.Unstructured), nil 234 } 235 236 func (c *dynamicResourceClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error { 237 if len(name) == 0 { 238 return fmt.Errorf("name is required") 239 } 240 if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil { 241 return err 242 } 243 deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts) 244 if err != nil { 245 return err 246 } 247 248 result := c.client.client. 249 Delete(). 250 AbsPath(append(c.makeURLSegments(name), subresources...)...). 251 SetHeader("Content-Type", runtime.ContentTypeJSON). 252 Body(deleteOptionsByte). 253 Do(ctx) 254 return result.Error() 255 } 256 257 func (c *dynamicResourceClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOptions metav1.ListOptions) error { 258 if err := validateNamespaceWithOptionalName(c.namespace); err != nil { 259 return err 260 } 261 262 deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts) 263 if err != nil { 264 return err 265 } 266 267 result := c.client.client. 268 Delete(). 269 AbsPath(c.makeURLSegments("")...). 270 SetHeader("Content-Type", runtime.ContentTypeJSON). 271 Body(deleteOptionsByte). 272 SpecificallyVersionedParams(&listOptions, dynamicParameterCodec, versionV1). 273 Do(ctx) 274 return result.Error() 275 } 276 277 func (c *dynamicResourceClient) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) { 278 if len(name) == 0 { 279 return nil, fmt.Errorf("name is required") 280 } 281 if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil { 282 return nil, err 283 } 284 result := c.client.client.Get().AbsPath(append(c.makeURLSegments(name), subresources...)...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do(ctx) 285 if err := result.Error(); err != nil { 286 return nil, err 287 } 288 retBytes, err := result.Raw() 289 if err != nil { 290 return nil, err 291 } 292 uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes) 293 if err != nil { 294 return nil, err 295 } 296 return uncastObj.(*unstructured.Unstructured), nil 297 } 298 299 func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) { 300 if watchListOptions, hasWatchListOptionsPrepared, watchListOptionsErr := watchlist.PrepareWatchListOptionsFromListOptions(opts); watchListOptionsErr != nil { 301 klog.Warningf("Failed preparing watchlist options for %v, falling back to the standard LIST semantics, err = %v", c.resource, watchListOptionsErr) 302 } else if hasWatchListOptionsPrepared { 303 result, err := c.watchList(ctx, watchListOptions) 304 if err == nil { 305 consistencydetector.CheckWatchListFromCacheDataConsistencyIfRequested(ctx, fmt.Sprintf("watchlist request for %v", c.resource), c.list, opts, result) 306 return result, nil 307 } 308 klog.Warningf("The watchlist request for %v ended with an error, falling back to the standard LIST semantics, err = %v", c.resource, err) 309 } 310 result, err := c.list(ctx, opts) 311 if err == nil { 312 consistencydetector.CheckListFromCacheDataConsistencyIfRequested(ctx, fmt.Sprintf("list request for %v", c.resource), c.list, opts, result) 313 } 314 return result, err 315 } 316 317 func (c *dynamicResourceClient) list(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) { 318 if err := validateNamespaceWithOptionalName(c.namespace); err != nil { 319 return nil, err 320 } 321 result := c.client.client.Get().AbsPath(c.makeURLSegments("")...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do(ctx) 322 if err := result.Error(); err != nil { 323 return nil, err 324 } 325 retBytes, err := result.Raw() 326 if err != nil { 327 return nil, err 328 } 329 uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes) 330 if err != nil { 331 return nil, err 332 } 333 if list, ok := uncastObj.(*unstructured.UnstructuredList); ok { 334 return list, nil 335 } 336 337 list, err := uncastObj.(*unstructured.Unstructured).ToList() 338 if err != nil { 339 return nil, err 340 } 341 return list, nil 342 } 343 344 // watchList establishes a watch stream with the server and returns an unstructured list. 345 func (c *dynamicResourceClient) watchList(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) { 346 if err := validateNamespaceWithOptionalName(c.namespace); err != nil { 347 return nil, err 348 } 349 350 var timeout time.Duration 351 if opts.TimeoutSeconds != nil { 352 timeout = time.Duration(*opts.TimeoutSeconds) * time.Second 353 } 354 355 result := &unstructured.UnstructuredList{} 356 err := c.client.client.Get().AbsPath(c.makeURLSegments("")...). 357 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1). 358 Timeout(timeout). 359 WatchList(ctx). 360 Into(result) 361 362 return result, err 363 } 364 365 func (c *dynamicResourceClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { 366 opts.Watch = true 367 if err := validateNamespaceWithOptionalName(c.namespace); err != nil { 368 return nil, err 369 } 370 return c.client.client.Get().AbsPath(c.makeURLSegments("")...). 371 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1). 372 Watch(ctx) 373 } 374 375 func (c *dynamicResourceClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) { 376 if len(name) == 0 { 377 return nil, fmt.Errorf("name is required") 378 } 379 if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil { 380 return nil, err 381 } 382 result := c.client.client. 383 Patch(pt). 384 AbsPath(append(c.makeURLSegments(name), subresources...)...). 385 Body(data). 386 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1). 387 Do(ctx) 388 if err := result.Error(); err != nil { 389 return nil, err 390 } 391 retBytes, err := result.Raw() 392 if err != nil { 393 return nil, err 394 } 395 uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes) 396 if err != nil { 397 return nil, err 398 } 399 return uncastObj.(*unstructured.Unstructured), nil 400 } 401 402 func (c *dynamicResourceClient) Apply(ctx context.Context, name string, obj *unstructured.Unstructured, opts metav1.ApplyOptions, subresources ...string) (*unstructured.Unstructured, error) { 403 if len(name) == 0 { 404 return nil, fmt.Errorf("name is required") 405 } 406 if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil { 407 return nil, err 408 } 409 outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) 410 if err != nil { 411 return nil, err 412 } 413 accessor, err := meta.Accessor(obj) 414 if err != nil { 415 return nil, err 416 } 417 managedFields := accessor.GetManagedFields() 418 if len(managedFields) > 0 { 419 return nil, fmt.Errorf(`cannot apply an object with managed fields already set. 420 Use the client-go/applyconfigurations "UnstructructuredExtractor" to obtain the unstructured ApplyConfiguration for the given field manager that you can use/modify here to apply`) 421 } 422 patchOpts := opts.ToPatchOptions() 423 424 result := c.client.client. 425 Patch(types.ApplyPatchType). 426 AbsPath(append(c.makeURLSegments(name), subresources...)...). 427 Body(outBytes). 428 SpecificallyVersionedParams(&patchOpts, dynamicParameterCodec, versionV1). 429 Do(ctx) 430 if err := result.Error(); err != nil { 431 return nil, err 432 } 433 retBytes, err := result.Raw() 434 if err != nil { 435 return nil, err 436 } 437 uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes) 438 if err != nil { 439 return nil, err 440 } 441 return uncastObj.(*unstructured.Unstructured), nil 442 } 443 func (c *dynamicResourceClient) ApplyStatus(ctx context.Context, name string, obj *unstructured.Unstructured, opts metav1.ApplyOptions) (*unstructured.Unstructured, error) { 444 return c.Apply(ctx, name, obj, opts, "status") 445 } 446 447 func validateNamespaceWithOptionalName(namespace string, name ...string) error { 448 if msgs := rest.IsValidPathSegmentName(namespace); len(msgs) != 0 { 449 return fmt.Errorf("invalid namespace %q: %v", namespace, msgs) 450 } 451 if len(name) > 1 { 452 panic("Invalid number of names") 453 } else if len(name) == 1 { 454 if msgs := rest.IsValidPathSegmentName(name[0]); len(msgs) != 0 { 455 return fmt.Errorf("invalid resource name %q: %v", name[0], msgs) 456 } 457 } 458 return nil 459 } 460 461 func (c *dynamicResourceClient) makeURLSegments(name string) []string { 462 url := []string{} 463 if len(c.resource.Group) == 0 { 464 url = append(url, "api") 465 } else { 466 url = append(url, "apis", c.resource.Group) 467 } 468 url = append(url, c.resource.Version) 469 470 if len(c.namespace) > 0 { 471 url = append(url, "namespaces", c.namespace) 472 } 473 url = append(url, c.resource.Resource) 474 475 if len(name) > 0 { 476 url = append(url, name) 477 } 478 479 return url 480 }