@@ -68,16 +68,21 @@ type versionedTracker struct {
6868}
6969
7070type fakeClient struct {
71- tracker versionedTracker
72- scheme * runtime.Scheme
71+ // trackerWriteLock must be acquired before writing to
72+ // the tracker or performing reads that affect a following
73+ // write.
74+ trackerWriteLock sync.Mutex
75+ tracker versionedTracker
76+
77+ schemeWriteLock sync.Mutex
78+ scheme * runtime.Scheme
79+
7380 restMapper meta.RESTMapper
7481 withStatusSubresource sets.Set [schema.GroupVersionKind ]
7582
7683 // indexes maps each GroupVersionKind (GVK) to the indexes registered for that GVK.
7784 // The inner map maps from index name to IndexerFunc.
7885 indexes map [schema.GroupVersionKind ]map [string ]client.IndexerFunc
79-
80- schemeWriteLock sync.Mutex
8186}
8287
8388var _ client.WithWatch = & fakeClient {}
@@ -467,6 +472,11 @@ func (t versionedTracker) updateObject(gvr schema.GroupVersionResource, obj runt
467472 switch {
468473 case allowsUnconditionalUpdate (gvk ):
469474 accessor .SetResourceVersion (oldAccessor .GetResourceVersion ())
475+ // This is needed because if the patch explicitly sets the RV to null, the client-go reaction we use
476+ // to apply it and whose output we process here will have it unset. It is not clear why the Kubernetes
477+ // apiserver accepts such a patch, but it does so we just copy that behavior.
478+ // Kubernetes apiserver behavior can be checked like this:
479+ // `kubectl patch configmap foo --patch '{"metadata":{"annotations":{"foo":"bar"},"resourceVersion":null}}' -v=9`
470480 case bytes .
471481 Contains (debug .Stack (), []byte ("sigs.k8s.io/controller-runtime/pkg/client/fake.(*fakeClient).Patch" )):
472482 // We apply patches using a client-go reaction that ends up calling the trackers Update. As we can't change
@@ -732,6 +742,8 @@ func (c *fakeClient) Create(ctx context.Context, obj client.Object, opts ...clie
732742 accessor .SetDeletionTimestamp (nil )
733743 }
734744
745+ c .trackerWriteLock .Lock ()
746+ defer c .trackerWriteLock .Unlock ()
735747 return c .tracker .Create (gvr , obj , accessor .GetNamespace ())
736748}
737749
@@ -753,6 +765,8 @@ func (c *fakeClient) Delete(ctx context.Context, obj client.Object, opts ...clie
753765 }
754766 }
755767
768+ c .trackerWriteLock .Lock ()
769+ defer c .trackerWriteLock .Unlock ()
756770 // Check the ResourceVersion if that Precondition was specified.
757771 if delOptions .Preconditions != nil && delOptions .Preconditions .ResourceVersion != nil {
758772 name := accessor .GetName ()
@@ -775,7 +789,7 @@ func (c *fakeClient) Delete(ctx context.Context, obj client.Object, opts ...clie
775789 }
776790 }
777791
778- return c .deleteObject (gvr , accessor )
792+ return c .deleteObjectLocked (gvr , accessor )
779793}
780794
781795func (c * fakeClient ) DeleteAllOf (ctx context.Context , obj client.Object , opts ... client.DeleteAllOfOption ) error {
@@ -793,6 +807,9 @@ func (c *fakeClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ..
793807 }
794808 }
795809
810+ c .trackerWriteLock .Lock ()
811+ defer c .trackerWriteLock .Unlock ()
812+
796813 gvr , _ := meta .UnsafeGuessKindToResource (gvk )
797814 o , err := c .tracker .List (gvr , gvk , dcOptions .Namespace )
798815 if err != nil {
@@ -812,7 +829,7 @@ func (c *fakeClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ..
812829 if err != nil {
813830 return err
814831 }
815- err = c .deleteObject (gvr , accessor )
832+ err = c .deleteObjectLocked (gvr , accessor )
816833 if err != nil {
817834 return err
818835 }
@@ -842,6 +859,9 @@ func (c *fakeClient) update(obj client.Object, isStatus bool, opts ...client.Upd
842859 if err != nil {
843860 return err
844861 }
862+
863+ c .trackerWriteLock .Lock ()
864+ defer c .trackerWriteLock .Unlock ()
845865 return c .tracker .update (gvr , obj , accessor .GetNamespace (), isStatus , false , * updateOptions .AsUpdateOptions ())
846866}
847867
@@ -877,6 +897,8 @@ func (c *fakeClient) patch(obj client.Object, patch client.Patch, opts ...client
877897 return err
878898 }
879899
900+ c .trackerWriteLock .Lock ()
901+ defer c .trackerWriteLock .Unlock ()
880902 oldObj , err := c .tracker .Get (gvr , accessor .GetNamespace (), accessor .GetName ())
881903 if err != nil {
882904 return err
@@ -1085,7 +1107,7 @@ func (c *fakeClient) SubResource(subResource string) client.SubResourceClient {
10851107 return & fakeSubResourceClient {client : c , subResource : subResource }
10861108}
10871109
1088- func (c * fakeClient ) deleteObject (gvr schema.GroupVersionResource , accessor metav1.Object ) error {
1110+ func (c * fakeClient ) deleteObjectLocked (gvr schema.GroupVersionResource , accessor metav1.Object ) error {
10891111 old , err := c .tracker .Get (gvr , accessor .GetNamespace (), accessor .GetName ())
10901112 if err == nil {
10911113 oldAccessor , err := meta .Accessor (old )
@@ -1167,7 +1189,7 @@ func (sw *fakeSubResourceClient) Update(ctx context.Context, obj client.Object,
11671189
11681190 switch sw .subResource {
11691191 case subResourceScale :
1170- if err := sw .client .Get (ctx , client .ObjectKeyFromObject (obj ), obj ); err != nil {
1192+ if err := sw .client .Get (ctx , client .ObjectKeyFromObject (obj ), obj . DeepCopyObject ().(client. Object ) ); err != nil {
11711193 return err
11721194 }
11731195 if updateOptions .SubResourceBody == nil {
0 commit comments