@@ -252,7 +252,8 @@ kubebuilder types):
252
252
It MUST correspond to one of the valid enum values of the discriminator's enum
253
253
type. It defaults to the go (i.e ` CamelCase ` ) representation of the field name if not specified.
254
254
` <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.
256
257
- ` // +unionDiscriminatedBy=<discriminatorName> ` before a member field identifies which
257
258
discriminator (and thus which union) the member field belongs to. Optional
258
259
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
297
298
type UnionType string
298
299
299
300
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 = ""
305
306
)
306
307
307
308
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
318
319
}
319
320
```
320
321
@@ -325,91 +326,91 @@ same structure), examples of what is allowed:
325
326
```
326
327
// This will have one embedded union.
327
328
type TopLevelUnion struct {
328
- Name string `json:"name"`
329
+ Name string `json:"name"`
329
330
330
- Union `json:",inline"`
331
+ Union `json:",inline"`
331
332
}
332
333
333
334
// +enum
334
335
type UnionType string
335
336
336
337
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 = ""
342
343
)
343
344
344
345
// This will generate one union, with two fields and a discriminator.
345
346
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"`
356
357
}
357
358
358
359
// +enum
359
360
type Union2Type string
360
361
361
362
const (
362
- Alpha Union2Type = "ALPHA"
363
- Beta = "BETA"
363
+ Alpha Union2Type = "ALPHA"
364
+ Beta = "BETA"
364
365
)
365
366
366
367
// This will generate one union that can be embedded because the members explicitly define their discriminator.
367
368
// Also, the unionMember markers here demonstrate how to customize the names used for
368
369
each field in the discriminator.
369
370
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"`
381
382
}
382
383
383
384
// +enum
384
385
type FieldType string
385
386
386
387
const (
387
- Field1 FieldType = "Field1"
388
- Field2 = "Field2"
389
- FieldNone = "None"
388
+ Field1 FieldType = "Field1"
389
+ Field2 = "Field2"
390
+ FieldNone = "None"
390
391
)
391
392
392
393
// This has 3 embedded unions:
393
394
// One for the fields that are directly embedded, one for Union, and one for Union2.
394
395
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"`
413
414
}
414
415
```
415
416
@@ -438,42 +439,40 @@ Conversion between OpenAPI v2 and OpenAPI v3 will preserve these fields.
438
439
439
440
Normalization refers to the process by which the API server attempts to understand
440
441
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 .
442
443
443
444
Issues primarily arise here because of version skew between a client and a server,
444
445
such as when a client is unaware of new fields added to a union and thus doesn't
445
446
know how to clear these new fields when trying to set a different field.
446
447
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
448
449
discriminator.
449
450
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).
468
456
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.
472
459
473
460
#### Validation
474
461
475
462
Objects must be validated AFTER the normalization process.
476
463
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
+
477
476
For custom resources, union validation will be done at the same point as the
478
477
existing structural schema validation that occurs in the custom resource handler.
479
478
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
493
492
validation rules if and only if the existing object also fails the stricter
494
493
validation for the same reason.
495
494
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
497
497
outside of this unions effort. For the initial alpha graduation of unions, we
498
498
do not propose supporting ratcheting validation. We will require all invalid CRs
499
499
to be made valid before they can be updated (the naive solution).
@@ -519,6 +519,15 @@ initial migration.
519
519
For non-discriminated unions, there are a few relatively straightforward types
520
520
that make good candidates for initial migration, such as ` ContainerState `
521
521
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
+
522
531
### Test Plan
523
532
524
533
[ x] I/we understand the owners of the involved components may require updates to
0 commit comments