k8s.io/client-go@v0.31.1/testing/fixture.go (about) 1 /* 2 Copyright 2015 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 testing 18 19 import ( 20 "fmt" 21 "reflect" 22 "sigs.k8s.io/structured-merge-diff/v4/typed" 23 "sigs.k8s.io/yaml" 24 "sort" 25 "strings" 26 "sync" 27 28 jsonpatch "gopkg.in/evanphx/json-patch.v4" 29 30 apierrors "k8s.io/apimachinery/pkg/api/errors" 31 "k8s.io/apimachinery/pkg/api/meta" 32 "k8s.io/apimachinery/pkg/api/meta/testrestmapper" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 35 "k8s.io/apimachinery/pkg/runtime" 36 "k8s.io/apimachinery/pkg/runtime/schema" 37 "k8s.io/apimachinery/pkg/types" 38 "k8s.io/apimachinery/pkg/util/json" 39 "k8s.io/apimachinery/pkg/util/managedfields" 40 "k8s.io/apimachinery/pkg/util/strategicpatch" 41 "k8s.io/apimachinery/pkg/watch" 42 restclient "k8s.io/client-go/rest" 43 ) 44 45 // ObjectTracker keeps track of objects. It is intended to be used to 46 // fake calls to a server by returning objects based on their kind, 47 // namespace and name. 48 type ObjectTracker interface { 49 // Add adds an object to the tracker. If object being added 50 // is a list, its items are added separately. 51 Add(obj runtime.Object) error 52 53 // Get retrieves the object by its kind, namespace and name. 54 Get(gvr schema.GroupVersionResource, ns, name string, opts ...metav1.GetOptions) (runtime.Object, error) 55 56 // Create adds an object to the tracker in the specified namespace. 57 Create(gvr schema.GroupVersionResource, obj runtime.Object, ns string, opts ...metav1.CreateOptions) error 58 59 // Update updates an existing object in the tracker in the specified namespace. 60 Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string, opts ...metav1.UpdateOptions) error 61 62 // Patch patches an existing object in the tracker in the specified namespace. 63 Patch(gvr schema.GroupVersionResource, obj runtime.Object, ns string, opts ...metav1.PatchOptions) error 64 65 // Apply applies an object in the tracker in the specified namespace. 66 Apply(gvr schema.GroupVersionResource, applyConfiguration runtime.Object, ns string, opts ...metav1.PatchOptions) error 67 68 // List retrieves all objects of a given kind in the given 69 // namespace. Only non-List kinds are accepted. 70 List(gvr schema.GroupVersionResource, gvk schema.GroupVersionKind, ns string, opts ...metav1.ListOptions) (runtime.Object, error) 71 72 // Delete deletes an existing object from the tracker. If object 73 // didn't exist in the tracker prior to deletion, Delete returns 74 // no error. 75 Delete(gvr schema.GroupVersionResource, ns, name string, opts ...metav1.DeleteOptions) error 76 77 // Watch watches objects from the tracker. Watch returns a channel 78 // which will push added / modified / deleted object. 79 Watch(gvr schema.GroupVersionResource, ns string, opts ...metav1.ListOptions) (watch.Interface, error) 80 } 81 82 // ObjectScheme abstracts the implementation of common operations on objects. 83 type ObjectScheme interface { 84 runtime.ObjectCreater 85 runtime.ObjectTyper 86 } 87 88 // ObjectReaction returns a ReactionFunc that applies core.Action to 89 // the given tracker. 90 // 91 // If tracker also implements ManagedFieldObjectTracker, then managed fields 92 // will be handled by the tracker and apply patch actions will be evaluated 93 // using the field manager and will take field ownership into consideration. 94 // Without a ManagedFieldObjectTracker, apply patch actions do not consider 95 // field ownership. 96 // 97 // WARNING: There is no server side defaulting, validation, or conversion handled 98 // by the fake client and subresources are not handled accurately (fields in the 99 // root resource are not automatically updated when a scale resource is updated, for example). 100 func ObjectReaction(tracker ObjectTracker) ReactionFunc { 101 reactor := objectTrackerReact{tracker: tracker} 102 return func(action Action) (bool, runtime.Object, error) { 103 // Here and below we need to switch on implementation types, 104 // not on interfaces, as some interfaces are identical 105 // (e.g. UpdateAction and CreateAction), so if we use them, 106 // updates and creates end up matching the same case branch. 107 switch action := action.(type) { 108 case ListActionImpl: 109 obj, err := reactor.List(action) 110 return true, obj, err 111 case GetActionImpl: 112 obj, err := reactor.Get(action) 113 return true, obj, err 114 case CreateActionImpl: 115 obj, err := reactor.Create(action) 116 return true, obj, err 117 case UpdateActionImpl: 118 obj, err := reactor.Update(action) 119 return true, obj, err 120 case DeleteActionImpl: 121 obj, err := reactor.Delete(action) 122 return true, obj, err 123 case PatchActionImpl: 124 if action.GetPatchType() == types.ApplyPatchType { 125 obj, err := reactor.Apply(action) 126 return true, obj, err 127 } 128 obj, err := reactor.Patch(action) 129 return true, obj, err 130 default: 131 return false, nil, fmt.Errorf("no reaction implemented for %s", action) 132 } 133 } 134 } 135 136 type objectTrackerReact struct { 137 tracker ObjectTracker 138 } 139 140 func (o objectTrackerReact) List(action ListActionImpl) (runtime.Object, error) { 141 return o.tracker.List(action.GetResource(), action.GetKind(), action.GetNamespace(), action.ListOptions) 142 } 143 144 func (o objectTrackerReact) Get(action GetActionImpl) (runtime.Object, error) { 145 return o.tracker.Get(action.GetResource(), action.GetNamespace(), action.GetName(), action.GetOptions) 146 } 147 148 func (o objectTrackerReact) Create(action CreateActionImpl) (runtime.Object, error) { 149 ns := action.GetNamespace() 150 gvr := action.GetResource() 151 objMeta, err := meta.Accessor(action.GetObject()) 152 if err != nil { 153 return nil, err 154 } 155 if action.GetSubresource() == "" { 156 err = o.tracker.Create(gvr, action.GetObject(), ns, action.CreateOptions) 157 if err != nil { 158 return nil, err 159 } 160 } else { 161 oldObj, getOldObjErr := o.tracker.Get(gvr, ns, objMeta.GetName(), metav1.GetOptions{}) 162 if getOldObjErr != nil { 163 return nil, getOldObjErr 164 } 165 // Check whether the existing historical object type is the same as the current operation object type that needs to be updated, and if it is the same, perform the update operation. 166 if reflect.TypeOf(oldObj) == reflect.TypeOf(action.GetObject()) { 167 // TODO: Currently we're handling subresource creation as an update 168 // on the enclosing resource. This works for some subresources but 169 // might not be generic enough. 170 err = o.tracker.Update(gvr, action.GetObject(), ns, metav1.UpdateOptions{ 171 DryRun: action.CreateOptions.DryRun, 172 FieldManager: action.CreateOptions.FieldManager, 173 FieldValidation: action.CreateOptions.FieldValidation, 174 }) 175 } else { 176 // If the historical object type is different from the current object type, need to make sure we return the object submitted,don't persist the submitted object in the tracker. 177 return action.GetObject(), nil 178 } 179 } 180 if err != nil { 181 return nil, err 182 } 183 obj, err := o.tracker.Get(gvr, ns, objMeta.GetName(), metav1.GetOptions{}) 184 return obj, err 185 } 186 187 func (o objectTrackerReact) Update(action UpdateActionImpl) (runtime.Object, error) { 188 ns := action.GetNamespace() 189 gvr := action.GetResource() 190 objMeta, err := meta.Accessor(action.GetObject()) 191 if err != nil { 192 return nil, err 193 } 194 195 err = o.tracker.Update(gvr, action.GetObject(), ns, action.UpdateOptions) 196 if err != nil { 197 return nil, err 198 } 199 200 obj, err := o.tracker.Get(gvr, ns, objMeta.GetName(), metav1.GetOptions{}) 201 return obj, err 202 } 203 204 func (o objectTrackerReact) Delete(action DeleteActionImpl) (runtime.Object, error) { 205 err := o.tracker.Delete(action.GetResource(), action.GetNamespace(), action.GetName(), action.DeleteOptions) 206 return nil, err 207 } 208 209 func (o objectTrackerReact) Apply(action PatchActionImpl) (runtime.Object, error) { 210 ns := action.GetNamespace() 211 gvr := action.GetResource() 212 213 patchObj := &unstructured.Unstructured{Object: map[string]interface{}{}} 214 if err := yaml.Unmarshal(action.GetPatch(), &patchObj.Object); err != nil { 215 return nil, err 216 } 217 err := o.tracker.Apply(gvr, patchObj, ns, action.PatchOptions) 218 if err != nil { 219 return nil, err 220 } 221 obj, err := o.tracker.Get(gvr, ns, action.GetName(), metav1.GetOptions{}) 222 return obj, err 223 } 224 225 func (o objectTrackerReact) Patch(action PatchActionImpl) (runtime.Object, error) { 226 ns := action.GetNamespace() 227 gvr := action.GetResource() 228 229 obj, err := o.tracker.Get(gvr, ns, action.GetName(), metav1.GetOptions{}) 230 if err != nil { 231 return nil, err 232 } 233 234 old, err := json.Marshal(obj) 235 if err != nil { 236 return nil, err 237 } 238 239 // reset the object in preparation to unmarshal, since unmarshal does not guarantee that fields 240 // in obj that are removed by patch are cleared 241 value := reflect.ValueOf(obj) 242 value.Elem().Set(reflect.New(value.Type().Elem()).Elem()) 243 244 switch action.GetPatchType() { 245 case types.JSONPatchType: 246 patch, err := jsonpatch.DecodePatch(action.GetPatch()) 247 if err != nil { 248 return nil, err 249 } 250 modified, err := patch.Apply(old) 251 if err != nil { 252 return nil, err 253 } 254 255 if err = json.Unmarshal(modified, obj); err != nil { 256 return nil, err 257 } 258 case types.MergePatchType: 259 modified, err := jsonpatch.MergePatch(old, action.GetPatch()) 260 if err != nil { 261 return nil, err 262 } 263 264 if err := json.Unmarshal(modified, obj); err != nil { 265 return nil, err 266 } 267 case types.StrategicMergePatchType: 268 mergedByte, err := strategicpatch.StrategicMergePatch(old, action.GetPatch(), obj) 269 if err != nil { 270 return nil, err 271 } 272 if err = json.Unmarshal(mergedByte, obj); err != nil { 273 return nil, err 274 } 275 default: 276 return nil, fmt.Errorf("PatchType %s is not supported", action.GetPatchType()) 277 } 278 279 if err = o.tracker.Patch(gvr, obj, ns, action.PatchOptions); err != nil { 280 return nil, err 281 } 282 283 return obj, nil 284 } 285 286 type tracker struct { 287 scheme ObjectScheme 288 decoder runtime.Decoder 289 lock sync.RWMutex 290 objects map[schema.GroupVersionResource]map[types.NamespacedName]runtime.Object 291 // The value type of watchers is a map of which the key is either a namespace or 292 // all/non namespace aka "" and its value is list of fake watchers. 293 // Manipulations on resources will broadcast the notification events into the 294 // watchers' channel. Note that too many unhandled events (currently 100, 295 // see apimachinery/pkg/watch.DefaultChanSize) will cause a panic. 296 watchers map[schema.GroupVersionResource]map[string][]*watch.RaceFreeFakeWatcher 297 } 298 299 var _ ObjectTracker = &tracker{} 300 301 // NewObjectTracker returns an ObjectTracker that can be used to keep track 302 // of objects for the fake clientset. Mostly useful for unit tests. 303 func NewObjectTracker(scheme ObjectScheme, decoder runtime.Decoder) ObjectTracker { 304 return &tracker{ 305 scheme: scheme, 306 decoder: decoder, 307 objects: make(map[schema.GroupVersionResource]map[types.NamespacedName]runtime.Object), 308 watchers: make(map[schema.GroupVersionResource]map[string][]*watch.RaceFreeFakeWatcher), 309 } 310 } 311 312 func (t *tracker) List(gvr schema.GroupVersionResource, gvk schema.GroupVersionKind, ns string, opts ...metav1.ListOptions) (runtime.Object, error) { 313 _, err := assertOptionalSingleArgument(opts) 314 if err != nil { 315 return nil, err 316 } 317 // Heuristic for list kind: original kind + List suffix. Might 318 // not always be true but this tracker has a pretty limited 319 // understanding of the actual API model. 320 listGVK := gvk 321 listGVK.Kind = listGVK.Kind + "List" 322 // GVK does have the concept of "internal version". The scheme recognizes 323 // the runtime.APIVersionInternal, but not the empty string. 324 if listGVK.Version == "" { 325 listGVK.Version = runtime.APIVersionInternal 326 } 327 328 list, err := t.scheme.New(listGVK) 329 if err != nil { 330 return nil, err 331 } 332 333 if !meta.IsListType(list) { 334 return nil, fmt.Errorf("%q is not a list type", listGVK.Kind) 335 } 336 337 t.lock.RLock() 338 defer t.lock.RUnlock() 339 340 objs, ok := t.objects[gvr] 341 if !ok { 342 return list, nil 343 } 344 345 matchingObjs, err := filterByNamespace(objs, ns) 346 if err != nil { 347 return nil, err 348 } 349 if err := meta.SetList(list, matchingObjs); err != nil { 350 return nil, err 351 } 352 return list.DeepCopyObject(), nil 353 } 354 355 func (t *tracker) Watch(gvr schema.GroupVersionResource, ns string, opts ...metav1.ListOptions) (watch.Interface, error) { 356 _, err := assertOptionalSingleArgument(opts) 357 if err != nil { 358 return nil, err 359 } 360 361 t.lock.Lock() 362 defer t.lock.Unlock() 363 364 fakewatcher := watch.NewRaceFreeFake() 365 366 if _, exists := t.watchers[gvr]; !exists { 367 t.watchers[gvr] = make(map[string][]*watch.RaceFreeFakeWatcher) 368 } 369 t.watchers[gvr][ns] = append(t.watchers[gvr][ns], fakewatcher) 370 return fakewatcher, nil 371 } 372 373 func (t *tracker) Get(gvr schema.GroupVersionResource, ns, name string, opts ...metav1.GetOptions) (runtime.Object, error) { 374 _, err := assertOptionalSingleArgument(opts) 375 if err != nil { 376 return nil, err 377 } 378 errNotFound := apierrors.NewNotFound(gvr.GroupResource(), name) 379 380 t.lock.RLock() 381 defer t.lock.RUnlock() 382 383 objs, ok := t.objects[gvr] 384 if !ok { 385 return nil, errNotFound 386 } 387 388 matchingObj, ok := objs[types.NamespacedName{Namespace: ns, Name: name}] 389 if !ok { 390 return nil, errNotFound 391 } 392 393 // Only one object should match in the tracker if it works 394 // correctly, as Add/Update methods enforce kind/namespace/name 395 // uniqueness. 396 obj := matchingObj.DeepCopyObject() 397 if status, ok := obj.(*metav1.Status); ok { 398 if status.Status != metav1.StatusSuccess { 399 return nil, &apierrors.StatusError{ErrStatus: *status} 400 } 401 } 402 403 return obj, nil 404 } 405 406 func (t *tracker) Add(obj runtime.Object) error { 407 if meta.IsListType(obj) { 408 return t.addList(obj, false) 409 } 410 objMeta, err := meta.Accessor(obj) 411 if err != nil { 412 return err 413 } 414 gvks, _, err := t.scheme.ObjectKinds(obj) 415 if err != nil { 416 return err 417 } 418 419 if partial, ok := obj.(*metav1.PartialObjectMetadata); ok && len(partial.TypeMeta.APIVersion) > 0 { 420 gvks = []schema.GroupVersionKind{partial.TypeMeta.GroupVersionKind()} 421 } 422 423 if len(gvks) == 0 { 424 return fmt.Errorf("no registered kinds for %v", obj) 425 } 426 for _, gvk := range gvks { 427 // NOTE: UnsafeGuessKindToResource is a heuristic and default match. The 428 // actual registration in apiserver can specify arbitrary route for a 429 // gvk. If a test uses such objects, it cannot preset the tracker with 430 // objects via Add(). Instead, it should trigger the Create() function 431 // of the tracker, where an arbitrary gvr can be specified. 432 gvr, _ := meta.UnsafeGuessKindToResource(gvk) 433 // Resource doesn't have the concept of "__internal" version, just set it to "". 434 if gvr.Version == runtime.APIVersionInternal { 435 gvr.Version = "" 436 } 437 438 err := t.add(gvr, obj, objMeta.GetNamespace(), false) 439 if err != nil { 440 return err 441 } 442 } 443 return nil 444 } 445 446 func (t *tracker) Create(gvr schema.GroupVersionResource, obj runtime.Object, ns string, opts ...metav1.CreateOptions) error { 447 _, err := assertOptionalSingleArgument(opts) 448 if err != nil { 449 return err 450 } 451 return t.add(gvr, obj, ns, false) 452 } 453 454 func (t *tracker) Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string, opts ...metav1.UpdateOptions) error { 455 _, err := assertOptionalSingleArgument(opts) 456 if err != nil { 457 return err 458 } 459 return t.add(gvr, obj, ns, true) 460 } 461 462 func (t *tracker) Patch(gvr schema.GroupVersionResource, patchedObject runtime.Object, ns string, opts ...metav1.PatchOptions) error { 463 _, err := assertOptionalSingleArgument(opts) 464 if err != nil { 465 return err 466 } 467 return t.add(gvr, patchedObject, ns, true) 468 } 469 470 func (t *tracker) Apply(gvr schema.GroupVersionResource, applyConfiguration runtime.Object, ns string, opts ...metav1.PatchOptions) error { 471 _, err := assertOptionalSingleArgument(opts) 472 if err != nil { 473 return err 474 } 475 applyConfigurationMeta, err := meta.Accessor(applyConfiguration) 476 if err != nil { 477 return err 478 } 479 480 obj, err := t.Get(gvr, ns, applyConfigurationMeta.GetName(), metav1.GetOptions{}) 481 if err != nil { 482 return err 483 } 484 485 old, err := json.Marshal(obj) 486 if err != nil { 487 return err 488 } 489 490 // reset the object in preparation to unmarshal, since unmarshal does not guarantee that fields 491 // in obj that are removed by patch are cleared 492 value := reflect.ValueOf(obj) 493 value.Elem().Set(reflect.New(value.Type().Elem()).Elem()) 494 495 // For backward compatibility with behavior 1.30 and earlier, continue to handle apply 496 // via strategic merge patch (clients may use fake.NewClientset and ManagedFieldObjectTracker 497 // for full field manager support). 498 patch, err := json.Marshal(applyConfiguration) 499 if err != nil { 500 return err 501 } 502 mergedByte, err := strategicpatch.StrategicMergePatch(old, patch, obj) 503 if err != nil { 504 return err 505 } 506 if err = json.Unmarshal(mergedByte, obj); err != nil { 507 return err 508 } 509 510 return t.add(gvr, obj, ns, true) 511 } 512 513 func (t *tracker) getWatches(gvr schema.GroupVersionResource, ns string) []*watch.RaceFreeFakeWatcher { 514 watches := []*watch.RaceFreeFakeWatcher{} 515 if t.watchers[gvr] != nil { 516 if w := t.watchers[gvr][ns]; w != nil { 517 watches = append(watches, w...) 518 } 519 if ns != metav1.NamespaceAll { 520 if w := t.watchers[gvr][metav1.NamespaceAll]; w != nil { 521 watches = append(watches, w...) 522 } 523 } 524 } 525 return watches 526 } 527 528 func (t *tracker) add(gvr schema.GroupVersionResource, obj runtime.Object, ns string, replaceExisting bool) error { 529 t.lock.Lock() 530 defer t.lock.Unlock() 531 532 gr := gvr.GroupResource() 533 534 // To avoid the object from being accidentally modified by caller 535 // after it's been added to the tracker, we always store the deep 536 // copy. 537 obj = obj.DeepCopyObject() 538 539 newMeta, err := meta.Accessor(obj) 540 if err != nil { 541 return err 542 } 543 544 // Propagate namespace to the new object if hasn't already been set. 545 if len(newMeta.GetNamespace()) == 0 { 546 newMeta.SetNamespace(ns) 547 } 548 549 if ns != newMeta.GetNamespace() { 550 msg := fmt.Sprintf("request namespace does not match object namespace, request: %q object: %q", ns, newMeta.GetNamespace()) 551 return apierrors.NewBadRequest(msg) 552 } 553 554 _, ok := t.objects[gvr] 555 if !ok { 556 t.objects[gvr] = make(map[types.NamespacedName]runtime.Object) 557 } 558 559 namespacedName := types.NamespacedName{Namespace: newMeta.GetNamespace(), Name: newMeta.GetName()} 560 if _, ok = t.objects[gvr][namespacedName]; ok { 561 if replaceExisting { 562 for _, w := range t.getWatches(gvr, ns) { 563 // To avoid the object from being accidentally modified by watcher 564 w.Modify(obj.DeepCopyObject()) 565 } 566 t.objects[gvr][namespacedName] = obj 567 return nil 568 } 569 return apierrors.NewAlreadyExists(gr, newMeta.GetName()) 570 } 571 572 if replaceExisting { 573 // Tried to update but no matching object was found. 574 return apierrors.NewNotFound(gr, newMeta.GetName()) 575 } 576 577 t.objects[gvr][namespacedName] = obj 578 579 for _, w := range t.getWatches(gvr, ns) { 580 // To avoid the object from being accidentally modified by watcher 581 w.Add(obj.DeepCopyObject()) 582 } 583 584 return nil 585 } 586 587 func (t *tracker) addList(obj runtime.Object, replaceExisting bool) error { 588 list, err := meta.ExtractList(obj) 589 if err != nil { 590 return err 591 } 592 errs := runtime.DecodeList(list, t.decoder) 593 if len(errs) > 0 { 594 return errs[0] 595 } 596 for _, obj := range list { 597 if err := t.Add(obj); err != nil { 598 return err 599 } 600 } 601 return nil 602 } 603 604 func (t *tracker) Delete(gvr schema.GroupVersionResource, ns, name string, opts ...metav1.DeleteOptions) error { 605 _, err := assertOptionalSingleArgument(opts) 606 if err != nil { 607 return err 608 } 609 t.lock.Lock() 610 defer t.lock.Unlock() 611 612 objs, ok := t.objects[gvr] 613 if !ok { 614 return apierrors.NewNotFound(gvr.GroupResource(), name) 615 } 616 617 namespacedName := types.NamespacedName{Namespace: ns, Name: name} 618 obj, ok := objs[namespacedName] 619 if !ok { 620 return apierrors.NewNotFound(gvr.GroupResource(), name) 621 } 622 623 delete(objs, namespacedName) 624 for _, w := range t.getWatches(gvr, ns) { 625 w.Delete(obj.DeepCopyObject()) 626 } 627 return nil 628 } 629 630 type managedFieldObjectTracker struct { 631 ObjectTracker 632 scheme ObjectScheme 633 objectConverter runtime.ObjectConvertor 634 mapper meta.RESTMapper 635 typeConverter managedfields.TypeConverter 636 } 637 638 var _ ObjectTracker = &managedFieldObjectTracker{} 639 640 // NewFieldManagedObjectTracker returns an ObjectTracker that can be used to keep track 641 // of objects and managed fields for the fake clientset. Mostly useful for unit tests. 642 func NewFieldManagedObjectTracker(scheme *runtime.Scheme, decoder runtime.Decoder, typeConverter managedfields.TypeConverter) ObjectTracker { 643 return &managedFieldObjectTracker{ 644 ObjectTracker: NewObjectTracker(scheme, decoder), 645 scheme: scheme, 646 objectConverter: scheme, 647 mapper: testrestmapper.TestOnlyStaticRESTMapper(scheme), 648 typeConverter: typeConverter, 649 } 650 } 651 652 func (t *managedFieldObjectTracker) Create(gvr schema.GroupVersionResource, obj runtime.Object, ns string, vopts ...metav1.CreateOptions) error { 653 opts, err := assertOptionalSingleArgument(vopts) 654 if err != nil { 655 return err 656 } 657 gvk, err := t.mapper.KindFor(gvr) 658 if err != nil { 659 return err 660 } 661 mgr, err := t.fieldManagerFor(gvk) 662 if err != nil { 663 return err 664 } 665 666 objType, err := meta.TypeAccessor(obj) 667 if err != nil { 668 return err 669 } 670 // Stamp GVK 671 apiVersion, kind := gvk.ToAPIVersionAndKind() 672 objType.SetAPIVersion(apiVersion) 673 objType.SetKind(kind) 674 675 objMeta, err := meta.Accessor(obj) 676 if err != nil { 677 return err 678 } 679 liveObject, err := t.ObjectTracker.Get(gvr, ns, objMeta.GetName(), metav1.GetOptions{}) 680 if apierrors.IsNotFound(err) { 681 liveObject, err = t.scheme.New(gvk) 682 if err != nil { 683 return err 684 } 685 liveObject.GetObjectKind().SetGroupVersionKind(gvk) 686 } else if err != nil { 687 return err 688 } 689 objWithManagedFields, err := mgr.Update(liveObject, obj, opts.FieldManager) 690 if err != nil { 691 return err 692 } 693 return t.ObjectTracker.Create(gvr, objWithManagedFields, ns, opts) 694 } 695 696 func (t *managedFieldObjectTracker) Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string, vopts ...metav1.UpdateOptions) error { 697 opts, err := assertOptionalSingleArgument(vopts) 698 if err != nil { 699 return err 700 } 701 gvk, err := t.mapper.KindFor(gvr) 702 if err != nil { 703 return err 704 } 705 mgr, err := t.fieldManagerFor(gvk) 706 if err != nil { 707 return err 708 } 709 710 objMeta, err := meta.Accessor(obj) 711 if err != nil { 712 return err 713 } 714 oldObj, err := t.ObjectTracker.Get(gvr, ns, objMeta.GetName(), metav1.GetOptions{}) 715 if err != nil { 716 return err 717 } 718 objWithManagedFields, err := mgr.Update(oldObj, obj, opts.FieldManager) 719 if err != nil { 720 return err 721 } 722 723 return t.ObjectTracker.Update(gvr, objWithManagedFields, ns, opts) 724 } 725 726 func (t *managedFieldObjectTracker) Patch(gvr schema.GroupVersionResource, patchedObject runtime.Object, ns string, vopts ...metav1.PatchOptions) error { 727 opts, err := assertOptionalSingleArgument(vopts) 728 if err != nil { 729 return err 730 } 731 gvk, err := t.mapper.KindFor(gvr) 732 if err != nil { 733 return err 734 } 735 mgr, err := t.fieldManagerFor(gvk) 736 if err != nil { 737 return err 738 } 739 740 objMeta, err := meta.Accessor(patchedObject) 741 if err != nil { 742 return err 743 } 744 oldObj, err := t.ObjectTracker.Get(gvr, ns, objMeta.GetName(), metav1.GetOptions{}) 745 if err != nil { 746 return err 747 } 748 objWithManagedFields, err := mgr.Update(oldObj, patchedObject, opts.FieldManager) 749 if err != nil { 750 return err 751 } 752 return t.ObjectTracker.Patch(gvr, objWithManagedFields, ns, vopts...) 753 } 754 755 func (t *managedFieldObjectTracker) Apply(gvr schema.GroupVersionResource, applyConfiguration runtime.Object, ns string, vopts ...metav1.PatchOptions) error { 756 opts, err := assertOptionalSingleArgument(vopts) 757 if err != nil { 758 return err 759 } 760 gvk, err := t.mapper.KindFor(gvr) 761 if err != nil { 762 return err 763 } 764 applyConfigurationMeta, err := meta.Accessor(applyConfiguration) 765 if err != nil { 766 return err 767 } 768 769 exists := true 770 liveObject, err := t.ObjectTracker.Get(gvr, ns, applyConfigurationMeta.GetName(), metav1.GetOptions{}) 771 if apierrors.IsNotFound(err) { 772 exists = false 773 liveObject, err = t.scheme.New(gvk) 774 if err != nil { 775 return err 776 } 777 liveObject.GetObjectKind().SetGroupVersionKind(gvk) 778 } else if err != nil { 779 return err 780 } 781 mgr, err := t.fieldManagerFor(gvk) 782 if err != nil { 783 return err 784 } 785 force := false 786 if opts.Force != nil { 787 force = *opts.Force 788 } 789 objWithManagedFields, err := mgr.Apply(liveObject, applyConfiguration, opts.FieldManager, force) 790 if err != nil { 791 return err 792 } 793 794 if !exists { 795 return t.ObjectTracker.Create(gvr, objWithManagedFields, ns, metav1.CreateOptions{ 796 DryRun: opts.DryRun, 797 FieldManager: opts.FieldManager, 798 FieldValidation: opts.FieldValidation, 799 }) 800 } else { 801 return t.ObjectTracker.Update(gvr, objWithManagedFields, ns, metav1.UpdateOptions{ 802 DryRun: opts.DryRun, 803 FieldManager: opts.FieldManager, 804 FieldValidation: opts.FieldValidation, 805 }) 806 } 807 } 808 809 func (t *managedFieldObjectTracker) fieldManagerFor(gvk schema.GroupVersionKind) (*managedfields.FieldManager, error) { 810 return managedfields.NewDefaultFieldManager( 811 t.typeConverter, 812 t.objectConverter, 813 &objectDefaulter{}, 814 t.scheme, 815 gvk, 816 gvk.GroupVersion(), 817 "", 818 nil) 819 } 820 821 // objectDefaulter implements runtime.Defaulter, but it actually 822 // does nothing. 823 type objectDefaulter struct{} 824 825 func (d *objectDefaulter) Default(_ runtime.Object) {} 826 827 // filterByNamespace returns all objects in the collection that 828 // match provided namespace. Empty namespace matches 829 // non-namespaced objects. 830 func filterByNamespace(objs map[types.NamespacedName]runtime.Object, ns string) ([]runtime.Object, error) { 831 var res []runtime.Object 832 833 for _, obj := range objs { 834 acc, err := meta.Accessor(obj) 835 if err != nil { 836 return nil, err 837 } 838 if ns != "" && acc.GetNamespace() != ns { 839 continue 840 } 841 res = append(res, obj) 842 } 843 844 // Sort res to get deterministic order. 845 sort.Slice(res, func(i, j int) bool { 846 acc1, _ := meta.Accessor(res[i]) 847 acc2, _ := meta.Accessor(res[j]) 848 if acc1.GetNamespace() != acc2.GetNamespace() { 849 return acc1.GetNamespace() < acc2.GetNamespace() 850 } 851 return acc1.GetName() < acc2.GetName() 852 }) 853 return res, nil 854 } 855 856 func DefaultWatchReactor(watchInterface watch.Interface, err error) WatchReactionFunc { 857 return func(action Action) (bool, watch.Interface, error) { 858 return true, watchInterface, err 859 } 860 } 861 862 // SimpleReactor is a Reactor. Each reaction function is attached to a given verb,resource tuple. "*" in either field matches everything for that value. 863 // For instance, *,pods matches all verbs on pods. This allows for easier composition of reaction functions 864 type SimpleReactor struct { 865 Verb string 866 Resource string 867 868 Reaction ReactionFunc 869 } 870 871 func (r *SimpleReactor) Handles(action Action) bool { 872 verbCovers := r.Verb == "*" || r.Verb == action.GetVerb() 873 if !verbCovers { 874 return false 875 } 876 877 return resourceCovers(r.Resource, action) 878 } 879 880 func (r *SimpleReactor) React(action Action) (bool, runtime.Object, error) { 881 return r.Reaction(action) 882 } 883 884 // SimpleWatchReactor is a WatchReactor. Each reaction function is attached to a given resource. "*" matches everything for that value. 885 // For instance, *,pods matches all verbs on pods. This allows for easier composition of reaction functions 886 type SimpleWatchReactor struct { 887 Resource string 888 889 Reaction WatchReactionFunc 890 } 891 892 func (r *SimpleWatchReactor) Handles(action Action) bool { 893 return resourceCovers(r.Resource, action) 894 } 895 896 func (r *SimpleWatchReactor) React(action Action) (bool, watch.Interface, error) { 897 return r.Reaction(action) 898 } 899 900 // SimpleProxyReactor is a ProxyReactor. Each reaction function is attached to a given resource. "*" matches everything for that value. 901 // For instance, *,pods matches all verbs on pods. This allows for easier composition of reaction functions. 902 type SimpleProxyReactor struct { 903 Resource string 904 905 Reaction ProxyReactionFunc 906 } 907 908 func (r *SimpleProxyReactor) Handles(action Action) bool { 909 return resourceCovers(r.Resource, action) 910 } 911 912 func (r *SimpleProxyReactor) React(action Action) (bool, restclient.ResponseWrapper, error) { 913 return r.Reaction(action) 914 } 915 916 func resourceCovers(resource string, action Action) bool { 917 if resource == "*" { 918 return true 919 } 920 921 if resource == action.GetResource().Resource { 922 return true 923 } 924 925 if index := strings.Index(resource, "/"); index != -1 && 926 resource[:index] == action.GetResource().Resource && 927 resource[index+1:] == action.GetSubresource() { 928 return true 929 } 930 931 return false 932 } 933 934 // assertOptionalSingleArgument returns an error if there is more than one variadic argument. 935 // Otherwise, it returns the first variadic argument, or zero value if there are no arguments. 936 func assertOptionalSingleArgument[T any](arguments []T) (T, error) { 937 var a T 938 switch len(arguments) { 939 case 0: 940 return a, nil 941 case 1: 942 return arguments[0], nil 943 default: 944 return a, fmt.Errorf("expected only one option argument but got %d", len(arguments)) 945 } 946 } 947 948 type TypeResolver interface { 949 Type(openAPIName string) typed.ParseableType 950 } 951 952 type TypeConverter struct { 953 Scheme *runtime.Scheme 954 TypeResolver TypeResolver 955 } 956 957 func (tc TypeConverter) ObjectToTyped(obj runtime.Object, opts ...typed.ValidationOptions) (*typed.TypedValue, error) { 958 gvk := obj.GetObjectKind().GroupVersionKind() 959 name, err := tc.openAPIName(gvk) 960 if err != nil { 961 return nil, err 962 } 963 t := tc.TypeResolver.Type(name) 964 switch o := obj.(type) { 965 case *unstructured.Unstructured: 966 return t.FromUnstructured(o.UnstructuredContent(), opts...) 967 default: 968 return t.FromStructured(obj, opts...) 969 } 970 } 971 972 func (tc TypeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) { 973 vu := value.AsValue().Unstructured() 974 switch o := vu.(type) { 975 case map[string]interface{}: 976 return &unstructured.Unstructured{Object: o}, nil 977 default: 978 return nil, fmt.Errorf("failed to convert value to unstructured for type %T", vu) 979 } 980 } 981 982 func (tc TypeConverter) openAPIName(kind schema.GroupVersionKind) (string, error) { 983 example, err := tc.Scheme.New(kind) 984 if err != nil { 985 return "", err 986 } 987 rtype := reflect.TypeOf(example).Elem() 988 name := friendlyName(rtype.PkgPath() + "." + rtype.Name()) 989 return name, nil 990 } 991 992 // This is a copy of openapi.friendlyName. 993 // TODO: consider introducing a shared version of this function in apimachinery. 994 func friendlyName(name string) string { 995 nameParts := strings.Split(name, "/") 996 // Reverse first part. e.g., io.k8s... instead of k8s.io... 997 if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") { 998 parts := strings.Split(nameParts[0], ".") 999 for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 { 1000 parts[i], parts[j] = parts[j], parts[i] 1001 } 1002 nameParts[0] = strings.Join(parts, ".") 1003 } 1004 return strings.Join(nameParts, ".") 1005 }