@@ -35,6 +35,7 @@ import (
35
35
"k8s.io/apimachinery/pkg/runtime"
36
36
"k8s.io/apimachinery/pkg/runtime/schema"
37
37
utilrand "k8s.io/apimachinery/pkg/util/rand"
38
+ "k8s.io/apimachinery/pkg/util/sets"
38
39
"k8s.io/apimachinery/pkg/util/validation/field"
39
40
"k8s.io/apimachinery/pkg/watch"
40
41
"k8s.io/client-go/kubernetes/scheme"
@@ -48,13 +49,15 @@ import (
48
49
49
50
type versionedTracker struct {
50
51
testing.ObjectTracker
51
- scheme * runtime.Scheme
52
+ scheme * runtime.Scheme
53
+ withStatusSubresource sets.Set [schema.GroupVersionKind ]
52
54
}
53
55
54
56
type 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 ]
58
61
59
62
// indexes maps each GroupVersionKind (GVK) to the indexes registered for that GVK.
60
63
// The inner map maps from index name to IndexerFunc.
@@ -95,12 +98,13 @@ func NewClientBuilder() *ClientBuilder {
95
98
96
99
// ClientBuilder builds a fake client.
97
100
type 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
104
108
105
109
// indexes maps each GroupVersionKind (GVK) to the indexes registered for that GVK.
106
110
// The inner map maps from index name to IndexerFunc.
@@ -185,6 +189,13 @@ func (f *ClientBuilder) WithIndex(obj runtime.Object, field string, extractValue
185
189
return f
186
190
}
187
191
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
+
188
199
// Build builds and returns a new fake client.
189
200
func (f * ClientBuilder ) Build () client.WithWatch {
190
201
if f .scheme == nil {
@@ -196,10 +207,19 @@ func (f *ClientBuilder) Build() client.WithWatch {
196
207
197
208
var tracker versionedTracker
198
209
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
+
199
219
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 }
201
221
} else {
202
- tracker = versionedTracker {ObjectTracker : f .objectTracker , scheme : f .scheme }
222
+ tracker = versionedTracker {ObjectTracker : f .objectTracker , scheme : f .scheme , withStatusSubresource : withStatusSubResource }
203
223
}
204
224
205
225
for _ , obj := range f .initObject {
@@ -217,11 +237,13 @@ func (f *ClientBuilder) Build() client.WithWatch {
217
237
panic (fmt .Errorf ("failed to add runtime object %v to fake client: %w" , obj , err ))
218
238
}
219
239
}
240
+
220
241
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 ,
225
247
}
226
248
}
227
249
@@ -318,6 +340,10 @@ func convertFromUnstructuredIfNecessary(s *runtime.Scheme, o runtime.Object) (ru
318
340
}
319
341
320
342
func (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 {
321
347
accessor , err := meta .Accessor (obj )
322
348
if err != nil {
323
349
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
337
363
return err
338
364
}
339
365
}
366
+ if ! isStatus && t .withStatusSubresource .Has (gvk ) {
367
+ clearObjectStatus (obj )
368
+ }
340
369
341
370
oldObject , err := t .ObjectTracker .Get (gvr , ns , accessor .GetName ())
342
371
if err != nil {
@@ -689,6 +718,10 @@ func (c *fakeClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ..
689
718
}
690
719
691
720
func (c * fakeClient ) Update (ctx context.Context , obj client.Object , opts ... client.UpdateOption ) error {
721
+ return c .update (ctx , obj , false , opts ... )
722
+ }
723
+
724
+ func (c * fakeClient ) update (ctx context.Context , obj client.Object , isStatus bool , opts ... client.UpdateOption ) error {
692
725
updateOptions := & client.UpdateOptions {}
693
726
updateOptions .ApplyOptions (opts )
694
727
@@ -706,10 +739,14 @@ func (c *fakeClient) Update(ctx context.Context, obj client.Object, opts ...clie
706
739
if err != nil {
707
740
return err
708
741
}
709
- return c .tracker .Update (gvr , obj , accessor .GetNamespace ())
742
+ return c .tracker .update (gvr , obj , accessor .GetNamespace (), isStatus )
710
743
}
711
744
712
745
func (c * fakeClient ) Patch (ctx context.Context , obj client.Object , patch client.Patch , opts ... client.PatchOption ) error {
746
+ return c .patch (ctx , obj , patch , false , opts ... )
747
+ }
748
+
749
+ func (c * fakeClient ) patch (ctx context.Context , obj client.Object , patch client.Patch , isStatus bool , opts ... client.PatchOption ) error {
713
750
patchOptions := & client.PatchOptions {}
714
751
patchOptions .ApplyOptions (opts )
715
752
@@ -732,6 +769,18 @@ func (c *fakeClient) Patch(ctx context.Context, obj client.Object, patch client.
732
769
return err
733
770
}
734
771
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
+
735
784
reaction := testing .ObjectReaction (c .tracker )
736
785
handled , o , err := reaction (testing .NewPatchAction (gvr , accessor .GetNamespace (), accessor .GetName (), patch .Type (), data ))
737
786
if err != nil {
@@ -741,10 +790,6 @@ func (c *fakeClient) Patch(ctx context.Context, obj client.Object, patch client.
741
790
panic ("tracker could not handle patch method" )
742
791
}
743
792
744
- gvk , err := apiutil .GVKForObject (obj , c .scheme )
745
- if err != nil {
746
- return err
747
- }
748
793
ta , err := meta .TypeAccessor (o )
749
794
if err != nil {
750
795
return err
@@ -762,6 +807,34 @@ func (c *fakeClient) Patch(ctx context.Context, obj client.Object, patch client.
762
807
return err
763
808
}
764
809
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
+
765
838
func (c * fakeClient ) Status () client.SubResourceWriter {
766
839
return c .SubResource ("status" )
767
840
}
@@ -818,7 +891,7 @@ func (sw *fakeSubResourceClient) Update(ctx context.Context, obj client.Object,
818
891
if updateOptions .SubResourceBody != nil {
819
892
body = updateOptions .SubResourceBody
820
893
}
821
- return sw .client .Update (ctx , body , & updateOptions .UpdateOptions )
894
+ return sw .client .update (ctx , body , true , & updateOptions .UpdateOptions )
822
895
}
823
896
824
897
func (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
833
906
body = patchOptions .SubResourceBody
834
907
}
835
908
836
- return sw .client .Patch (ctx , body , patch , & patchOptions .PatchOptions )
909
+ return sw .client .patch (ctx , body , patch , true , & patchOptions .PatchOptions )
837
910
}
838
911
839
912
func allowsUnconditionalUpdate (gvk schema.GroupVersionKind ) bool {
@@ -933,6 +1006,42 @@ func allowsCreateOnUpdate(gvk schema.GroupVersionKind) bool {
933
1006
return false
934
1007
}
935
1008
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
+
936
1045
// zero zeros the value of a pointer.
937
1046
func zero (x interface {}) {
938
1047
if x == nil {
0 commit comments