@@ -158,7 +158,7 @@ take an aligned approach to adding apply to clients in a typesafe way.
158
158
The client-go typed clients will be extended to include Apply functions, e.g.:
159
159
160
160
``` go
161
- func (c *deployments ) Apply (ctx Context , deployment *appsv1apply .Deployment , fieldManager string , metav1 .ApplyOptions ) (*Deployment , error )
161
+ func (c *deployments ) Apply (ctx Context , fieldManager string , deployment *appsv1apply .Deployment , opts metav1 .ApplyOptions , subresources ... string ) (*Deployment , error )
162
162
```
163
163
164
164
`ApplyOptions` will be added to metav1 even though `PatchOptions` will continue
@@ -174,32 +174,56 @@ type ApplyOptions struct {
174
174
func (ApplyOptions ) ToPatchOptions (fieldManager string ) PatchOptions
175
175
```
176
176
177
+ <<[UNRESOLVED @jpbetz ]>>
178
+ Should the Force field also be made a required field like fieldManager is?
179
+ @lavalamp pointed out that user things typically want false but
180
+ controller-things typically want true, so false isn't a great default.
181
+
182
+ Also, can we structure this so that we don't have so many positional params?
183
+ Moving two params from options to the top level positional params results in a
184
+ longer function signature. Alternative proposed by @lavalamp was to do something
185
+ more like this (ordering or parameters and function names TBD):
186
+
187
+ ```go
188
+ client.Deployments().BeginApply("field-manager-name", options).Body(
189
+ appsv1apply.Deployment().
190
+ SetObjectMeta(&metav1apply.ObjectMeta()). ...
191
+ ).Apply(ctx)
192
+ ```
193
+
194
+ <<[/UNRESOLVED]>>
195
+
177
196
Apply will combine the `fieldManager` argument with `ApplyOptions` to create the
178
197
`PatchOptions`.
179
198
180
199
Each apply call will be required to provide a fieldmanager name. We will not
181
- provide a way for the fieldmanager name to be set for the entire
182
- clientset. There are a couple reasons for this:
183
-
184
- - If a client has multiple code paths where it makes apply requests to the same
185
- object, but with different field sets, they must use different field manager
186
- names. If they use the same field manager name they will cause fields to be
187
- accidentally removed or disowned. This is a potential foot gun we would like to
188
- avoid.
189
-
190
- - Apply requests always conflict with update requests, even if they were made by
191
- the same client with the same field manager name. This is by design. So when a
192
- controller migrates from update to apply, it will need to deal with conflicts
193
- regardless of what field manager name is used.
200
+ provide a a way for the fieldmanager name to be set for the entire
201
+ clientset. The main reason for this is that if a client has multiple code paths
202
+ where it makes apply requests to the same object, but with different field sets,
203
+ they must use different field manager names. If they use the same field manager
204
+ name they will cause fields to be accidentally removed or disowned. This is a
205
+ potential foot gun we would like to avoid.
194
206
195
207
### Generated apply configuration types
196
208
197
209
All fields present in an apply configuration become owned by the applier after
198
210
when the apply request succeeds. Go structs contain zero valued fields which are
199
- included even if the user never explicitly sets the field. Required boolean
200
- fields are a good example of fields that would be applied incorrectly using go
201
- structs, e.g. `ContainerStatus.Ready` (required, not omitempty). Because of this
202
- we cannot use the existing go structs to represent apply configurations.
211
+ included even if the user never explicitly sets the field. This means that all required
212
+ fields (must not be a pointer and must not be "omitempty") create fundamental soundness
213
+ problem.
214
+
215
+ In practice there are over 100 ints and bools that meet this criteria this in
216
+ the Kubernetes API. Some examples:
217
+
218
+ - [`ContainerPort.ContainerPort`](https:// github.com/kubernetes/kubernetes/blob/6d76ece4d6e1ad01bad3e866279bc35065813ec7/staging/src/k8s.io/api/core/v1/types.go#L1836)
219
+ - [`HorizontalPodAutoscalerSpec.MaxReplicas`](https:// github.com/kubernetes/kubernetes/blob/6d76ece4d6e1ad01bad3e866279bc35065813ec7/staging/src/k8s.io/api/autoscaling/v1/types.go#L49)
220
+ - [`ContainerStatus.Ready`](https:// github.com/kubernetes/kubernetes/blob/6d76ece4d6e1ad01bad3e866279bc35065813ec7/staging/src/k8s.io/api/core/v1/types.go#L2470)
221
+
222
+ While there are arguments to be made all of these fields are expected to cause
223
+ problems in practice, there are some that clearly would, e.g. ContainerPort.
224
+
225
+ Because of this we cannot use the existing go structs to represent apply
226
+ configurations.
203
227
204
228
<<[UNRESOLVED @jpbetz @jennybuckley ]>>
205
229
Finalize which alternative to use based on developer feedback. See the
@@ -213,16 +237,20 @@ gather feedback on what developers prefer.
213
237
Example usage:
214
238
215
239
```go
240
+ import (
241
+ ptr "k8s.io/utils/pointer"
242
+ )
243
+
216
244
&appsv1apply.Deployment{
217
- Name: &pointer .StringPtr (" nginx-deployment" ),
245
+ Name: ptr .StringPtr (" nginx-deployment" ),
218
246
Spec: &appsv1apply.DeploymentSpec {
219
- Replicas: &pointer .Int32Ptr (0 ),
247
+ Replicas: ptr .Int32Ptr (0 ),
220
248
Template: &v1apply.PodTemplate {
221
249
Spec: &v1apply.PodSpec {
222
250
Containers: []v1.Containers {
223
251
{
224
- Name: &pointer .StringPtr (" nginx" ),
225
- Image: &pointer .StringPtr (" nginx:latest" ),
252
+ Name: ptr .StringPtr (" nginx" ),
253
+ Image: ptr .StringPtr (" nginx:latest" ),
226
254
},
227
255
}
228
256
},
@@ -241,26 +269,32 @@ Example usage:
241
269
242
270
``` go
243
271
&appsv1apply.Deployment ().
244
- SetObjectMeta (&metav1apply.ObjectMeta ().
245
- SetName (" nginx-deployment" )).
246
- SetSpec (&appsv1apply.DeploymentSpec ().
247
- SetReplicas (0 ).
248
- SetTemplate (
272
+ ObjectMeta (&metav1apply.ObjectMeta ().
273
+ Name (" nginx-deployment" )).
274
+ Spec (&appsv1apply.DeploymentSpec ().
275
+ Replicas (0 ).
276
+ Template (
249
277
&v1apply.PodTemplate ().
250
- SetSpec (&v1apply.SetPodSpec ().
251
- SetContainers (v1apply.ContainerList {
278
+ Spec (&v1apply.SetPodSpec ().
279
+ Containers (v1apply.ContainerList {
252
280
v1apply.Container ().
253
- SetName (" nginx" ).
254
- SetImage (" nginx:1.14.2" )
281
+ Name (" nginx" ).
282
+ Image (" nginx:1.14.2" )
255
283
v1apply.Container ().
256
- SetName (" sidecar" ).
284
+ Name (" sidecar" ).
257
285
})
258
286
)
259
287
)
260
288
)
261
289
)
262
290
```
263
291
292
+ <<[ UNRESOLVED @jpbetz ] >>
293
+ Finalize how setter functions are named if we go with this alternative. Options we
294
+ are considering are: (1) Just use the field name (2) prefix field name with "Set" (3)
295
+ prefix field name with "With".
296
+ <<[ /UNRESOLVED] >>
297
+
264
298
#### Comparison of alternatives
265
299
266
300
See https://github.com/kubernetes/kubernetes/pull/95988 for a working implementation
@@ -311,8 +345,8 @@ the apply functions and apply configuration types.
311
345
- Add staging/src/k8s.io/code-generator/cmd/applyconfigurations-gen
312
346
- Generates into staging/vendor/k8s.io/client-go/applyconfigurations/
313
347
- Only generate builders for struct types reachable from the types that have the +clientgen annotation
314
- - Don't generate builders for MarshalJSON types (Quantity, IntOrString)
315
- - Don't generate builders for RawExtension or Unknown
348
+ - Don't generate builders for MarshalJSON types (Time, Duration), juse reference them directly
349
+ - Don't generate builders for RawExtension or Unknown (e.g. [ AdmissionRequest.Object ] ( https://github.com/kubernetes/kubernetes/blob/fec1a366c3b17b5d46b79ddac0b8bb04dfd212ee/staging/src/k8s.io/api/admission/v1/types.go#L98 ) - [ example usage ] ( https://github.com/kubernetes/kubernetes/blob/fec1a366c3b17b5d46b79ddac0b8bb04dfd212ee/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview_test.go#L536 ) )
316
350
317
351
##### client-gen changes
318
352
@@ -332,8 +366,9 @@ error in this case, which is relatively trivial to resolve
332
366
For "structs with pointers", json.Marshal, json.Unmarshal and conversions to and
333
367
from unstructured work the same as with go structs.
334
368
335
- For "builders", each could implement ` MarshalJSON ` , ` UnmarshalJSON ` ,
336
- ` ToUnstructured ` and ` FromUnstructured ` .
369
+ For "builders", each would implement ` MarshalJSON ` , ` UnmarshalJSON ` ,
370
+ ` ToUnstructured ` and ` FromUnstructured ` . Builders would also provide getter functions
371
+ to view what has been built.
337
372
338
373
### Test Plan
339
374
0 commit comments