@@ -41,6 +41,7 @@ import (
41
41
https://github.com/kubernetes/kubernetes/pull/120326 (v5.6.0+incompatible
42
42
missing a critical fix)
43
43
*/
44
+
44
45
jsonpatch "gopkg.in/evanphx/json-patch.v4"
45
46
appsv1 "k8s.io/api/apps/v1"
46
47
authenticationv1 "k8s.io/api/authentication/v1"
@@ -717,6 +718,10 @@ func (c *fakeClient) List(ctx context.Context, obj client.ObjectList, opts ...cl
717
718
if err := ensureTypeMeta (o , gvk ); err != nil {
718
719
return err
719
720
}
721
+
722
+ if ! c .returnManagedFields {
723
+ o .(metav1.Object ).SetManagedFields (nil )
724
+ }
720
725
}
721
726
722
727
if listOpts .LabelSelector == nil && listOpts .FieldSelector == nil {
@@ -870,7 +875,7 @@ func (c *fakeClient) Create(ctx context.Context, obj client.Object, opts ...clie
870
875
c .trackerWriteLock .Lock ()
871
876
defer c .trackerWriteLock .Unlock ()
872
877
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 {
874
879
// The managed fields tracker sets gvk even on errors
875
880
_ = ensureTypeMeta (obj , gvk )
876
881
return err
@@ -1113,11 +1118,7 @@ func (c *fakeClient) patch(obj client.Object, patch client.Patch, opts ...client
1113
1118
return err
1114
1119
}
1115
1120
1116
- data , err := patch .Data (obj )
1117
- if err != nil {
1118
- return err
1119
- }
1120
-
1121
+ var isApplyCreate bool
1121
1122
c .trackerWriteLock .Lock ()
1122
1123
defer c .trackerWriteLock .Unlock ()
1123
1124
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
1126
1127
return err
1127
1128
}
1128
1129
oldObj = & unstructured.Unstructured {}
1130
+ isApplyCreate = true
1129
1131
}
1130
1132
oldAccessor , err := meta .Accessor (oldObj )
1131
1133
if err != nil {
1132
1134
return err
1133
1135
}
1134
1136
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 ())
1146
1140
}
1147
- newObj , err := meta .Accessor (o )
1141
+
1142
+ data , err := patch .Data (obj )
1148
1143
if err != nil {
1149
1144
return err
1150
1145
}
1151
1146
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 , obj )
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
+ }
1155
1183
}
1156
1184
1157
1185
reaction := testing .ObjectReaction (c .tracker )
@@ -1206,7 +1234,7 @@ func deletionTimestampEqual(newObj metav1.Object, obj metav1.Object) bool {
1206
1234
// This results in some code duplication, but was found to be a cleaner alternative than unmarshalling and introspecting the patch data
1207
1235
// and easier than refactoring the k8s client-go method upstream.
1208
1236
// 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 ) {
1210
1238
ns := action .GetNamespace ()
1211
1239
gvr := action .GetResource ()
1212
1240
@@ -1262,20 +1290,7 @@ func dryPatch(action testing.PatchActionImpl, tracker testing.ObjectTracker, new
1262
1290
case types .ApplyCBORPatchType :
1263
1291
return nil , errors .New ("apply CBOR patches are not supported in the fake client" )
1264
1292
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" )
1279
1294
default :
1280
1295
return nil , fmt .Errorf ("%s PatchType is not supported" , action .GetPatchType ())
1281
1296
}
0 commit comments