Skip to content

Commit d882c04

Browse files
committed
address apelisse feedback
1 parent 5100aa8 commit d882c04

File tree

1 file changed

+38
-54
lines changed
  • keps/sig-api-machinery/1027-api-unions

1 file changed

+38
-54
lines changed

keps/sig-api-machinery/1027-api-unions/README.md

Lines changed: 38 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,14 @@ become possible.
124124

125125
### Goals
126126

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
130135

131136
### Non-Goals
132137

@@ -150,26 +155,29 @@ Include as much detail as possible so that people can understand the "how" of
150155
the system. The goal here is to make this feel real for users without getting
151156
bogged down.
152157
153-
TODO: ask antoine if these stories are complete/sufficient
154158
-->
155159

156160

157161
#### Story 1
158162

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

164167
#### Story 2
165168

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+
166175
As a maintainer of existing builtin APIs, I should be able to add new fields to
167176
a union and have it behave as expected.
168177

169178
### Notes/Constraints/Caveats (Optional)
170179

171180
<!--
172-
TODO: ?
173181
What are the caveats to the proposal?
174182
What are some important details that didn't come across above?
175183
Go in to as much detail as necessary here.
@@ -203,14 +211,19 @@ Consider including folks who also work outside the SIG or subproject.
203211
We're proposing a new type of tags for go types (in-tree types, and also
204212
kubebuilder types):
205213

214+
206215
- `// +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.
210223
- `// +unionMember=<discriminatorName>` before a field means that this
211224
field is a member of a union. The `<discriminatorName>` is optional if there
212225
is only union in a struct and required if there are multiple unions per
213-
struct.
226+
struct.
214227

215228
Note unions can't span across multiple go structures (all the fields that are part of a union has to be together in the
216229
same structure), examples of what is allowed:
@@ -226,12 +239,14 @@ type TopLevelUnion struct {
226239
// This will generate one union, with two fields and a discriminator.
227240
type Union struct {
228241
// +unionDiscriminator
229-
// +optional
242+
// +unionAllowEmpty
243+
// +required
230244
UnionType string `json:"unionType"`
231245
232-
// +optional
233246
// +unionMember
247+
// +optional
234248
FieldA int `json:"fieldA"`
249+
// +unionMember
235250
// +optional
236251
FieldB int `json:"fieldB"`
237252
}
@@ -255,7 +270,8 @@ type InlinedUnion struct {
255270
Name string `json:"name"`
256271
257272
// +unionDiscriminator
258-
// +optional
273+
// +unionAllowEmpty
274+
// +required
259275
FieldType string `json:"fieldType"`
260276
// +unionMember=fieldType
261277
// +optional
@@ -290,45 +306,15 @@ each list item is made of:
290306

291307
Conversion between OpenAPI v2 and OpenAPI v3 will preserve these fields.
292308

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-
323309
### Normalization and Validation
324310

325311
Normalization refers to the process by which the API server attempts to understand
326312
and correct clients which may provide the server with conflicting or incomplete
327313
information about a union.
328314

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,
330316
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.
332318

333319
For unions with a discriminator (all newly created unions and some existing
334320
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.
341327
but that given field is empty, it should maintain the value of the field that
342328
currently exists.
343329

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-
350330
For situations where a typed client is unaware of a new field (that is currently
351331
set) and drops the set field such that no fields are now set, the server will
352332
clear all the fields of the union. This is unavoidable for unions without a
353333
discriminator and a major reason why we recommend all new unions have a
354334
discriminator.
355335

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+
356340
Objects must be validated AFTER the normalization process.
357341

358342
### Test Plan

0 commit comments

Comments
 (0)