Skip to content

Commit 870d966

Browse files
committed
apply feedback
1 parent 59dea9c commit 870d966

File tree

1 file changed

+71
-36
lines changed
  • keps/sig-api-machinery/2144-clientgo-apply

1 file changed

+71
-36
lines changed

keps/sig-api-machinery/2144-clientgo-apply/README.md

Lines changed: 71 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ take an aligned approach to adding apply to clients in a typesafe way.
158158
The client-go typed clients will be extended to include Apply functions, e.g.:
159159

160160
```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)
162162
```
163163

164164
`ApplyOptions` will be added to metav1 even though `PatchOptions` will continue
@@ -174,32 +174,56 @@ type ApplyOptions struct {
174174
func (ApplyOptions) ToPatchOptions(fieldManager string) PatchOptions
175175
```
176176

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+
177196
Apply will combine the `fieldManager` argument with `ApplyOptions` to create the
178197
`PatchOptions`.
179198

180199
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.
194206

195207
### Generated apply configuration types
196208

197209
All fields present in an apply configuration become owned by the applier after
198210
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.
203227

204228
<<[UNRESOLVED @jpbetz @jennybuckley ]>>
205229
Finalize which alternative to use based on developer feedback. See the
@@ -213,16 +237,20 @@ gather feedback on what developers prefer.
213237
Example usage:
214238

215239
```go
240+
import (
241+
ptr "k8s.io/utils/pointer"
242+
)
243+
216244
&appsv1apply.Deployment{
217-
Name: &pointer.StringPtr("nginx-deployment"),
245+
Name: ptr.StringPtr("nginx-deployment"),
218246
Spec: &appsv1apply.DeploymentSpec{
219-
Replicas: &pointer.Int32Ptr(0),
247+
Replicas: ptr.Int32Ptr(0),
220248
Template: &v1apply.PodTemplate{
221249
Spec: &v1apply.PodSpec{
222250
Containers: []v1.Containers{
223251
{
224-
Name: &pointer.StringPtr("nginx"),
225-
Image: &pointer.StringPtr("nginx:latest"),
252+
Name: ptr.StringPtr("nginx"),
253+
Image: ptr.StringPtr("nginx:latest"),
226254
},
227255
}
228256
},
@@ -241,26 +269,32 @@ Example usage:
241269

242270
```go
243271
&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(
249277
&v1apply.PodTemplate().
250-
SetSpec(&v1apply.SetPodSpec().
251-
SetContainers(v1apply.ContainerList{
278+
Spec(&v1apply.SetPodSpec().
279+
Containers(v1apply.ContainerList{
252280
v1apply.Container().
253-
SetName("nginx").
254-
SetImage("nginx:1.14.2")
281+
Name("nginx").
282+
Image("nginx:1.14.2")
255283
v1apply.Container().
256-
SetName("sidecar").
284+
Name("sidecar").
257285
})
258286
)
259287
)
260288
)
261289
)
262290
```
263291

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+
264298
#### Comparison of alternatives
265299

266300
See https://github.com/kubernetes/kubernetes/pull/95988 for a working implementation
@@ -311,8 +345,8 @@ the apply functions and apply configuration types.
311345
- Add staging/src/k8s.io/code-generator/cmd/applyconfigurations-gen
312346
- Generates into staging/vendor/k8s.io/client-go/applyconfigurations/
313347
- 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))
316350

317351
##### client-gen changes
318352

@@ -332,8 +366,9 @@ error in this case, which is relatively trivial to resolve
332366
For "structs with pointers", json.Marshal, json.Unmarshal and conversions to and
333367
from unstructured work the same as with go structs.
334368

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.
337372

338373
### Test Plan
339374

0 commit comments

Comments
 (0)