k8s.io/client-go@v0.31.1/dynamic/fake/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 fake 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 24 "k8s.io/apimachinery/pkg/api/meta" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 27 "k8s.io/apimachinery/pkg/labels" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/runtime/schema" 30 "k8s.io/apimachinery/pkg/runtime/serializer" 31 "k8s.io/apimachinery/pkg/types" 32 "k8s.io/apimachinery/pkg/watch" 33 "k8s.io/client-go/dynamic" 34 "k8s.io/client-go/testing" 35 ) 36 37 func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeDynamicClient { 38 unstructuredScheme := runtime.NewScheme() 39 for gvk := range scheme.AllKnownTypes() { 40 if unstructuredScheme.Recognizes(gvk) { 41 continue 42 } 43 if strings.HasSuffix(gvk.Kind, "List") { 44 unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.UnstructuredList{}) 45 continue 46 } 47 unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.Unstructured{}) 48 } 49 50 objects, err := convertObjectsToUnstructured(scheme, objects) 51 if err != nil { 52 panic(err) 53 } 54 55 for _, obj := range objects { 56 gvk := obj.GetObjectKind().GroupVersionKind() 57 if !unstructuredScheme.Recognizes(gvk) { 58 unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.Unstructured{}) 59 } 60 gvk.Kind += "List" 61 if !unstructuredScheme.Recognizes(gvk) { 62 unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.UnstructuredList{}) 63 } 64 } 65 66 return NewSimpleDynamicClientWithCustomListKinds(unstructuredScheme, nil, objects...) 67 } 68 69 // NewSimpleDynamicClientWithCustomListKinds try not to use this. In general you want to have the scheme have the List types registered 70 // and allow the default guessing for resources match. Sometimes that doesn't work, so you can specify a custom mapping here. 71 func NewSimpleDynamicClientWithCustomListKinds(scheme *runtime.Scheme, gvrToListKind map[schema.GroupVersionResource]string, objects ...runtime.Object) *FakeDynamicClient { 72 // In order to use List with this client, you have to have your lists registered so that the object tracker will find them 73 // in the scheme to support the t.scheme.New(listGVK) call when it's building the return value. 74 // Since the base fake client needs the listGVK passed through the action (in cases where there are no instances, it 75 // cannot look up the actual hits), we need to know a mapping of GVR to listGVK here. For GETs and other types of calls, 76 // there is no return value that contains a GVK, so it doesn't have to know the mapping in advance. 77 78 // first we attempt to invert known List types from the scheme to auto guess the resource with unsafe guesses 79 // this covers common usage of registering types in scheme and passing them 80 completeGVRToListKind := map[schema.GroupVersionResource]string{} 81 for listGVK := range scheme.AllKnownTypes() { 82 if !strings.HasSuffix(listGVK.Kind, "List") { 83 continue 84 } 85 nonListGVK := listGVK.GroupVersion().WithKind(listGVK.Kind[:len(listGVK.Kind)-4]) 86 plural, _ := meta.UnsafeGuessKindToResource(nonListGVK) 87 completeGVRToListKind[plural] = listGVK.Kind 88 } 89 90 for gvr, listKind := range gvrToListKind { 91 if !strings.HasSuffix(listKind, "List") { 92 panic("coding error, listGVK must end in List or this fake client doesn't work right") 93 } 94 listGVK := gvr.GroupVersion().WithKind(listKind) 95 96 // if we already have this type registered, just skip it 97 if _, err := scheme.New(listGVK); err == nil { 98 completeGVRToListKind[gvr] = listKind 99 continue 100 } 101 102 scheme.AddKnownTypeWithName(listGVK, &unstructured.UnstructuredList{}) 103 completeGVRToListKind[gvr] = listKind 104 } 105 106 codecs := serializer.NewCodecFactory(scheme) 107 o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) 108 for _, obj := range objects { 109 if err := o.Add(obj); err != nil { 110 panic(err) 111 } 112 } 113 114 cs := &FakeDynamicClient{scheme: scheme, gvrToListKind: completeGVRToListKind, tracker: o} 115 cs.AddReactor("*", "*", testing.ObjectReaction(o)) 116 cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { 117 gvr := action.GetResource() 118 ns := action.GetNamespace() 119 watch, err := o.Watch(gvr, ns) 120 if err != nil { 121 return false, nil, err 122 } 123 return true, watch, nil 124 }) 125 126 return cs 127 } 128 129 // Clientset implements clientset.Interface. Meant to be embedded into a 130 // struct to get a default implementation. This makes faking out just the method 131 // you want to test easier. 132 type FakeDynamicClient struct { 133 testing.Fake 134 scheme *runtime.Scheme 135 gvrToListKind map[schema.GroupVersionResource]string 136 tracker testing.ObjectTracker 137 } 138 139 type dynamicResourceClient struct { 140 client *FakeDynamicClient 141 namespace string 142 resource schema.GroupVersionResource 143 listKind string 144 } 145 146 var ( 147 _ dynamic.Interface = &FakeDynamicClient{} 148 _ testing.FakeClient = &FakeDynamicClient{} 149 ) 150 151 func (c *FakeDynamicClient) Tracker() testing.ObjectTracker { 152 return c.tracker 153 } 154 155 func (c *FakeDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableResourceInterface { 156 return &dynamicResourceClient{client: c, resource: resource, listKind: c.gvrToListKind[resource]} 157 } 158 159 func (c *dynamicResourceClient) Namespace(ns string) dynamic.ResourceInterface { 160 ret := *c 161 ret.namespace = ns 162 return &ret 163 } 164 165 func (c *dynamicResourceClient) Create(ctx context.Context, obj *unstructured.Unstructured, opts metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) { 166 var uncastRet runtime.Object 167 var err error 168 switch { 169 case len(c.namespace) == 0 && len(subresources) == 0: 170 uncastRet, err = c.client.Fake. 171 Invokes(testing.NewRootCreateAction(c.resource, obj), obj) 172 173 case len(c.namespace) == 0 && len(subresources) > 0: 174 var accessor metav1.Object // avoid shadowing err 175 accessor, err = meta.Accessor(obj) 176 if err != nil { 177 return nil, err 178 } 179 name := accessor.GetName() 180 uncastRet, err = c.client.Fake. 181 Invokes(testing.NewRootCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), obj), obj) 182 183 case len(c.namespace) > 0 && len(subresources) == 0: 184 uncastRet, err = c.client.Fake. 185 Invokes(testing.NewCreateAction(c.resource, c.namespace, obj), obj) 186 187 case len(c.namespace) > 0 && len(subresources) > 0: 188 var accessor metav1.Object // avoid shadowing err 189 accessor, err = meta.Accessor(obj) 190 if err != nil { 191 return nil, err 192 } 193 name := accessor.GetName() 194 uncastRet, err = c.client.Fake. 195 Invokes(testing.NewCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), c.namespace, obj), obj) 196 197 } 198 199 if err != nil { 200 return nil, err 201 } 202 if uncastRet == nil { 203 return nil, err 204 } 205 206 ret := &unstructured.Unstructured{} 207 if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil { 208 return nil, err 209 } 210 return ret, err 211 } 212 213 func (c *dynamicResourceClient) Update(ctx context.Context, obj *unstructured.Unstructured, opts metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) { 214 var uncastRet runtime.Object 215 var err error 216 switch { 217 case len(c.namespace) == 0 && len(subresources) == 0: 218 uncastRet, err = c.client.Fake. 219 Invokes(testing.NewRootUpdateAction(c.resource, obj), obj) 220 221 case len(c.namespace) == 0 && len(subresources) > 0: 222 uncastRet, err = c.client.Fake. 223 Invokes(testing.NewRootUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), obj), obj) 224 225 case len(c.namespace) > 0 && len(subresources) == 0: 226 uncastRet, err = c.client.Fake. 227 Invokes(testing.NewUpdateAction(c.resource, c.namespace, obj), obj) 228 229 case len(c.namespace) > 0 && len(subresources) > 0: 230 uncastRet, err = c.client.Fake. 231 Invokes(testing.NewUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, obj), obj) 232 233 } 234 235 if err != nil { 236 return nil, err 237 } 238 if uncastRet == nil { 239 return nil, err 240 } 241 242 ret := &unstructured.Unstructured{} 243 if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil { 244 return nil, err 245 } 246 return ret, err 247 } 248 249 func (c *dynamicResourceClient) UpdateStatus(ctx context.Context, obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) { 250 var uncastRet runtime.Object 251 var err error 252 switch { 253 case len(c.namespace) == 0: 254 uncastRet, err = c.client.Fake. 255 Invokes(testing.NewRootUpdateSubresourceAction(c.resource, "status", obj), obj) 256 257 case len(c.namespace) > 0: 258 uncastRet, err = c.client.Fake. 259 Invokes(testing.NewUpdateSubresourceAction(c.resource, "status", c.namespace, obj), obj) 260 261 } 262 263 if err != nil { 264 return nil, err 265 } 266 if uncastRet == nil { 267 return nil, err 268 } 269 270 ret := &unstructured.Unstructured{} 271 if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil { 272 return nil, err 273 } 274 return ret, err 275 } 276 277 func (c *dynamicResourceClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error { 278 var err error 279 switch { 280 case len(c.namespace) == 0 && len(subresources) == 0: 281 _, err = c.client.Fake. 282 Invokes(testing.NewRootDeleteAction(c.resource, name), &metav1.Status{Status: "dynamic delete fail"}) 283 284 case len(c.namespace) == 0 && len(subresources) > 0: 285 _, err = c.client.Fake. 286 Invokes(testing.NewRootDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic delete fail"}) 287 288 case len(c.namespace) > 0 && len(subresources) == 0: 289 _, err = c.client.Fake. 290 Invokes(testing.NewDeleteAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic delete fail"}) 291 292 case len(c.namespace) > 0 && len(subresources) > 0: 293 _, err = c.client.Fake. 294 Invokes(testing.NewDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, name), &metav1.Status{Status: "dynamic delete fail"}) 295 } 296 297 return err 298 } 299 300 func (c *dynamicResourceClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOptions metav1.ListOptions) error { 301 var err error 302 switch { 303 case len(c.namespace) == 0: 304 action := testing.NewRootDeleteCollectionAction(c.resource, listOptions) 305 _, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "dynamic deletecollection fail"}) 306 307 case len(c.namespace) > 0: 308 action := testing.NewDeleteCollectionAction(c.resource, c.namespace, listOptions) 309 _, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "dynamic deletecollection fail"}) 310 311 } 312 313 return err 314 } 315 316 func (c *dynamicResourceClient) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) { 317 var uncastRet runtime.Object 318 var err error 319 switch { 320 case len(c.namespace) == 0 && len(subresources) == 0: 321 uncastRet, err = c.client.Fake. 322 Invokes(testing.NewRootGetAction(c.resource, name), &metav1.Status{Status: "dynamic get fail"}) 323 324 case len(c.namespace) == 0 && len(subresources) > 0: 325 uncastRet, err = c.client.Fake. 326 Invokes(testing.NewRootGetSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic get fail"}) 327 328 case len(c.namespace) > 0 && len(subresources) == 0: 329 uncastRet, err = c.client.Fake. 330 Invokes(testing.NewGetAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic get fail"}) 331 332 case len(c.namespace) > 0 && len(subresources) > 0: 333 uncastRet, err = c.client.Fake. 334 Invokes(testing.NewGetSubresourceAction(c.resource, c.namespace, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic get fail"}) 335 } 336 337 if err != nil { 338 return nil, err 339 } 340 if uncastRet == nil { 341 return nil, err 342 } 343 344 ret := &unstructured.Unstructured{} 345 if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil { 346 return nil, err 347 } 348 return ret, err 349 } 350 351 func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) { 352 if len(c.listKind) == 0 { 353 panic(fmt.Sprintf("coding error: you must register resource to list kind for every resource you're going to LIST when creating the client. See NewSimpleDynamicClientWithCustomListKinds or register the list into the scheme: %v out of %v", c.resource, c.client.gvrToListKind)) 354 } 355 listGVK := c.resource.GroupVersion().WithKind(c.listKind) 356 listForFakeClientGVK := c.resource.GroupVersion().WithKind(c.listKind[:len(c.listKind)-4]) /*base library appends List*/ 357 358 var obj runtime.Object 359 var err error 360 switch { 361 case len(c.namespace) == 0: 362 obj, err = c.client.Fake. 363 Invokes(testing.NewRootListAction(c.resource, listForFakeClientGVK, opts), &metav1.Status{Status: "dynamic list fail"}) 364 365 case len(c.namespace) > 0: 366 obj, err = c.client.Fake. 367 Invokes(testing.NewListAction(c.resource, listForFakeClientGVK, c.namespace, opts), &metav1.Status{Status: "dynamic list fail"}) 368 369 } 370 371 if obj == nil { 372 return nil, err 373 } 374 375 label, _, _ := testing.ExtractFromListOptions(opts) 376 if label == nil { 377 label = labels.Everything() 378 } 379 380 retUnstructured := &unstructured.Unstructured{} 381 if err := c.client.scheme.Convert(obj, retUnstructured, nil); err != nil { 382 return nil, err 383 } 384 entireList, err := retUnstructured.ToList() 385 if err != nil { 386 return nil, err 387 } 388 389 list := &unstructured.UnstructuredList{} 390 list.SetRemainingItemCount(entireList.GetRemainingItemCount()) 391 list.SetResourceVersion(entireList.GetResourceVersion()) 392 list.SetContinue(entireList.GetContinue()) 393 list.GetObjectKind().SetGroupVersionKind(listGVK) 394 for i := range entireList.Items { 395 item := &entireList.Items[i] 396 metadata, err := meta.Accessor(item) 397 if err != nil { 398 return nil, err 399 } 400 if label.Matches(labels.Set(metadata.GetLabels())) { 401 list.Items = append(list.Items, *item) 402 } 403 } 404 return list, nil 405 } 406 407 func (c *dynamicResourceClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { 408 switch { 409 case len(c.namespace) == 0: 410 return c.client.Fake. 411 InvokesWatch(testing.NewRootWatchAction(c.resource, opts)) 412 413 case len(c.namespace) > 0: 414 return c.client.Fake. 415 InvokesWatch(testing.NewWatchAction(c.resource, c.namespace, opts)) 416 417 } 418 419 panic("math broke") 420 } 421 422 // TODO: opts are currently ignored. 423 func (c *dynamicResourceClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) { 424 var uncastRet runtime.Object 425 var err error 426 switch { 427 case len(c.namespace) == 0 && len(subresources) == 0: 428 uncastRet, err = c.client.Fake. 429 Invokes(testing.NewRootPatchAction(c.resource, name, pt, data), &metav1.Status{Status: "dynamic patch fail"}) 430 431 case len(c.namespace) == 0 && len(subresources) > 0: 432 uncastRet, err = c.client.Fake. 433 Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, pt, data, subresources...), &metav1.Status{Status: "dynamic patch fail"}) 434 435 case len(c.namespace) > 0 && len(subresources) == 0: 436 uncastRet, err = c.client.Fake. 437 Invokes(testing.NewPatchAction(c.resource, c.namespace, name, pt, data), &metav1.Status{Status: "dynamic patch fail"}) 438 439 case len(c.namespace) > 0 && len(subresources) > 0: 440 uncastRet, err = c.client.Fake. 441 Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, pt, data, subresources...), &metav1.Status{Status: "dynamic patch fail"}) 442 443 } 444 445 if err != nil { 446 return nil, err 447 } 448 if uncastRet == nil { 449 return nil, err 450 } 451 452 ret := &unstructured.Unstructured{} 453 if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil { 454 return nil, err 455 } 456 return ret, err 457 } 458 459 // TODO: opts are currently ignored. 460 func (c *dynamicResourceClient) Apply(ctx context.Context, name string, obj *unstructured.Unstructured, options metav1.ApplyOptions, subresources ...string) (*unstructured.Unstructured, error) { 461 outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) 462 if err != nil { 463 return nil, err 464 } 465 var uncastRet runtime.Object 466 switch { 467 case len(c.namespace) == 0 && len(subresources) == 0: 468 uncastRet, err = c.client.Fake. 469 Invokes(testing.NewRootPatchAction(c.resource, name, types.ApplyPatchType, outBytes), &metav1.Status{Status: "dynamic patch fail"}) 470 471 case len(c.namespace) == 0 && len(subresources) > 0: 472 uncastRet, err = c.client.Fake. 473 Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, types.ApplyPatchType, outBytes, subresources...), &metav1.Status{Status: "dynamic patch fail"}) 474 475 case len(c.namespace) > 0 && len(subresources) == 0: 476 uncastRet, err = c.client.Fake. 477 Invokes(testing.NewPatchAction(c.resource, c.namespace, name, types.ApplyPatchType, outBytes), &metav1.Status{Status: "dynamic patch fail"}) 478 479 case len(c.namespace) > 0 && len(subresources) > 0: 480 uncastRet, err = c.client.Fake. 481 Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, types.ApplyPatchType, outBytes, subresources...), &metav1.Status{Status: "dynamic patch fail"}) 482 483 } 484 485 if err != nil { 486 return nil, err 487 } 488 if uncastRet == nil { 489 return nil, err 490 } 491 492 ret := &unstructured.Unstructured{} 493 if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil { 494 return nil, err 495 } 496 return ret, nil 497 } 498 499 func (c *dynamicResourceClient) ApplyStatus(ctx context.Context, name string, obj *unstructured.Unstructured, options metav1.ApplyOptions) (*unstructured.Unstructured, error) { 500 return c.Apply(ctx, name, obj, options, "status") 501 } 502 503 func convertObjectsToUnstructured(s *runtime.Scheme, objs []runtime.Object) ([]runtime.Object, error) { 504 ul := make([]runtime.Object, 0, len(objs)) 505 506 for _, obj := range objs { 507 u, err := convertToUnstructured(s, obj) 508 if err != nil { 509 return nil, err 510 } 511 512 ul = append(ul, u) 513 } 514 return ul, nil 515 } 516 517 func convertToUnstructured(s *runtime.Scheme, obj runtime.Object) (runtime.Object, error) { 518 var ( 519 err error 520 u unstructured.Unstructured 521 ) 522 523 u.Object, err = runtime.DefaultUnstructuredConverter.ToUnstructured(obj) 524 if err != nil { 525 return nil, fmt.Errorf("failed to convert to unstructured: %w", err) 526 } 527 528 gvk := u.GroupVersionKind() 529 if gvk.Group == "" || gvk.Kind == "" { 530 gvks, _, err := s.ObjectKinds(obj) 531 if err != nil { 532 return nil, fmt.Errorf("failed to convert to unstructured - unable to get GVK %w", err) 533 } 534 apiv, k := gvks[0].ToAPIVersionAndKind() 535 u.SetAPIVersion(apiv) 536 u.SetKind(k) 537 } 538 return &u, nil 539 }