@@ -124,9 +124,14 @@ become possible.
124
124
125
125
### Goals
126
126
127
- The goal is to enable a union or "oneof" semantics in Kubernetes types, both for
128
- in-tree types and for CRDs. Validation of these unions should be centralized
129
- rather than being written on a per resource basis in validation functions.
127
+ Provide a simple mechanism for API authors to label fields of a resource as
128
+ members of a oneOf, in order to receive standardized:
129
+
130
+ * Validation - ensuring only one member field is set (or at most one if
131
+ desired).
132
+ * Normalization - ensuring the API server can modify the fields of the oneOf to
133
+ best match the intent of the client, despite the client potentially having
134
+ incomplete information
130
135
131
136
### Non-Goals
132
137
@@ -150,26 +155,29 @@ Include as much detail as possible so that people can understand the "how" of
150
155
the system. The goal here is to make this feel real for users without getting
151
156
bogged down.
152
157
153
- TODO: ask antoine if these stories are complete/sufficient
154
158
-->
155
159
156
160
157
161
#### Story 1
158
162
159
- As a CRD owner, I should be able to author my CRD such that the openapi schema
160
- indicates which fields are members of this oneOf, so that when users crate
161
- custom resources, the oneOf fields will be automatically validated, without me
162
- having to write custom validation logic.
163
+ As a CRD owner, I can use simple semantics (such as go tags), to express the
164
+ desired validation of a oneOf (at most one or exactly one field may be set), and
165
+ the API server will perform this validation automatically.
163
166
164
167
#### Story 2
165
168
169
+ As a client, I can read, modify, and update the union fields of an object, even
170
+ if I am not aware of all of the possible fields, and the server will properly
171
+ interpret my intent.
172
+
173
+ #### Story 3
174
+
166
175
As a maintainer of existing builtin APIs, I should be able to add new fields to
167
176
a union and have it behave as expected.
168
177
169
178
### Notes/Constraints/Caveats (Optional)
170
179
171
180
<!--
172
- TODO: ?
173
181
What are the caveats to the proposal?
174
182
What are some important details that didn't come across above?
175
183
Go in to as much detail as necessary here.
@@ -203,14 +211,19 @@ Consider including folks who also work outside the SIG or subproject.
203
211
We're proposing a new type of tags for go types (in-tree types, and also
204
212
kubebuilder types):
205
213
214
+
206
215
- ` // +unionDiscriminator ` before a field means that this field is the
207
- discriminator for the union. This field MUST be a string. Can be optional or
208
- required. If required, the union must have exactly one member set, if
209
- optional, the union may have at most one member set.
216
+ discriminator for the union. This field MUST be a string. This field MUST be
217
+ required.
218
+ - ` // +unionAllowEmpty ` before a discriminator field means that by setting the
219
+ discriminator to an empty string, none of the union fields are persisted
220
+ (meaning at most one member of the union must be set). If not present on the
221
+ discriminator, exactly one of the union members must be set in order to be
222
+ valid.
210
223
- ` // +unionMember=<discriminatorName> ` before a field means that this
211
224
field is a member of a union. The ` <discriminatorName> ` is optional if there
212
225
is only union in a struct and required if there are multiple unions per
213
- struct.
226
+ struct.
214
227
215
228
Note unions can't span across multiple go structures (all the fields that are part of a union has to be together in the
216
229
same structure), examples of what is allowed:
@@ -226,12 +239,14 @@ type TopLevelUnion struct {
226
239
// This will generate one union, with two fields and a discriminator.
227
240
type Union struct {
228
241
// +unionDiscriminator
229
- // +optional
242
+ // +unionAllowEmpty
243
+ // +required
230
244
UnionType string `json:"unionType"`
231
245
232
- // +optional
233
246
// +unionMember
247
+ // +optional
234
248
FieldA int `json:"fieldA"`
249
+ // +unionMember
235
250
// +optional
236
251
FieldB int `json:"fieldB"`
237
252
}
@@ -255,7 +270,8 @@ type InlinedUnion struct {
255
270
Name string `json:"name"`
256
271
257
272
// +unionDiscriminator
258
- // +optional
273
+ // +unionAllowEmpty
274
+ // +required
259
275
FieldType string `json:"fieldType"`
260
276
// +unionMember=fieldType
261
277
// +optional
@@ -290,45 +306,15 @@ each list item is made of:
290
306
291
307
Conversion between OpenAPI v2 and OpenAPI v3 will preserve these fields.
292
308
293
- ### Discriminator
294
-
295
- // TODO: ask Antoine
296
- For backward compatibility reasons, discriminators should be added to existing
297
- union structures as an optional string. This has a nice property that it's going
298
- to allow conflict detection when the selected union field is changed.
299
-
300
- We also do strongly recommend new APIs to be written with a discriminator, and
301
- tools (kube-builder) should probably enforce the presence of a discriminator in
302
- CRDs.
303
-
304
- The value of the discriminator is going to be set automatically by the apiserver
305
- when a new field is changed in the union. It will be set to the value of the
306
- ` fields-to-discriminateBy ` for that specific field.
307
-
308
- When the value of the discriminator is explicitly changed by the client, it
309
- will be interpreted as an intention to clear all the other fields. See section
310
- below.
311
-
312
- #### "At most one" versus "exactly one"
313
-
314
- // TODO: check this
315
- We propose supporting both "at most one" and "exactly one" semantics.
316
-
317
- To distinguish between the two, API authors should tag the discriminator as
318
- "optional" or "required" based on the desired behavior. We the discriminator is
319
- set to an empty string (or missing from the object), this will indicate to the
320
- server to clear all fields of the union (or fail the request if the
321
- discriminator is required).
322
-
323
309
### Normalization and Validation
324
310
325
311
Normalization refers to the process by which the API server attempts to understand
326
312
and correct clients which may provide the server with conflicting or incomplete
327
313
information about a union.
328
314
329
- Issues primarily arise here because of version skew between a client and server,
315
+ Issues primarily arise here because of version skew between a client and a server,
330
316
such as when a client is unaware of new fields added to a union and thus doesn't
331
- know how to clear these new fields when trying to set a different.
317
+ know how to clear these new fields when trying to set a different field .
332
318
333
319
For unions with a discriminator (all newly created unions and some existing
334
320
unions), normalization is simple: the server should always respect the
@@ -341,18 +327,16 @@ given field, it should clear out any other fields that are set.
341
327
but that given field is empty, it should maintain the value of the field that
342
328
currently exists.
343
329
344
- For union types without a discriminator (only existing unions), normalization is a little more
345
- complicated and incomplete. If multiple union fields are set:
346
- 1 . If greater than two of the set union fields were not previously set, error
347
- 2 . If one of the set union fields was previously set, and one is newly set,
348
- respect the newly set field and clear the other.
349
-
350
330
For situations where a typed client is unaware of a new field (that is currently
351
331
set) and drops the set field such that no fields are now set, the server will
352
332
clear all the fields of the union. This is unavoidable for unions without a
353
333
discriminator and a major reason why we recommend all new unions have a
354
334
discriminator.
355
335
336
+ Union types without a discriminator must continue to perform normalization and
337
+ validation manually (i.e. per resource in the validation functions), as is
338
+ currently done for existing union types.
339
+
356
340
Objects must be validated AFTER the normalization process.
357
341
358
342
### Test Plan
0 commit comments