Skip to content

Commit 0d0bcb7

Browse files
committed
Address liggitt feedback
1 parent 908da47 commit 0d0bcb7

File tree

1 file changed

+100
-91
lines changed
  • keps/sig-api-machinery/1027-api-unions

1 file changed

+100
-91
lines changed

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

Lines changed: 100 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,8 @@ kubebuilder types):
252252
It MUST correspond to one of the valid enum values of the discriminator's enum
253253
type. It defaults to the go (i.e `CamelCase`) representation of the field name if not specified.
254254
`<memberName>` should only be set if authors want to customize how the fields
255-
are represented in the discriminator field.
255+
are represented in the discriminator field. `<memberName>` should match the
256+
serialized JSON name of the field case-insensitively.
256257
- `// +unionDiscriminatedBy=<discriminatorName>` before a member field identifies which
257258
discriminator (and thus which union) the member field belongs to. Optional
258259
unless there are multiple unions/discriminators in a single struct. If used,
@@ -297,24 +298,24 @@ Below is an example of how to define a union based on the above design
297298
type UnionType string
298299
299300
const (
300-
FieldA UnionType = "FieldA"
301-
FieldB = "FieldB"
302-
FieldC = "FieldC"
303-
FieldD = "FieldD"
304-
FieldNone = ""
301+
FieldA UnionType = "FieldA"
302+
FieldB UnionType = "FieldB"
303+
FieldC UnionType = "FieldC"
304+
FieldD UnionType = "FieldD"
305+
FieldNone UnionType = ""
305306
)
306307
307308
type Union struct {
308-
// +unionDiscriminator
309-
// +required
310-
UnionType UnionType
311-
312-
// +unionMember
313-
// +optional
314-
FieldA int
315-
// +unionMember
316-
// +optional
317-
FieldB int
309+
// +unionDiscriminator
310+
// +required
311+
UnionType UnionType
312+
313+
// +unionMember
314+
// +optional
315+
FieldA int
316+
// +unionMember
317+
// +optional
318+
FieldB int
318319
}
319320
```
320321

@@ -325,91 +326,91 @@ same structure), examples of what is allowed:
325326
```
326327
// This will have one embedded union.
327328
type TopLevelUnion struct {
328-
Name string `json:"name"`
329+
Name string `json:"name"`
329330
330-
Union `json:",inline"`
331+
Union `json:",inline"`
331332
}
332333
333334
// +enum
334335
type UnionType string
335336
336337
const (
337-
FieldA UnionType = "FieldA"
338-
FieldB = "FieldB"
339-
FieldC = "FieldC"
340-
FieldD = "FieldD"
341-
FieldNone = ""
338+
FieldA UnionType = "FieldA"
339+
FieldB UnionType = "FieldB"
340+
FieldC UnionType = "FieldC"
341+
FieldD UnionType = "FieldD"
342+
FieldNone UnionType = ""
342343
)
343344
344345
// This will generate one union, with two fields and a discriminator.
345346
type Union struct {
346-
// +unionDiscriminator
347-
// +required
348-
UnionType UnionType `json:"unionType"`
349-
350-
// +unionMember
351-
// +optional
352-
FieldA int `json:"fieldA"`
353-
// +unionMember
354-
// +optional
355-
FieldB int `json:"fieldB"`
347+
// +unionDiscriminator
348+
// +required
349+
UnionType UnionType `json:"unionType"`
350+
351+
// +unionMember
352+
// +optional
353+
FieldA int `json:"fieldA"`
354+
// +unionMember
355+
// +optional
356+
FieldB int `json:"fieldB"`
356357
}
357358
358359
// +enum
359360
type Union2Type string
360361
361362
const (
362-
Alpha Union2Type = "ALPHA"
363-
Beta = "BETA"
363+
Alpha Union2Type = "ALPHA"
364+
Beta = "BETA"
364365
)
365366
366367
// This will generate one union that can be embedded because the members explicitly define their discriminator.
367368
// Also, the unionMember markers here demonstrate how to customize the names used for
368369
each field in the discriminator.
369370
type Union2 struct {
370-
// +unionDiscriminator
371-
// +required
372-
Type2 Union2Type `json:"type"`
373-
// +unionMember=ALPHA,
374-
// +unionDiscriminatedBy=Type2
375-
// +optional
376-
Alpha int `json:"alpha"`
377-
// +unionMember=BETA
378-
// +unionDiscriminatedBy=Type2
379-
// +optional
380-
Beta int `json:"beta"`
371+
// +unionDiscriminator
372+
// +required
373+
Type2 Union2Type `json:"type"`
374+
// +unionMember=ALPHA,
375+
// +unionDiscriminatedBy=Type2
376+
// +optional
377+
Alpha int `json:"alpha"`
378+
// +unionMember=BETA
379+
// +unionDiscriminatedBy=Type2
380+
// +optional
381+
Beta int `json:"beta"`
381382
}
382383
383384
// +enum
384385
type FieldType string
385386
386387
const (
387-
Field1 FieldType = "Field1"
388-
Field2 = "Field2"
389-
FieldNone = "None"
388+
Field1 FieldType = "Field1"
389+
Field2 = "Field2"
390+
FieldNone = "None"
390391
)
391392
392393
// This has 3 embedded unions:
393394
// One for the fields that are directly embedded, one for Union, and one for Union2.
394395
type InlinedUnion struct {
395-
Name string `json:"name"`
396-
397-
// +unionDiscriminator
398-
// +required
399-
FieldType FieldType `json:"fieldType"`
400-
// +unionMember
401-
// +unionDiscriminatedBy=FieldType
402-
// +optional
403-
Field1 *int `json:"field1,omitempty"`
404-
// +unionMember
405-
// +unionDiscriminatedBy=FieldType
406-
// +optional
407-
Field2 *int `json:"field2,omitempty"`
408-
409-
// Union does not label its members, so it
410-
cannot be inlined
411-
union Union `json:"union"`
412-
Union2 `json:",inline"`
396+
Name string `json:"name"`
397+
398+
// +unionDiscriminator
399+
// +required
400+
FieldType FieldType `json:"fieldType"`
401+
// +unionMember
402+
// +unionDiscriminatedBy=FieldType
403+
// +optional
404+
Field1 *int `json:"field1,omitempty"`
405+
// +unionMember
406+
// +unionDiscriminatedBy=FieldType
407+
// +optional
408+
Field2 *int `json:"field2,omitempty"`
409+
410+
// Union does not label its members, so it
411+
cannot be inlined
412+
union Union `json:"union"`
413+
Union2 `json:",inline"`
413414
}
414415
```
415416

@@ -438,42 +439,40 @@ Conversion between OpenAPI v2 and OpenAPI v3 will preserve these fields.
438439

439440
Normalization refers to the process by which the API server attempts to understand
440441
and correct clients which may provide the server with conflicting or incomplete
441-
information about a union.
442+
information about a union in update or patch requests.
442443

443444
Issues primarily arise here because of version skew between a client and a server,
444445
such as when a client is unaware of new fields added to a union and thus doesn't
445446
know how to clear these new fields when trying to set a different field.
446447

447-
For unions with a discriminator, normalization is simple: the server should always respect the
448+
For unions that follow this design, normalization is simple: the server should always respect the
448449
discriminator.
449450

450-
This means two things:
451-
1. when the server receives a request with a discriminator set to a
452-
given field, and multiple member fields are set it should:
453-
a. If the discriminator has been modified from the one set in the old object,
454-
the server should clear all fields except the one pointed to by the discriminator.
455-
b. If the discriminator is unchanged but a new member field has been set (in
456-
addition to the existing one that has not been cleared), it should error
457-
loudly that the client must change the discriminator if it changes any union
458-
member fields.
459-
2. when the server receives a request with a discriminator set to a given field,
460-
but that given field is empty, the server should fail with a clear error
461-
message.
462-
463-
For situations where a typed client is unaware of a new field (that is currently
464-
set) and drops the set field such that no fields are now set, the server will
465-
clear all the fields of the union. This is unavoidable for unions without a
466-
discriminator and a major reason why we recommend all new unions have a
467-
discriminator.
451+
This means that when the server receives an update request with a discriminator set to a
452+
given field, and multiple member fields are set it should clear all fields
453+
except the one pointed to by the discriminator _if and only if_ the
454+
discriminator has been modified. Having multiple fields set, and a discriminator
455+
not modified is invalid and caught later by the validation step (see below).
468456

469-
Union types without a discriminator must continue to perform normalization and
470-
validation manually (i.e. per resource in the validation functions), as is
471-
currently done for existing union types.
457+
For both custom resources and built-in types, we expect union normalization to be
458+
called by the request handlers shortly after mutating admission occurs.
472459

473460
#### Validation
474461

475462
Objects must be validated AFTER the normalization process.
476463

464+
Some validation situations specific to unions are:
465+
1. When multiple union fields are set and the discriminator is not set we should
466+
error loudly that the client must change the discriminator if it changes any
467+
union member fields.
468+
2. When the server receiveds a request with a discriminator set to a given
469+
field, but that given field is empty, the server should fail with a clear
470+
error message. Note this does not apply to discriminator values that do not
471+
correspond to any field (as in the "empty union members case").
472+
473+
For both custom resources and built-in types, validation will occur as part of
474+
the request validation, before validating admission occurs.
475+
477476
For custom resources, union validation will be done at the same point as the
478477
existing structural schema validation that occurs in the custom resource handler.
479478
This ensures that any generic validation changes made to all custom resources (such
@@ -493,7 +492,8 @@ as an alternative. Ratcheting validation means that objects will ignore stricter
493492
validation rules if and only if the existing object also fails the stricter
494493
validation for the same reason.
495494

496-
Ratcheting validation for custom resources is a separate effort proposed
495+
Ratcheting validation for custom resources is a [separate
496+
effort](https://github.com/kubernetes/kubernetes/issues/94060) proposed
497497
outside of this unions effort. For the initial alpha graduation of unions, we
498498
do not propose supporting ratcheting validation. We will require all invalid CRs
499499
to be made valid before they can be updated (the naive solution).
@@ -519,6 +519,15 @@ initial migration.
519519
For non-discriminated unions, there are a few relatively straightforward types
520520
that make good candidates for initial migration, such as `ContainerState`
521521

522+
Until migrated, union types without a discriminator (i.e. only existing unions that have not been migrated
523+
to the current desgin), cannot be tagged with the go markers described above and
524+
thus will not be treated as "unions" in the sense of this currently proposed
525+
normalization and validation logic.
526+
527+
These legacy unions must continue to perform normalization and
528+
validation manually, per resource in the validation functions.
529+
530+
522531
### Test Plan
523532

524533
[x] I/we understand the owners of the involved components may require updates to

0 commit comments

Comments
 (0)