@@ -17,13 +17,10 @@ limitations under the License.
1717package fake
1818
1919import (
20- "bytes"
2120 "context"
2221 "errors"
2322 "fmt"
2423 "reflect"
25- "runtime/debug"
26- "strconv"
2724 "strings"
2825 "sync"
2926 "time"
@@ -65,7 +62,6 @@ import (
6562 utilrand "k8s.io/apimachinery/pkg/util/rand"
6663 "k8s.io/apimachinery/pkg/util/sets"
6764 "k8s.io/apimachinery/pkg/util/strategicpatch"
68- "k8s.io/apimachinery/pkg/util/validation/field"
6965 "k8s.io/apimachinery/pkg/watch"
7066 clientgoapplyconfigurations "k8s.io/client-go/applyconfigurations"
7167 "k8s.io/client-go/kubernetes/scheme"
@@ -79,13 +75,6 @@ import (
7975 "sigs.k8s.io/controller-runtime/pkg/internal/objectutil"
8076)
8177
82- type versionedTracker struct {
83- testing.ObjectTracker
84- scheme * runtime.Scheme
85- withStatusSubresource sets.Set [schema.GroupVersionKind ]
86- usesFieldManagedObjectTracker bool
87- }
88-
8978type fakeClient struct {
9079 // trackerWriteLock must be acquired before writing to
9180 // the tracker or performing reads that affect a following
@@ -313,7 +302,7 @@ func (f *ClientBuilder) Build() client.WithWatch {
313302 usesFieldManagedObjectTracker = true
314303 }
315304 tracker := versionedTracker {
316- ObjectTracker : f .objectTracker ,
305+ upstream : f .objectTracker ,
317306 scheme : f .scheme ,
318307 withStatusSubresource : withStatusSubResource ,
319308 usesFieldManagedObjectTracker : usesFieldManagedObjectTracker ,
@@ -354,83 +343,6 @@ func (f *ClientBuilder) Build() client.WithWatch {
354343
355344const trackerAddResourceVersion = "999"
356345
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-
434346// convertFromUnstructuredIfNecessary will convert runtime.Unstructured for a GVK that is recognized
435347// by the schema into the whatever the schema produces with New() for said GVK.
436348// 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
465377 return typed , nil
466378}
467379
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-
613380func (c * fakeClient ) Get (ctx context.Context , key client.ObjectKey , obj client.Object , opts ... client.GetOption ) error {
614381 if err := c .addToSchemeIfUnknownAndUnstructuredOrPartial (obj ); err != nil {
615382 return err
@@ -1233,6 +1000,15 @@ func (c *fakeClient) patch(obj client.Object, patch client.Patch, opts ...client
12331000 reaction := testing .ObjectReaction (c .tracker )
12341001 handled , o , err := reaction (action )
12351002 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+ }
12361012 return err
12371013 }
12381014 if ! handled {
0 commit comments