1
1
Add new patchStrategy to clear fields not present in the patch
2
2
=============
3
3
4
- Add tags ` patchStrategy:"replaceKeys" ` . For a given type that has the tag, all keys/fields missing
5
- from the request will be cleared when patching the object.
6
- For a field presents in the request, it will be merged with the live config.
4
+ We introduce a new struct tag ` patchStrategy:"retainKeys" ` and
5
+ a new optional directive ` $retainKeys: <list of fields> ` in the patch.
7
6
8
7
The proposal of Full Union is in [ kubernetes/community #388 ] ( https://github.com/kubernetes/community/pull/388 ) .
9
8
@@ -66,11 +65,53 @@ It indicates how to generate and merge a patch for lists. It could be `merge` or
66
65
67
66
new tags:
68
67
69
- ` patchStrategy: replaceKeys ` :
68
+ ` patchStrategy: "retainKeys" ` :
69
+
70
+ We introduce a new optional directive ` $retainKeys ` to support the new patch strategy.
71
+
72
+ ` $retainKeys ` directive has the following properties:
73
+ - It contains a list of strings.
74
+ - All fields needing to be preserved must be present in the ` $retainKeys ` list.
75
+ - The fields that are present will be merged with live object.
76
+ - All of the missing fields will be cleared when patching.
77
+ - All fields in the ` $retainKeys ` list must be a superset or the same as the fields present in the patch.
78
+
79
+ A new patch will have the same content as the old patch and an additional new directive.
80
+ It will be backward compatible.
81
+
82
+ #### When the patch doesn't have ` $retainKeys ` directive
83
+
84
+ When the patch doesn't have ` $retainKeys ` directive, even for a type with ` patchStrategy: "retainKeys" ` ,
85
+ the server won't treat the patch with the retainKeys logic.
86
+
87
+ This will guarantee the backward compatibility: old patch behaves the same as before on the new server.
88
+
89
+ #### When the patch has fields that not present in the ` $retainKeys ` list
90
+
91
+ The server will reject the patch in this case.
92
+
93
+ This is an invalid patch:
94
+
95
+ ``` yaml
96
+ union :
97
+ $retainKeys :
98
+ - foo
99
+ foo : a
100
+ bar : x
101
+ ` ` `
102
+
103
+ #### When the ` $retainKeys` list has fields that are not present in the patch
104
+
105
+ The server will merge the change and clear the fields not present in the `$retainKeys` list
70
106
71
- We introduce a new value ` replaceKeys ` for ` patchStrategy ` .
72
- It indicates that all fields needing to be preserved must be present in the patch.
73
- And the fields that are present will be merged with live object. All the missing fields will be cleared when patching.
107
+ This is a valid patch :
108
+ ` ` ` yaml
109
+ union:
110
+ $retainKeys:
111
+ - foo
112
+ - bar
113
+ foo: a
114
+ ` ` `
74
115
75
116
# ### Examples
76
117
@@ -80,8 +121,8 @@ Type definition:
80
121
` ` ` go
81
122
type ContainerStatus struct {
82
123
...
83
- // Add patchStrategy:"replaceKeys "
84
- State ContainerState ` json:"state,omitempty" protobuf:"bytes,2,opt,name=state" patchStrategy:"replaceKeys "` `
124
+ // Add patchStrategy:"retainKeys "
125
+ State ContainerState ` json:"state,omitempty" protobuf:"bytes,2,opt,name=state" patchStrategy:"retainKeys "``
85
126
...
86
127
}
87
128
```
@@ -101,7 +142,8 @@ state:
101
142
Patch:
102
143
` ` ` yaml
103
144
state :
104
- $patch: replaceKeys
145
+ $retainKeys :
146
+ - terminated
105
147
terminated :
106
148
exitCode : 0
107
149
finishedAt : ...
@@ -120,8 +162,8 @@ Type definition:
120
162
` ` ` go
121
163
type DeploymentSpec struct {
122
164
...
123
- // Add patchStrategy:"replaceKeys "
124
- Strategy DeploymentStrategy ` json:" strategy,omitempty" protobuf:" bytes,4,opt,name=strategy" patchStrategy:" replaceKeys " `
165
+ // Add patchStrategy:"retainKeys "
166
+ Strategy DeploymentStrategy `json:"strategy,omitempty" protobuf:"bytes,4,opt,name=strategy" patchStrategy:"retainKeys "`
125
167
...
126
168
}
127
169
```
@@ -144,7 +186,9 @@ unionName:
144
186
Patch:
145
187
` ` ` yaml
146
188
unionName :
147
- $patch: replaceKeys
189
+ $retainKeys :
190
+ - discriminatorName
191
+ - barField
148
192
discriminatorName : bar
149
193
barField :
150
194
barSubfield : val2
@@ -159,14 +203,14 @@ unionName:
159
203
160
204
3) Inlined union with ` patchMergeKey` only.
161
205
This case is special, because `Volumes` already has a tag `patchStrategy:"merge"`.
162
- We change the tag to ` patchStrategy:"merge|replaceKeys "`
206
+ We change the tag to `patchStrategy:"merge|retainKeys "`
163
207
164
208
Type definition :
165
209
` ` ` go
166
210
type PodSpec struct {
167
211
...
168
- // Add another value "replaceKeys " to patchStrategy
169
- Volumes []Volume ` json:" volumes,omitempty" patchStrategy:" merge|replaceKeys " patchMergeKey:" name" protobuf:" bytes,1,rep,name=volumes" `
212
+ // Add another value "retainKeys " to patchStrategy
213
+ Volumes []Volume ` json:"volumes,omitempty" patchStrategy:"merge|retainKeys " patchMergeKey:"name" protobuf:"bytes,1,rep,name=volumes"`
170
214
...
171
215
}
172
216
```
@@ -191,8 +235,10 @@ Patch:
191
235
` ` ` yaml
192
236
spec :
193
237
volumes :
194
- - name: foo
195
- $patch: replaceKeys
238
+ - $retainKeys :
239
+ - name
240
+ - hostPath
241
+ name : foo
196
242
hostPath :
197
243
path : ...
198
244
` ` `
@@ -224,8 +270,8 @@ Changes about how to generate the patch rely on package Strategic Merge Patch.
224
270
Strategic Merge Patch is a package used by both client and server. A typical usage is that a client
225
271
calls the function to calculate the patch and the API server calls another function to merge the patch.
226
272
227
- We need to make sure the client always sends a patch that includes all of the fields that it wants to keep .
228
- When merging, auto clear missing fields of a patch if the patch has a directive ` $patch: replaceKeys `
273
+ We need to make sure the new client always sends its patches with the ` $retainKeys` directive .
274
+ When merging, auto clear missing fields of a patch if the patch has a directive `$retainKeys `
229
275
230
276
# ## Open API
231
277
@@ -238,13 +284,32 @@ The changes are all backward compatible.
238
284
Old kubectl vs New server : All behave the same as before, since no new directive in the patch.
239
285
240
286
New kubectl vs Old server : All behave the same as before, since new directive will not be recognized
241
- by the old server and it will be dropped in conversion; Unchanged fields will not affect the merged result .
287
+ by the old server and it will be dropped in conversion.
242
288
243
289
# Alternatives Considered
244
290
291
+ # 1. Use directive `$patch: retainKeys` in the patch
292
+
293
+ Add tags `patchStrategy:"retainKeys"`.
294
+ For a given type that has the tag, all keys/fields missing
295
+ from the request will be cleared when patching the object.
296
+ Each field present in the request will be merged with the live config.
297
+
298
+ # # Analysis
299
+
300
+ There are 2 reasons of avoiding this logic :
301
+ - Using `$patch` as directive key will break backward compatibility.
302
+ But can easily fixed by using a different key, e.g. `retainKeys : true`.
303
+ Reason is that `$patch` has been used in earlier releases.
304
+ If we add new value to this directive,
305
+ the old server will reject the new patch due to not knowing the new value.
306
+ - The patch has to include the entire struct to hold the place in a list with `replace` patch strategy,
307
+ even though there may be no changes at all.
308
+ This is less efficient compared to the approach above.
309
+
245
310
The proposals below are not mutually exclusive with the proposal above, and maybe can be added at some point in the future.
246
311
247
- # 1 . Add Discriminators in All Unions /OneOf APIs
312
+ # 2 . Add Discriminators in All Unions/OneOf APIs
248
313
249
314
Original issue is described in kubernetes/kubernetes#35345
250
315
0 commit comments