@@ -35,6 +35,7 @@ import (
3535	"k8s.io/apimachinery/pkg/runtime" 
3636	"k8s.io/apimachinery/pkg/runtime/schema" 
3737	utilrand "k8s.io/apimachinery/pkg/util/rand" 
38+ 	"k8s.io/apimachinery/pkg/util/sets" 
3839	"k8s.io/apimachinery/pkg/util/validation/field" 
3940	"k8s.io/apimachinery/pkg/watch" 
4041	"k8s.io/client-go/kubernetes/scheme" 
@@ -48,13 +49,15 @@ import (
4849
4950type  versionedTracker  struct  {
5051	testing.ObjectTracker 
51- 	scheme  * runtime.Scheme 
52+ 	scheme                 * runtime.Scheme 
53+ 	withStatusSubresource  sets.Set [schema.GroupVersionKind ]
5254}
5355
5456type  fakeClient  struct  {
55- 	tracker     versionedTracker 
56- 	scheme      * runtime.Scheme 
57- 	restMapper  meta.RESTMapper 
57+ 	tracker                versionedTracker 
58+ 	scheme                 * runtime.Scheme 
59+ 	restMapper             meta.RESTMapper 
60+ 	withStatusSubresource  sets.Set [schema.GroupVersionKind ]
5861
5962	// indexes maps each GroupVersionKind (GVK) to the indexes registered for that GVK. 
6063	// The inner map maps from index name to IndexerFunc. 
@@ -95,12 +98,13 @@ func NewClientBuilder() *ClientBuilder {
9598
9699// ClientBuilder builds a fake client. 
97100type  ClientBuilder  struct  {
98- 	scheme              * runtime.Scheme 
99- 	restMapper          meta.RESTMapper 
100- 	initObject          []client.Object 
101- 	initLists           []client.ObjectList 
102- 	initRuntimeObjects  []runtime.Object 
103- 	objectTracker       testing.ObjectTracker 
101+ 	scheme                 * runtime.Scheme 
102+ 	restMapper             meta.RESTMapper 
103+ 	initObject             []client.Object 
104+ 	initLists              []client.ObjectList 
105+ 	initRuntimeObjects     []runtime.Object 
106+ 	withStatusSubresource  []client.Object 
107+ 	objectTracker          testing.ObjectTracker 
104108
105109	// indexes maps each GroupVersionKind (GVK) to the indexes registered for that GVK. 
106110	// The inner map maps from index name to IndexerFunc. 
@@ -185,6 +189,13 @@ func (f *ClientBuilder) WithIndex(obj runtime.Object, field string, extractValue
185189	return  f 
186190}
187191
192+ // WithStatusSubresource configures the passed object with a status subresource, which means 
193+ // calls to Update and Patch will not alters its status. 
194+ func  (f  * ClientBuilder ) WithStatusSubresource (o  ... client.Object ) * ClientBuilder  {
195+ 	f .withStatusSubresource  =  append (f .withStatusSubresource , o ... )
196+ 	return  f 
197+ }
198+ 
188199// Build builds and returns a new fake client. 
189200func  (f  * ClientBuilder ) Build () client.WithWatch  {
190201	if  f .scheme  ==  nil  {
@@ -196,10 +207,19 @@ func (f *ClientBuilder) Build() client.WithWatch {
196207
197208	var  tracker  versionedTracker 
198209
210+ 	withStatusSubResource  :=  sets .New (inTreeResourcesWithStatus ()... )
211+ 	for  _ , o  :=  range  f .withStatusSubresource  {
212+ 		gvk , err  :=  apiutil .GVKForObject (o , f .scheme )
213+ 		if  err  !=  nil  {
214+ 			panic (fmt .Errorf ("failed to get gvk for object %T: %w" , withStatusSubResource , err ))
215+ 		}
216+ 		withStatusSubResource .Insert (gvk )
217+ 	}
218+ 
199219	if  f .objectTracker  ==  nil  {
200- 		tracker  =  versionedTracker {ObjectTracker : testing .NewObjectTracker (f .scheme , scheme .Codecs .UniversalDecoder ()), scheme : f .scheme }
220+ 		tracker  =  versionedTracker {ObjectTracker : testing .NewObjectTracker (f .scheme , scheme .Codecs .UniversalDecoder ()), scheme : f .scheme ,  withStatusSubresource :  withStatusSubResource }
201221	} else  {
202- 		tracker  =  versionedTracker {ObjectTracker : f .objectTracker , scheme : f .scheme }
222+ 		tracker  =  versionedTracker {ObjectTracker : f .objectTracker , scheme : f .scheme ,  withStatusSubresource :  withStatusSubResource }
203223	}
204224
205225	for  _ , obj  :=  range  f .initObject  {
@@ -217,11 +237,13 @@ func (f *ClientBuilder) Build() client.WithWatch {
217237			panic (fmt .Errorf ("failed to add runtime object %v to fake client: %w" , obj , err ))
218238		}
219239	}
240+ 
220241	return  & fakeClient {
221- 		tracker :    tracker ,
222- 		scheme :     f .scheme ,
223- 		restMapper : f .restMapper ,
224- 		indexes :    f .indexes ,
242+ 		tracker :               tracker ,
243+ 		scheme :                f .scheme ,
244+ 		restMapper :            f .restMapper ,
245+ 		indexes :               f .indexes ,
246+ 		withStatusSubresource : withStatusSubResource ,
225247	}
226248}
227249
@@ -318,6 +340,10 @@ func convertFromUnstructuredIfNecessary(s *runtime.Scheme, o runtime.Object) (ru
318340}
319341
320342func  (t  versionedTracker ) Update (gvr  schema.GroupVersionResource , obj  runtime.Object , ns  string ) error  {
343+ 	return  t .update (gvr , obj , ns , false )
344+ }
345+ 
346+ func  (t  versionedTracker ) update (gvr  schema.GroupVersionResource , obj  runtime.Object , ns  string , isStatus  bool ) error  {
321347	accessor , err  :=  meta .Accessor (obj )
322348	if  err  !=  nil  {
323349		return  fmt .Errorf ("failed to get accessor for object: %w" , err )
@@ -337,6 +363,9 @@ func (t versionedTracker) Update(gvr schema.GroupVersionResource, obj runtime.Ob
337363			return  err 
338364		}
339365	}
366+ 	if  ! isStatus  &&  t .withStatusSubresource .Has (gvk ) {
367+ 		clearObjectStatus (obj )
368+ 	}
340369
341370	oldObject , err  :=  t .ObjectTracker .Get (gvr , ns , accessor .GetName ())
342371	if  err  !=  nil  {
@@ -689,6 +718,10 @@ func (c *fakeClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ..
689718}
690719
691720func  (c  * fakeClient ) Update (ctx  context.Context , obj  client.Object , opts  ... client.UpdateOption ) error  {
721+ 	return  c .update (obj , false , opts ... )
722+ }
723+ 
724+ func  (c  * fakeClient ) update (obj  client.Object , isStatus  bool , opts  ... client.UpdateOption ) error  {
692725	updateOptions  :=  & client.UpdateOptions {}
693726	updateOptions .ApplyOptions (opts )
694727
@@ -706,10 +739,14 @@ func (c *fakeClient) Update(ctx context.Context, obj client.Object, opts ...clie
706739	if  err  !=  nil  {
707740		return  err 
708741	}
709- 	return  c .tracker .Update (gvr , obj , accessor .GetNamespace ())
742+ 	return  c .tracker .update (gvr , obj , accessor .GetNamespace (),  isStatus )
710743}
711744
712745func  (c  * fakeClient ) Patch (ctx  context.Context , obj  client.Object , patch  client.Patch , opts  ... client.PatchOption ) error  {
746+ 	return  c .patch (obj , patch , false , opts ... )
747+ }
748+ 
749+ func  (c  * fakeClient ) patch (obj  client.Object , patch  client.Patch , isStatus  bool , opts  ... client.PatchOption ) error  {
713750	patchOptions  :=  & client.PatchOptions {}
714751	patchOptions .ApplyOptions (opts )
715752
@@ -732,6 +769,18 @@ func (c *fakeClient) Patch(ctx context.Context, obj client.Object, patch client.
732769		return  err 
733770	}
734771
772+ 	gvk , err  :=  apiutil .GVKForObject (obj , c .scheme )
773+ 	if  err  !=  nil  {
774+ 		return  err 
775+ 	}
776+ 
777+ 	if  ! isStatus  &&  c .withStatusSubresource .Has (gvk ) {
778+ 		data , err  =  clearStatus (data )
779+ 		if  err  !=  nil  {
780+ 			return  fmt .Errorf ("failed to clear status: %w" , err )
781+ 		}
782+ 	}
783+ 
735784	reaction  :=  testing .ObjectReaction (c .tracker )
736785	handled , o , err  :=  reaction (testing .NewPatchAction (gvr , accessor .GetNamespace (), accessor .GetName (), patch .Type (), data ))
737786	if  err  !=  nil  {
@@ -741,10 +790,6 @@ func (c *fakeClient) Patch(ctx context.Context, obj client.Object, patch client.
741790		panic ("tracker could not handle patch method" )
742791	}
743792
744- 	gvk , err  :=  apiutil .GVKForObject (obj , c .scheme )
745- 	if  err  !=  nil  {
746- 		return  err 
747- 	}
748793	ta , err  :=  meta .TypeAccessor (o )
749794	if  err  !=  nil  {
750795		return  err 
@@ -762,6 +807,34 @@ func (c *fakeClient) Patch(ctx context.Context, obj client.Object, patch client.
762807	return  err 
763808}
764809
810+ func  clearObjectStatus (o  runtime.Object ) {
811+ 	if  unstructured , isUnstructured  :=  o .(* unstructured.Unstructured ); isUnstructured  {
812+ 		delete (unstructured .Object , "status" )
813+ 		return 
814+ 	}
815+ 	tp  :=  reflect .TypeOf (o )
816+ 	value  :=  reflect .ValueOf (o )
817+ 	if  tp .Kind () ==  reflect .Pointer  {
818+ 		tp  =  tp .Elem ()
819+ 		value  =  value .Elem ()
820+ 	}
821+ 	statusType , found  :=  tp .FieldByName ("Status" )
822+ 	if  ! found  {
823+ 		return 
824+ 	}
825+ 	value .FieldByName ("Status" ).Set (reflect .New (statusType .Type ).Elem ())
826+ }
827+ 
828+ func  clearStatus (in  []byte ) ([]byte , error ) {
829+ 	m  :=  map [string ]any {}
830+ 	if  err  :=  json .Unmarshal (in , & m ); err  !=  nil  {
831+ 		return  nil , err 
832+ 	}
833+ 	delete (m , "status" )
834+ 
835+ 	return  json .Marshal (m )
836+ }
837+ 
765838func  (c  * fakeClient ) Status () client.SubResourceWriter  {
766839	return  c .SubResource ("status" )
767840}
@@ -818,7 +891,7 @@ func (sw *fakeSubResourceClient) Update(ctx context.Context, obj client.Object,
818891	if  updateOptions .SubResourceBody  !=  nil  {
819892		body  =  updateOptions .SubResourceBody 
820893	}
821- 	return  sw .client .Update ( ctx ,  body , & updateOptions .UpdateOptions )
894+ 	return  sw .client .update ( body ,  true , & updateOptions .UpdateOptions )
822895}
823896
824897func  (sw  * fakeSubResourceClient ) Patch (ctx  context.Context , obj  client.Object , patch  client.Patch , opts  ... client.SubResourcePatchOption ) error  {
@@ -833,7 +906,7 @@ func (sw *fakeSubResourceClient) Patch(ctx context.Context, obj client.Object, p
833906		body  =  patchOptions .SubResourceBody 
834907	}
835908
836- 	return  sw .client .Patch ( ctx ,  body , patch , & patchOptions .PatchOptions )
909+ 	return  sw .client .patch ( body , patch ,  true , & patchOptions .PatchOptions )
837910}
838911
839912func  allowsUnconditionalUpdate (gvk  schema.GroupVersionKind ) bool  {
@@ -933,6 +1006,42 @@ func allowsCreateOnUpdate(gvk schema.GroupVersionKind) bool {
9331006	return  false 
9341007}
9351008
1009+ func  inTreeResourcesWithStatus () []schema.GroupVersionKind  {
1010+ 	return  []schema.GroupVersionKind {
1011+ 		{Version : "v1" , Kind : "Namespace" },
1012+ 		{Version : "v1" , Kind : "Node" },
1013+ 		{Version : "v1" , Kind : "PersistentVolumeClaim" },
1014+ 		{Version : "v1" , Kind : "PersistentVolume" },
1015+ 		{Version : "v1" , Kind : "Pod" },
1016+ 		{Version : "v1" , Kind : "ReplicationController" },
1017+ 		{Version : "v1" , Kind : "Service" },
1018+ 
1019+ 		{Group : "apps" , Version : "v1" , Kind : "Deployment" },
1020+ 		{Group : "apps" , Version : "v1" , Kind : "DaemonSet" },
1021+ 		{Group : "apps" , Version : "v1" , Kind : "ReplicaSet" },
1022+ 		{Group : "apps" , Version : "v1" , Kind : "StatefulSet" },
1023+ 
1024+ 		{Group : "autoscaling" , Version : "v1" , Kind : "HorizontalPodAutoscaler" },
1025+ 
1026+ 		{Group : "batch" , Version : "v1" , Kind : "CronJob" },
1027+ 		{Group : "batch" , Version : "v1" , Kind : "Job" },
1028+ 
1029+ 		{Group : "certificates.k8s.io" , Version : "v1" , Kind : "CertificateSigningRequest" },
1030+ 
1031+ 		{Group : "networking.k8s.io" , Version : "v1" , Kind : "Ingress" },
1032+ 		{Group : "networking.k8s.io" , Version : "v1" , Kind : "NetworkPolicy" },
1033+ 
1034+ 		{Group : "policy" , Version : "v1" , Kind : "PodDisruptionBudget" },
1035+ 
1036+ 		{Group : "storage.k8s.io" , Version : "v1" , Kind : "VolumeAttachment" },
1037+ 
1038+ 		{Group : "apiextensions.k8s.io" , Version : "v1" , Kind : "CustomResourceDefinition" },
1039+ 
1040+ 		{Group : "flowcontrol.apiserver.k8s.io" , Version : "v1beta2" , Kind : "FlowSchema" },
1041+ 		{Group : "flowcontrol.apiserver.k8s.io" , Version : "v1beta2" , Kind : "PriorityLevelConfiguration" },
1042+ 	}
1043+ }
1044+ 
9361045// zero zeros the value of a pointer. 
9371046func  zero (x  interface {}) {
9381047	if  x  ==  nil  {
0 commit comments