@@ -17,13 +17,10 @@ limitations under the License.
17
17
package fake
18
18
19
19
import (
20
- "bytes"
21
20
"context"
22
21
"errors"
23
22
"fmt"
24
23
"reflect"
25
- "runtime/debug"
26
- "strconv"
27
24
"strings"
28
25
"sync"
29
26
"time"
@@ -65,7 +62,6 @@ import (
65
62
utilrand "k8s.io/apimachinery/pkg/util/rand"
66
63
"k8s.io/apimachinery/pkg/util/sets"
67
64
"k8s.io/apimachinery/pkg/util/strategicpatch"
68
- "k8s.io/apimachinery/pkg/util/validation/field"
69
65
"k8s.io/apimachinery/pkg/watch"
70
66
clientgoapplyconfigurations "k8s.io/client-go/applyconfigurations"
71
67
"k8s.io/client-go/kubernetes/scheme"
@@ -79,13 +75,6 @@ import (
79
75
"sigs.k8s.io/controller-runtime/pkg/internal/objectutil"
80
76
)
81
77
82
- type versionedTracker struct {
83
- testing.ObjectTracker
84
- scheme * runtime.Scheme
85
- withStatusSubresource sets.Set [schema.GroupVersionKind ]
86
- usesFieldManagedObjectTracker bool
87
- }
88
-
89
78
type fakeClient struct {
90
79
// trackerWriteLock must be acquired before writing to
91
80
// the tracker or performing reads that affect a following
@@ -313,7 +302,7 @@ func (f *ClientBuilder) Build() client.WithWatch {
313
302
usesFieldManagedObjectTracker = true
314
303
}
315
304
tracker := versionedTracker {
316
- ObjectTracker : f .objectTracker ,
305
+ upstream : f .objectTracker ,
317
306
scheme : f .scheme ,
318
307
withStatusSubresource : withStatusSubResource ,
319
308
usesFieldManagedObjectTracker : usesFieldManagedObjectTracker ,
@@ -354,83 +343,6 @@ func (f *ClientBuilder) Build() client.WithWatch {
354
343
355
344
const trackerAddResourceVersion = "999"
356
345
357
- func (t versionedTracker ) Add (obj runtime.Object ) error {
358
- var objects []runtime.Object
359
- if meta .IsListType (obj ) {
360
- var err error
361
- objects , err = meta .ExtractList (obj )
362
- if err != nil {
363
- return err
364
- }
365
- } else {
366
- objects = []runtime.Object {obj }
367
- }
368
- for _ , obj := range objects {
369
- accessor , err := meta .Accessor (obj )
370
- if err != nil {
371
- return fmt .Errorf ("failed to get accessor for object: %w" , err )
372
- }
373
- if accessor .GetDeletionTimestamp () != nil && len (accessor .GetFinalizers ()) == 0 {
374
- return fmt .Errorf ("refusing to create obj %s with metadata.deletionTimestamp but no finalizers" , accessor .GetName ())
375
- }
376
- if accessor .GetResourceVersion () == "" {
377
- // We use a "magic" value of 999 here because this field
378
- // is parsed as uint and and 0 is already used in Update.
379
- // As we can't go lower, go very high instead so this can
380
- // be recognized
381
- accessor .SetResourceVersion (trackerAddResourceVersion )
382
- }
383
-
384
- obj , err = convertFromUnstructuredIfNecessary (t .scheme , obj )
385
- if err != nil {
386
- return err
387
- }
388
-
389
- // If the fieldManager can not decode fields, it will just silently clear them. This is pretty
390
- // much guaranteed not to be what someone that initializes a fake client with objects that
391
- // have them set wants, so validate them here.
392
- // Ref https://github.com/kubernetes/kubernetes/blob/a956ef4862993b825bcd524a19260192ff1da72d/staging/src/k8s.io/apimachinery/pkg/util/managedfields/internal/fieldmanager.go#L105
393
- if t .usesFieldManagedObjectTracker {
394
- if err := managedfields .ValidateManagedFields (accessor .GetManagedFields ()); err != nil {
395
- return fmt .Errorf ("invalid managedFields on %T: %w" , obj , err )
396
- }
397
- }
398
- if err := t .ObjectTracker .Add (obj ); err != nil {
399
- return err
400
- }
401
- }
402
-
403
- return nil
404
- }
405
-
406
- func (t versionedTracker ) Create (gvr schema.GroupVersionResource , obj runtime.Object , ns string , opts ... metav1.CreateOptions ) error {
407
- accessor , err := meta .Accessor (obj )
408
- if err != nil {
409
- return fmt .Errorf ("failed to get accessor for object: %w" , err )
410
- }
411
- if accessor .GetName () == "" {
412
- gvk , _ := apiutil .GVKForObject (obj , t .scheme )
413
- return apierrors .NewInvalid (
414
- gvk .GroupKind (),
415
- accessor .GetName (),
416
- field.ErrorList {field .Required (field .NewPath ("metadata.name" ), "name is required" )})
417
- }
418
- if accessor .GetResourceVersion () != "" {
419
- return apierrors .NewBadRequest ("resourceVersion can not be set for Create requests" )
420
- }
421
- accessor .SetResourceVersion ("1" )
422
- obj , err = convertFromUnstructuredIfNecessary (t .scheme , obj )
423
- if err != nil {
424
- return err
425
- }
426
- if err := t .ObjectTracker .Create (gvr , obj , ns , opts ... ); err != nil {
427
- accessor .SetResourceVersion ("" )
428
- return err
429
- }
430
-
431
- return nil
432
- }
433
-
434
346
// convertFromUnstructuredIfNecessary will convert runtime.Unstructured for a GVK that is recognized
435
347
// by the schema into the whatever the schema produces with New() for said GVK.
436
348
// This is required because the tracker unconditionally saves on manipulations, but its List() implementation
@@ -465,151 +377,6 @@ func convertFromUnstructuredIfNecessary(s *runtime.Scheme, o runtime.Object) (ru
465
377
return typed , nil
466
378
}
467
379
468
- func (t versionedTracker ) Update (gvr schema.GroupVersionResource , obj runtime.Object , ns string , opts ... metav1.UpdateOptions ) error {
469
- updateOpts , err := getSingleOrZeroOptions (opts )
470
- if err != nil {
471
- return err
472
- }
473
-
474
- return t .update (gvr , obj , ns , false , false , updateOpts )
475
- }
476
-
477
- func (t versionedTracker ) update (gvr schema.GroupVersionResource , obj runtime.Object , ns string , isStatus , deleting bool , opts metav1.UpdateOptions ) error {
478
- gvk , err := apiutil .GVKForObject (obj , t .scheme )
479
- if err != nil {
480
- return err
481
- }
482
- obj , err = t .updateObject (gvr , obj , ns , isStatus , deleting , opts .DryRun )
483
- if err != nil {
484
- return err
485
- }
486
- if obj == nil {
487
- return nil
488
- }
489
-
490
- if u , unstructured := obj .(* unstructured.Unstructured ); unstructured {
491
- u .SetGroupVersionKind (gvk )
492
- }
493
-
494
- return t .ObjectTracker .Update (gvr , obj , ns , opts )
495
- }
496
-
497
- func (t versionedTracker ) Patch (gvr schema.GroupVersionResource , obj runtime.Object , ns string , opts ... metav1.PatchOptions ) error {
498
- patchOptions , err := getSingleOrZeroOptions (opts )
499
- if err != nil {
500
- return err
501
- }
502
-
503
- // We apply patches using a client-go reaction that ends up calling the trackers Patch. As we can't change
504
- // that reaction, we use the callstack to figure out if this originated from the status client.
505
- isStatus := bytes .Contains (debug .Stack (), []byte ("sigs.k8s.io/controller-runtime/pkg/client/fake.(*fakeSubResourceClient).statusPatch" ))
506
-
507
- obj , err = t .updateObject (gvr , obj , ns , isStatus , false , patchOptions .DryRun )
508
- if err != nil {
509
- return err
510
- }
511
- if obj == nil {
512
- return nil
513
- }
514
-
515
- return t .ObjectTracker .Patch (gvr , obj , ns , patchOptions )
516
- }
517
-
518
- func (t versionedTracker ) updateObject (gvr schema.GroupVersionResource , obj runtime.Object , ns string , isStatus , deleting bool , dryRun []string ) (runtime.Object , error ) {
519
- accessor , err := meta .Accessor (obj )
520
- if err != nil {
521
- return nil , fmt .Errorf ("failed to get accessor for object: %w" , err )
522
- }
523
-
524
- if accessor .GetName () == "" {
525
- gvk , _ := apiutil .GVKForObject (obj , t .scheme )
526
- return nil , apierrors .NewInvalid (
527
- gvk .GroupKind (),
528
- accessor .GetName (),
529
- field.ErrorList {field .Required (field .NewPath ("metadata.name" ), "name is required" )})
530
- }
531
-
532
- gvk , err := apiutil .GVKForObject (obj , t .scheme )
533
- if err != nil {
534
- return nil , err
535
- }
536
-
537
- oldObject , err := t .ObjectTracker .Get (gvr , ns , accessor .GetName ())
538
- if err != nil {
539
- // If the resource is not found and the resource allows create on update, issue a
540
- // create instead.
541
- if apierrors .IsNotFound (err ) && allowsCreateOnUpdate (gvk ) {
542
- return nil , t .Create (gvr , obj , ns )
543
- }
544
- return nil , err
545
- }
546
-
547
- if t .withStatusSubresource .Has (gvk ) {
548
- if isStatus { // copy everything but status and metadata.ResourceVersion from original object
549
- if err := copyStatusFrom (obj , oldObject ); err != nil {
550
- return nil , fmt .Errorf ("failed to copy non-status field for object with status subresouce: %w" , err )
551
- }
552
- passedRV := accessor .GetResourceVersion ()
553
- if err := copyFrom (oldObject , obj ); err != nil {
554
- return nil , fmt .Errorf ("failed to restore non-status fields: %w" , err )
555
- }
556
- accessor .SetResourceVersion (passedRV )
557
- } else { // copy status from original object
558
- if err := copyStatusFrom (oldObject , obj ); err != nil {
559
- return nil , fmt .Errorf ("failed to copy the status for object with status subresource: %w" , err )
560
- }
561
- }
562
- } else if isStatus {
563
- return nil , apierrors .NewNotFound (gvr .GroupResource (), accessor .GetName ())
564
- }
565
-
566
- oldAccessor , err := meta .Accessor (oldObject )
567
- if err != nil {
568
- return nil , err
569
- }
570
-
571
- // If the new object does not have the resource version set and it allows unconditional update,
572
- // default it to the resource version of the existing resource
573
- if accessor .GetResourceVersion () == "" {
574
- switch {
575
- case allowsUnconditionalUpdate (gvk ):
576
- accessor .SetResourceVersion (oldAccessor .GetResourceVersion ())
577
- // This is needed because if the patch explicitly sets the RV to null, the client-go reaction we use
578
- // to apply it and whose output we process here will have it unset. It is not clear why the Kubernetes
579
- // apiserver accepts such a patch, but it does so we just copy that behavior.
580
- // Kubernetes apiserver behavior can be checked like this:
581
- // `kubectl patch configmap foo --patch '{"metadata":{"annotations":{"foo":"bar"},"resourceVersion":null}}' -v=9`
582
- case bytes .
583
- Contains (debug .Stack (), []byte ("sigs.k8s.io/controller-runtime/pkg/client/fake.(*fakeClient).Patch" )):
584
- // We apply patches using a client-go reaction that ends up calling the trackers Update. As we can't change
585
- // that reaction, we use the callstack to figure out if this originated from the "fakeClient.Patch" func.
586
- accessor .SetResourceVersion (oldAccessor .GetResourceVersion ())
587
- }
588
- }
589
-
590
- if accessor .GetResourceVersion () != oldAccessor .GetResourceVersion () {
591
- return nil , apierrors .NewConflict (gvr .GroupResource (), accessor .GetName (), errors .New ("object was modified" ))
592
- }
593
- if oldAccessor .GetResourceVersion () == "" {
594
- oldAccessor .SetResourceVersion ("0" )
595
- }
596
- intResourceVersion , err := strconv .ParseUint (oldAccessor .GetResourceVersion (), 10 , 64 )
597
- if err != nil {
598
- return nil , fmt .Errorf ("can not convert resourceVersion %q to int: %w" , oldAccessor .GetResourceVersion (), err )
599
- }
600
- intResourceVersion ++
601
- accessor .SetResourceVersion (strconv .FormatUint (intResourceVersion , 10 ))
602
-
603
- if ! deleting && ! deletionTimestampEqual (accessor , oldAccessor ) {
604
- return nil , fmt .Errorf ("error: Unable to edit %s: metadata.deletionTimestamp field is immutable" , accessor .GetName ())
605
- }
606
-
607
- if ! accessor .GetDeletionTimestamp ().IsZero () && len (accessor .GetFinalizers ()) == 0 {
608
- return nil , t .ObjectTracker .Delete (gvr , accessor .GetNamespace (), accessor .GetName (), metav1.DeleteOptions {DryRun : dryRun })
609
- }
610
- return convertFromUnstructuredIfNecessary (t .scheme , obj )
611
- }
612
-
613
380
func (c * fakeClient ) Get (ctx context.Context , key client.ObjectKey , obj client.Object , opts ... client.GetOption ) error {
614
381
if err := c .addToSchemeIfUnknownAndUnstructuredOrPartial (obj ); err != nil {
615
382
return err
@@ -1233,6 +1000,15 @@ func (c *fakeClient) patch(obj client.Object, patch client.Patch, opts ...client
1233
1000
reaction := testing .ObjectReaction (c .tracker )
1234
1001
handled , o , err := reaction (action )
1235
1002
if err != nil {
1003
+ // The reaction calls tracker.Get after tracker.Apply to return the object,
1004
+ // but we may have deleted it in tracker.Apply if there was no finalizer
1005
+ // left.
1006
+ if apierrors .IsNotFound (err ) &&
1007
+ patch .Type () == types .ApplyPatchType &&
1008
+ oldAccessor .GetDeletionTimestamp () != nil &&
1009
+ len (obj .GetFinalizers ()) == 0 {
1010
+ return nil
1011
+ }
1236
1012
return err
1237
1013
}
1238
1014
if ! handled {
0 commit comments