@@ -41,6 +41,7 @@ import (
4141 https://github.com/kubernetes/kubernetes/pull/120326 (v5.6.0+incompatible
4242 missing a critical fix)
4343 */
44+
4445 jsonpatch "gopkg.in/evanphx/json-patch.v4"
4546 appsv1 "k8s.io/api/apps/v1"
4647 authenticationv1 "k8s.io/api/authentication/v1"
@@ -717,6 +718,10 @@ func (c *fakeClient) List(ctx context.Context, obj client.ObjectList, opts ...cl
717718 if err := ensureTypeMeta (o , gvk ); err != nil {
718719 return err
719720 }
721+
722+ if ! c .returnManagedFields {
723+ o .(metav1.Object ).SetManagedFields (nil )
724+ }
720725 }
721726
722727 if listOpts .LabelSelector == nil && listOpts .FieldSelector == nil {
@@ -870,7 +875,7 @@ func (c *fakeClient) Create(ctx context.Context, obj client.Object, opts ...clie
870875 c .trackerWriteLock .Lock ()
871876 defer c .trackerWriteLock .Unlock ()
872877
873- if err := c .tracker .Create (gvr , obj , accessor .GetNamespace ()); err != nil {
878+ if err := c .tracker .Create (gvr , obj , accessor .GetNamespace (), * createOptions . AsCreateOptions () ); err != nil {
874879 // The managed fields tracker sets gvk even on errors
875880 _ = ensureTypeMeta (obj , gvk )
876881 return err
@@ -1113,11 +1118,7 @@ func (c *fakeClient) patch(obj client.Object, patch client.Patch, opts ...client
11131118 return err
11141119 }
11151120
1116- data , err := patch .Data (obj )
1117- if err != nil {
1118- return err
1119- }
1120-
1121+ var isApplyCreate bool
11211122 c .trackerWriteLock .Lock ()
11221123 defer c .trackerWriteLock .Unlock ()
11231124 oldObj , err := c .tracker .Get (gvr , accessor .GetNamespace (), accessor .GetName ())
@@ -1126,32 +1127,59 @@ func (c *fakeClient) patch(obj client.Object, patch client.Patch, opts ...client
11261127 return err
11271128 }
11281129 oldObj = & unstructured.Unstructured {}
1130+ isApplyCreate = true
11291131 }
11301132 oldAccessor , err := meta .Accessor (oldObj )
11311133 if err != nil {
11321134 return err
11331135 }
11341136
1135- // Apply patch without updating object.
1136- // To remain in accordance with the behavior of k8s api behavior,
1137- // a patch must not allow for changes to the deletionTimestamp of an object.
1138- // The reaction() function applies the patch to the object and calls Update(),
1139- // whereas dryPatch() replicates this behavior but skips the call to Update().
1140- // This ensures that the patch may be rejected if a deletionTimestamp is modified, prior
1141- // to updating the object.
1142- action := testing .NewPatchAction (gvr , accessor .GetNamespace (), accessor .GetName (), patch .Type (), data )
1143- o , err := dryPatch (action , c .tracker , obj )
1144- if err != nil {
1145- return err
1137+ // SSA deletionTimestamp updates are silently ignored
1138+ if patch .Type () == types .ApplyPatchType && ! isApplyCreate {
1139+ obj .SetDeletionTimestamp (oldAccessor .GetDeletionTimestamp ())
11461140 }
1147- newObj , err := meta .Accessor (o )
1141+
1142+ data , err := patch .Data (obj )
11481143 if err != nil {
11491144 return err
11501145 }
11511146
1152- // Validate that deletionTimestamp has not been changed
1153- if ! deletionTimestampEqual (newObj , oldAccessor ) {
1154- return fmt .Errorf ("rejected patch, metadata.deletionTimestamp immutable" )
1147+ action := testing .NewPatchActionWithOptions (
1148+ gvr ,
1149+ accessor .GetNamespace (),
1150+ accessor .GetName (),
1151+ patch .Type (),
1152+ data ,
1153+ * patchOptions .AsPatchOptions (),
1154+ )
1155+
1156+ // Apply is implemented in the tracker and calling it has side-effects
1157+ // such as bumping RV and updating managedFields timestamps, hence we
1158+ // can not dry-run it. Luckily, the only validation we use it for
1159+ // doesn't apply to SSA - Creating objects with non-nil deletionTimestamp
1160+ // through SSA is possible and updating the deletionTimestamp is valid,
1161+ // but has no effect.
1162+ if patch .Type () != types .ApplyPatchType {
1163+ // Apply patch without updating object.
1164+ // To remain in accordance with the behavior of k8s api behavior,
1165+ // a patch must not allow for changes to the deletionTimestamp of an object.
1166+ // The reaction() function applies the patch to the object and calls Update(),
1167+ // whereas dryPatch() replicates this behavior but skips the call to Update().
1168+ // This ensures that the patch may be rejected if a deletionTimestamp is modified, prior
1169+ // to updating the object.
1170+ o , err := dryPatch (action , c .tracker )
1171+ if err != nil {
1172+ return err
1173+ }
1174+ newObj , err := meta .Accessor (o )
1175+ if err != nil {
1176+ return err
1177+ }
1178+
1179+ // Validate that deletionTimestamp has not been changed
1180+ if ! deletionTimestampEqual (newObj , oldAccessor ) {
1181+ return fmt .Errorf ("rejected patch, metadata.deletionTimestamp immutable" )
1182+ }
11551183 }
11561184
11571185 reaction := testing .ObjectReaction (c .tracker )
@@ -1206,7 +1234,7 @@ func deletionTimestampEqual(newObj metav1.Object, obj metav1.Object) bool {
12061234// This results in some code duplication, but was found to be a cleaner alternative than unmarshalling and introspecting the patch data
12071235// and easier than refactoring the k8s client-go method upstream.
12081236// Duplicate of upstream: https://github.com/kubernetes/client-go/blob/783d0d33626e59d55d52bfd7696b775851f92107/testing/fixture.go#L146-L194
1209- func dryPatch (action testing.PatchActionImpl , tracker testing.ObjectTracker , newObj runtime. Object ) (runtime.Object , error ) {
1237+ func dryPatch (action testing.PatchActionImpl , tracker testing.ObjectTracker ) (runtime.Object , error ) {
12101238 ns := action .GetNamespace ()
12111239 gvr := action .GetResource ()
12121240
@@ -1262,20 +1290,7 @@ func dryPatch(action testing.PatchActionImpl, tracker testing.ObjectTracker, new
12621290 case types .ApplyCBORPatchType :
12631291 return nil , errors .New ("apply CBOR patches are not supported in the fake client" )
12641292 case types .ApplyPatchType :
1265- // There doesn't seem to be a way to test this without actually applying it as apply is implemented in the tracker.
1266- // We have to make sure no reader sees this and we can not handle errors resetting the obj to the original state.
1267- defer func () {
1268- if unstructured , isUnstructured := obj .(* unstructured.Unstructured ); isUnstructured && unstructured .GetKind () == "" {
1269- unstructured .SetGroupVersionKind (newObj .GetObjectKind ().GroupVersionKind ())
1270- }
1271- if err := tracker .Add (obj ); err != nil {
1272- panic (err )
1273- }
1274- }()
1275- if err := tracker .Apply (gvr , newObj , ns , action .PatchOptions ); err != nil {
1276- return nil , err
1277- }
1278- return tracker .Get (gvr , ns , action .GetName ())
1293+ return nil , errors .New ("bug in controller-runtime: should not end up in dryPatch for SSA" )
12791294 default :
12801295 return nil , fmt .Errorf ("%s PatchType is not supported" , action .GetPatchType ())
12811296 }
0 commit comments