12
12
- [ Design Details] ( #design-details )
13
13
- [ Apply functions] ( #apply-functions )
14
14
- [ Generated apply configuration types] ( #generated-apply-configuration-types )
15
- - [ Alternative 1: Generated structs where all fields are pointers] ( #alternative-1-generated-structs-where-all-fields-are-pointers )
16
- - [ Alternative 2: Generated " ; builders" ; ] ( #alternative-2-generated-builders )
17
- - [ Comparison of alternatives] ( #comparison-of-alternatives )
18
15
- [ DeepCopy support] ( #deepcopy-support )
19
16
- [ Code Generator Changes] ( #code-generator-changes )
20
17
- [ Addition of applyconfiguration-gen] ( #addition-of-applyconfiguration-gen )
21
18
- [ client-gen changes] ( #client-gen-changes )
19
+ - [ read/modify/write loop support] ( #readmodifywrite-loop-support )
22
20
- [ Interoperability with structured and unstructured types] ( #interoperability-with-structured-and-unstructured-types )
23
21
- [ Test Plan] ( #test-plan )
24
22
- [ Fuzz-based round-trip testing] ( #fuzz-based-round-trip-testing )
37
35
- [ Implementation History] ( #implementation-history )
38
36
- [ Drawbacks] ( #drawbacks )
39
37
- [ Alternatives] ( #alternatives )
38
+ - [ Alternative: Generated structs where all fields are pointers] ( #alternative-generated-structs-where-all-fields-are-pointers )
40
39
- [ Alternative: Use YAML directly] ( #alternative-use-yaml-directly )
41
40
- [ Alternative: Combine go structs with fieldset mask] ( #alternative-combine-go-structs-with-fieldset-mask )
42
41
- [ Alternative: Use varadic function based builders] ( #alternative-use-varadic-function-based-builders )
@@ -61,7 +60,7 @@ checklist items _must_ be updated for the enhancement to be released.
61
60
Items marked with (R) are required * prior to targeting to a milestone / release* .
62
61
63
62
- [x] (R) Enhancement issue in release milestone, which links to KEP dir in [ kubernetes/enhancements] (not the initial KEP PR)
64
- - [ ] (R) KEP approvers have approved the KEP status as ` implementable `
63
+ - [x ] (R) KEP approvers have approved the KEP status as ` implementable `
65
64
- [x] (R) Design details are appropriately documented
66
65
- [ ] (R) Test plan is in place, giving consideration to SIG Architecture and SIG Testing input
67
66
- [x] (R) Graduation criteria is in place
@@ -221,111 +220,38 @@ While there are arguments to be made all of these fields are expected to cause
221
220
problems in practice, there are some that clearly would, e.g. ContainerPort.
222
221
223
222
Because of this we cannot use the existing go structs to represent apply
224
- configurations.
223
+ configurations. Instead, we will generated "builders".
225
224
226
- <<[ UNRESOLVED @jpbetz @jennybuckley ] >>
227
- Finalize which alternative to use based on developer feedback. See the
228
- [ Alternatives] ( #alternatives ) for a complete list, but are currently focusing on
229
- the two below alternatives. We are working with the Kubebuilder community to
230
- gather feedback on what developers prefer.
231
- <<[ /UNRESOLVED] >>
232
-
233
- #### Alternative 1: Generated structs where all fields are pointers
234
-
235
- Example usage:
236
-
237
- ``` go
238
- import (
239
- ptr " k8s.io/utils/pointer"
240
- )
241
-
242
- &appsv1apply.Deployment {
243
- Name : ptr.StringPtr (" nginx-deployment" ),
244
- Spec : &appsv1apply.DeploymentSpec {
245
- Replicas: ptr.Int32Ptr (0 ),
246
- Template: &v1apply.PodTemplate {
247
- Spec: &v1apply.PodSpec {
248
- Containers: []v1.Containers {
249
- {
250
- Name: ptr.StringPtr (" nginx" ),
251
- Image: ptr.StringPtr (" nginx:latest" ),
252
- },
253
- }
254
- },
255
- },
256
- },
257
- }
258
- ```
259
-
260
- For this approach, developers need to use a library like
261
- https://github.com/kubernetes/utils/blob/master/pointer/pointer.go
262
- to inline primitive literals.
263
-
264
- #### Alternative 2: Generated "builders"
265
-
266
- Example usage:
225
+ Example generated builder:
267
226
268
227
``` go
269
- &appsv1apply.Deployment ().
270
- ObjectMeta (&metav1apply.ObjectMeta ().
271
- Name (" nginx-deployment" )).
272
- Spec (&appsv1apply.DeploymentSpec ().
228
+ appsv1apply.Deployment (" ns" , " nginx-deployment" ).
229
+ Spec (appsv1apply.DeploymentSpec ().
273
230
Replicas (0 ).
274
231
Template (
275
- & v1apply.PodTemplate ().
276
- Spec (& v1apply.SetPodSpec ().
277
- Containers (v1apply. ContainerList {
232
+ v1apply.PodTemplate ().
233
+ Spec (v1apply.PodSpec ().
234
+ Containers (
278
235
v1apply.Container ().
279
236
Name (" nginx" ).
280
- Image (" nginx:1.14.2" )
237
+ Image (" nginx:1.14.2" ),
281
238
v1apply.Container ().
282
239
Name (" sidecar" ).
283
- } )
240
+ )
284
241
)
285
242
)
286
243
)
287
244
)
288
245
```
289
246
290
- <<[ UNRESOLVED @jpbetz ] >>
291
- Finalize how setter functions are named if we go with this alternative. Options we
292
- are considering are: (1) Just use the field name (2) prefix field name with "Set" (3)
293
- prefix field name with "With".
294
- <<[ /UNRESOLVED] >>
295
-
296
- #### Comparison of alternatives
297
-
298
- See https://github.com/kubernetes/kubernetes/pull/95988 for a working implementation
299
- of alterative 1 and https://github.com/jpbetz/kubernetes/tree/apply-client-go-builders
300
- for a working implementation of alternative 2.
247
+ See https://github.com/jpbetz/kubernetes/tree/apply-client-go-builders for a working
248
+ implementation.
301
249
302
- Of the two leading alternatives--"builders" and "structs with pointers"--we implemented
303
- prototypes of both. They had roughly equivalent performance, and no differences
304
- in their capabilities. The choice between the two is primarily one of aesthetics/ergonomics .
250
+ The namespace and name will be immutable once set on the constructor. The constructor
251
+ will be generated according to the scope of the object - cluster scoped objects will
252
+ not have a namespace argument .
305
253
306
- Some of the feedback we have heard from the community:
307
-
308
- - "structs with pointers" feels more go idiomatic and more closely aligned with
309
- the go structs used for Kubernetes types both for builtin types and by
310
- Kubebuilder.
311
- - It's nice how "builders" are clearly visually distinct from the main go types.
312
- - Having to use a utility library to wrap literal values as pointers for the
313
- "structs with pointers" is not a big deal. I'm already familiar
314
- with having to do this in go and once I learn use a utility library for it
315
- I'm all set.
316
- - The "builders" are awkward to use in an IDE. I felt like I was fighting with
317
- my IDE to get chain function calls and organize them hierarchally as expected
318
- by this approach.
319
-
320
- TODO: We are providing the developer community with a fork of controller-tools
321
- that will allow them to generate apply configuration types that have both the
322
- alternatives. Our goal is to get feedback from developers after they try out the
323
- generated apply configurations and use that to inform our decision.
324
-
325
- While it is possible to generate types that have both the pointer fields exposed
326
- and the builder functions, we would prefer to provide clear guidance to the
327
- community on how apply configurations should be represented in go and encourage
328
- consistent use of only one of these approaches.
254
+ TypeMeta info (apiVersion and type) is autopopulated by the constructor.
329
255
330
256
#### DeepCopy support
331
257
@@ -358,6 +284,67 @@ adding functions to interfaces is a change we have made in the past, and develop
358
284
that have alternate implementations of the interface will usually get a compiler
359
285
error in this case, which is relatively trivial to resolve
360
286
287
+ ### read/modify/write loop support
288
+
289
+ While it is
290
+ [ recommended] ( https://kubernetes.io/docs/reference/using-api/server-side-apply/ )
291
+ that apply clients use "fully specified intent" rather than
292
+ read/modify/write loops, there are many existing controllers that use
293
+ a get/modify/update loop today, and are not easy to convert to the
294
+ "fully specified intend" approach.
295
+
296
+ For such controllers, providing a convenient way to do a
297
+ "get-previously-applied/modify/apply" loop is pragmatic. It allows
298
+ these controller to benefit from apply by sending a minimal apply
299
+ patch (as opposed to sending the entire object for update) and to
300
+ reduce the odds of conflict with other controllers.
301
+
302
+ To support this, the builders will include "BuildApply" utility functions
303
+ function. For example:
304
+
305
+ ```
306
+ fieldManger := "my-field-manager"
307
+ // 1. read
308
+ deployment, err := client.AppsV1().Deployments(ns).Get("deployment-name", metav1.GetOptions{})
309
+ if err != nil {
310
+ // handle err
311
+ }
312
+ applyConfig, err := appsv1apply.BuildDeploymentApply(deployment, fieldManager)
313
+ if err != nil {
314
+ // handle err
315
+ }
316
+
317
+ // 2. modify
318
+ applyConfig.GetSpec().Replicas(10)
319
+
320
+ // 3. apply
321
+ client.AppsV1().Deployments(ns).Apply(ctx, applyConfig, metav1.ApplyOptions{FieldManager: fieldManager, Force: true})
322
+ ```
323
+
324
+ In the above example, ` BuildDeploymentApply ` constructs a populated
325
+ apply configuration from a deployment object returned from a Get. It
326
+ uses the provided field manager to get the matching field set
327
+ (` FieldsV1 ` data) from object and then combines the field set with the
328
+ object to produce the apply configuration.
329
+
330
+ We will clearly document that on the "BuildApply" functions that we
331
+ recommend using "fully specified intent" when using apply and also
332
+ when it might be appropriate (and inappropriate) to use the "BuildApply"
333
+ utility.
334
+
335
+ An alternative we considered, but rejected, was to add ` GetForApply() `
336
+ style functions to the client. The benefit of this approach is that
337
+ the caller doesn't have to deal with converting the response object
338
+ into an apply configuration, and there is no possibility of the client
339
+ modifying the object before converting it into an apply
340
+ configuration. The biggest problem with this approach is that there
341
+ are many methods that return an object
342
+ (get/create/update/patch/watch/list) that would all need to have a
343
+ corresponding ` <method>ForApply() ` .
344
+
345
+ Corner case: If the version of the managed fields does not match the
346
+ object (which is possible, but only if changing versions and using the
347
+ same field manager) the "BuildApply" function will return an error.
361
348
362
349
### Interoperability with structured and unstructured types
363
350
@@ -368,6 +355,11 @@ For "builders", each would implement `MarshalJSON`, `UnmarshalJSON`,
368
355
` ToUnstructured ` and ` FromUnstructured ` . Builders would also provide getter functions
369
356
to view what has been built.
370
357
358
+ ` FromUnstructured ` will check for the presence managed fields in the unstructured data,
359
+ if present, it will fail with an error indicating that objects retrieved via a Get
360
+ cannot be converted using ` FromUnstructured ` and should instead be converted to an
361
+ apply configuration using a "BuildApply" function.
362
+
371
363
### Test Plan
372
364
373
365
#### Fuzz-based round-trip testing
@@ -389,8 +381,11 @@ it is correct using the existing e2e tests, expanding coverage as needed.
389
381
390
382
### Graduation Criteria
391
383
392
- This enhancement will graduate to GA as part of Server Side Apply. It does
393
- not make sense to graduate it independently.
384
+ Because client-go has no feature gates, the gating of this
385
+ functionality is determined by the Server Side Apply
386
+ functionality. For this reason this enhancement is a considered a
387
+ sub-KEP of Server Side Apply, and will graduate to GA as part of
388
+ Server Side Apply, which is slated for 1.21.
394
389
395
390
### Upgrade / Downgrade Strategy
396
391
@@ -500,6 +495,64 @@ Major milestones might include:
500
495
501
496
## Alternatives
502
497
498
+ #### Alternative: Generated structs where all fields are pointers
499
+
500
+ Example usage:
501
+
502
+ ``` go
503
+ import (
504
+ ptr " k8s.io/utils/pointer"
505
+ )
506
+
507
+ &appsv1apply.Deployment {
508
+ Name : ptr.StringPtr (" nginx-deployment" ),
509
+ Spec : &appsv1apply.DeploymentSpec {
510
+ Replicas: ptr.Int32Ptr (0 ),
511
+ Template: &v1apply.PodTemplate {
512
+ Spec: &v1apply.PodSpec {
513
+ Containers: []v1.Containers {
514
+ {
515
+ Name: ptr.StringPtr (" nginx" ),
516
+ Image: ptr.StringPtr (" nginx:latest" ),
517
+ },
518
+ }
519
+ },
520
+ },
521
+ },
522
+ }
523
+ ```
524
+
525
+ There was mixed support for this approach in the community. Some feedback we have
526
+ gotten when comparing the "builders" alternative with this "structs with pointers"
527
+ alternative include:
528
+
529
+ - "structs with pointers" feels more go idiomatic and more closely aligned with
530
+ the go structs used for Kubernetes types both for builtin types and by
531
+ Kubebuilder.
532
+ - It's nice how "builders" are clearly visually distinct from the main go types.
533
+ - Having to use a utility library to wrap literal values as pointers for the
534
+ "structs with pointers" is not a big deal. I'm already familiar
535
+ with having to do this in go and once I learn use a utility library for it
536
+ I'm all set.
537
+ - The "builders" are awkward to use in an IDE. I felt like I was fighting with
538
+ my IDE to get chain function calls and organize them hierarchally as expected
539
+ by this approach.
540
+
541
+ Limitations:
542
+
543
+ - Even when using a library like
544
+ https://github.com/kubernetes/utils/blob/master/pointer/pointer.go to
545
+ inline primitive literals, some values, like enumeration values cannot
546
+ be inlined since ptr.StringPtr() does not work with them.
547
+ - Direct access to struct allow developers to do conversions that are
548
+ unsafe, like directly converting from a type's go struct, retrieved
549
+ via a Get, to it apply configuration without using the managed
550
+ fields during the conversion.
551
+ - No good way to future proof this against adding tombstone support in the future
552
+ like there is with the builders approach.
553
+ - Code that reads the value of a deeply nested field because it must defererence
554
+ pointers at each level of nesting.
555
+
503
556
### Alternative: Use YAML directly
504
557
505
558
For fields that need to be set programmatically, use templating.
0 commit comments