Skip to content

Commit 1c1f7d2

Browse files
committed
KEP-5073: Declarative Validation: Explain and update document with cross-field validation information
1 parent 42cd64c commit 1c1f7d2

File tree

1 file changed

+66
-86
lines changed
  • keps/sig-api-machinery/5073-declarative-validation-with-validation-gen

1 file changed

+66
-86
lines changed

keps/sig-api-machinery/5073-declarative-validation-with-validation-gen/README.md

Lines changed: 66 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
- [Proposal](#proposal)
99
- [Overview](#overview)
1010
- [Introduce <code>validation-gen</code>](#introduce-validation-gen)
11-
- [<code>validation-gen</code> Has No Plans To Use CEL Validation Directly](#validation-gen-has-no-plans-to-use-cel-validation-directly)
11+
- [<code>validation-gen</code>'s Approach to Dedicated Tags and Escape Hatch Tags](#validation-gens-approach-to-dedicated-tags-and-escape-hatch-tags)
1212
- [IDL Tag Authoring DevEx and User Error Messaging](#idl-tag-authoring-devex-and-user-error-messaging)
1313
- [Introduce new validation tests and test framework](#introduce-new-validation-tests-and-test-framework)
1414
- [New Validations Vs Migrating Validations](#new-validations-vs-migrating-validations)
@@ -73,6 +73,8 @@
7373
- [Status-Type Subresources](#status-type-subresources)
7474
- [Scale-Type Subresources](#scale-type-subresources)
7575
- [Streaming Subresources](#streaming-subresources)
76+
- [Cross-Field Validation](#cross-field-validation)
77+
- [Cross-Field Validation and Ratcheting](#cross-field-validation-and-ratcheting)
7678
- [Ratcheting](#ratcheting)
7779
- [Core Principles](#core-principles)
7880
- [Default Ratcheting Behavior](#default-ratcheting-behavior)
@@ -276,9 +278,8 @@ Please feel free to try out the [prototype](https://github.com/jpbetz/kubernetes
276278

277279
`validation-gen` will parse structured comments (IDL tags) within Kubernetes API type definitions (types.go files) and generate corresponding Go validation functions. `validation-gen` will be built as an extensible framework allowing new "Validators" to be added by describing what IDL tags they parse, the constraints on the IDL tags (for UX error messaging), the format of the IDL tag + how it is used (for documentation), and what actual validation logic will be for the generated code given the tagged field and associated args. The generators validators will be registered with the scheme in a similar way to generated conversion and defaulting.
278280

279-
#### `validation-gen` Has No Plans To Use CEL Validation Directly
280-
281-
The previous Declarative Validation proposal ([KEP-4153](https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/4153-declarative-validation)) proposed using CEL for a number of the complex validations present in the current Kubernetes validation logic for native types (cross-field, transition, etc.). The `validation-gen` solution presented here uses go code directly for the validations which means we do not plan on evaluating CEL server side as we can write arbitrary go code to perform server side validations. This allows the `validation-gen` solution to be highly flexible and performant. If we reach a point where CEL makes sense, we can evaluate it at that time.
281+
#### `validation-gen`'s Approach to Dedicated Tags and Escape Hatch Tags
282+
The development and use of an "escape hatch" tag (CEL expression based tag, etc.) is to only be attempted after a rigorous attempt to use, enhance, or propose a dedicated tag. The goal is to use dedicated IDL tags for the vast majority of validations, ensuring that CEL is reserved for exceptional cases if used at all. Over time, if CEL appears necessary for a validation, additional discussion will occur to prioritize creating a dedicated CEL expression tag. Currently the aim is to get as far as possible with no CEL for declarative validation tags. This principal mitigates concerns about the potential for overuse of a CEL escape hatch tag and the associated review complexity for common validation rules.
282283

283284
#### IDL Tag Authoring DevEx and User Error Messaging
284285

@@ -606,13 +607,13 @@ The below rules are currently implemented or are very similar to an existing val
606607
</tr>
607608
<tr>
608609
<td>
609-
numeric limits
610+
numeric limits (constant or field refs capable)
610611
</td>
611612
<td>
612613
`+k8s:minimum`, `+k8s:maximum`, `+k8s:exclusiveMinimum`, `+k8s:exclusiveMaximum`
613614
</td>
614615
<td>
615-
`minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`
616+
minimum, maximum, exclusiveMinimum, exclusiveMaximum (for constants); x-kubernetes-validations (for field refs)
616617
</td>
617618
</tr>
618619
<tr>
@@ -727,108 +728,37 @@ The below rules are currently implemented or are very similar to an existing val
727728
N/A
728729
</td>
729730
</tr>
730-
<tr>
731-
<td style="background-color: null">
732-
immutable after set
733-
</td>
734-
<td style="background-color: null">
735-
`+k8s:immutable`
736-
</td>
737-
<td style="background-color: null">
738-
N/A
739-
</td>
740-
</tr>
741-
<tr>
742-
<td style="background-color: null">
743-
required once set
744-
</td>
745-
<td style="background-color: null">
746-
`+k8s:requiredOnceSet`
747-
</td>
748-
<td style="background-color: null">
749-
N/A
750-
</td>
751-
</tr>
752-
<tr>
753-
<td style="background-color: null">
754-
immutable(frozen) at creation
755-
</td>
756-
<td style="background-color: null">
757-
`+k8s:frozen`
758-
</td>
759-
<td style="background-color: null">
760-
N/A
761-
</td>
762-
</tr>
763-
<tr>
764-
<td style="background-color: null">
765-
group membership (virtual field)
766-
</td>
767-
<td style="background-color: null">
768-
`+k8s:memberOf(group: &lt;groupname>)`
769-
</td>
770-
<td style="background-color: null">
771-
N/A
772-
</td>
773-
</tr>
774-
<tr>
775-
<td style="background-color: null">
776-
list map item reference (virtual field)
777-
</td>
778-
<td style="background-color: null">
779-
`+k8s:listMapItem(list-map-key-field-name: value,...])`
780-
</td>
781-
<td style="background-color: null">
782-
N/A
783-
</td>
784-
</tr>
785-
</table>
786-
787-
The below rules are not currently implemented in the [validation-gen prototype](https://github.com/jpbetz/kubernetes/tree/validation-gen) so the exact syntax is still WIP
788-
789-
<table>
790731
<tr>
791732
<td>
792-
<strong>Type of validation</strong>
733+
union member (Discriminated/Non-Discriminated)
793734
</td>
794735
<td>
795-
<strong>IDL tag</strong>
736+
+k8s:unionMember={"union": ""}
796737
</td>
797738
<td>
798-
<strong>Relative OpenAPI validation field</strong>
739+
x-kubernetes-unions
799740
</td>
800741
</tr>
801742
<tr>
802743
<td>
803-
regex matches
744+
union discriminator
804745
</td>
805746
<td>
806-
`+k8s:pattern`
747+
+k8s:unionDiscriminator={"union": ""}
807748
</td>
808749
<td>
809-
`pattern`
750+
x-kubernetes-unions
810751
</td>
811752
</tr>
812753
<tr>
813754
<td>
814-
cross field validation
755+
immutable value
815756
</td>
816757
<td>
817-
`TBD
758+
+k8s:immutable
818759
</td>
819760
<td>
820-
`x-kubernetes-validations`
821-
</td>
822-
</tr>
823-
<tr>
824-
<td>
825-
<a href="https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#transition-rules">transition rules</a>
826-
</td>
827-
<td>
828-
`TBD
829-
</td>
830-
<td>
831-
`x-kubernetes-validations`
761+
x-kubernetes-validations (CEL: !has(oldSelf...) || self... == oldSelf...)
832762
</td>
833763
</tr>
834764
</table>
@@ -1424,6 +1354,56 @@ is not stored, the use case is much simpler and only requires the "Subresource V
14241354

14251355
The streamed data does not require declarative validation, as it is not structured resource data.
14261356

1357+
### Cross-Field Validation
1358+
1359+
A cross-field validation refers to any validation rule whose outcome depends on the value of more than one field within a given Kubernetes API object, potentially including fields from the previous version of the object (`oldSelf`) or external options (namely feature gates). This differs from single-field validation which only considers the value of the field where the validation tag is placed.
1360+
1361+
These types of validations often have more complex logic and can be more difficult UX-wise to create a dedicated tag for as there are more options for representing them (tag directly on N fields, tag on one of the fields with args for the other fields, on the parent struct with args for all fields, etc.). From an analysis of current validation logic in `kubernetes/kubernetes` across native types in `pkg/apis`, a number of validation categories were identified:
1362+
1363+
* Conditional Requirement/Validation
1364+
* Non-Discriminated Unions
1365+
* Discriminated Unions
1366+
* List/Map Integrity
1367+
* Comparison
1368+
* Status Condition Validation
1369+
* Format/Value Dependencies
1370+
* Rules where the validity or format of one field depends directly on the value of another field (or a value calculated from other fields). Includes: Checking if a generated string (like hostname + index) forms a valid DNS label, validating a field based on a prefix derived from another (like metadata name), or validating a field against a calculated aggregate value (like commonEncodingVersion).
1371+
* Transition Rules (Immutability, Ratcheting, etc.)
1372+
* At least "oneOf" Required
1373+
* Co-occurrence Requirements
1374+
* Rules defining relationships where fields must appear together, be consistent if both present, or satisfy a bi-directional implication (A if and only if B).
1375+
* Complex/Custom Logic
1376+
1377+
From this list of categories, the goal for Declarative Validation is to create dedicated tags capable of handling these categories similarly/identically to the current validation logic. The table in "Catalog of Supported Validation Rules & Associated IDL Tags" includes a number of these cross-field validation tags targeting the above categories including:
1378+
1379+
* Non-Discriminated & Discriminated Unions: `+k8s:union[Member|Discriminator]`
1380+
* Comparison: `+k8s:[minimum|maximum]` w/ field ref support
1381+
* Transition Rules - Immutability: `+k8s:immutable`.
1382+
1383+
Some categories can be covered by extending and or/chaining a combination of tags. For example “Status Condition Validation” can, for identified cases (example below for CertificateSigningRequest), be handled using a combination of `+k8s:subfield` (enhanced to support targeting list entries) and `+k8s:unionMember`:
1384+
1385+
```go
1386+
type CertificateSigningRequestStatus struct {
1387+
// +k8s:subfield({"type":"Approved"})=+k8s:optional
1388+
// +k8s:subfield({"type":"Approved", "status": "true"})=+k8s:unionMember
1389+
1390+
// +k8s:subfield({"type":"Denied"})=+k8s:optional
1391+
// +k8s:subfield({"type":"Denied", "status": "true"})=+k8s:unionMember
1392+
1393+
// +k8s:subfield({"type":"Failed"})=+k8s:optional
1394+
// +k8s:subfield({"type":"Failed"})=+k8s:immutable
1395+
Conditions []CertificateSigningRequestCondition
1396+
}
1397+
```
1398+
1399+
#### Cross-Field Validation and Ratcheting
1400+
For cross-field validations, the validation logic is evaluated at the common ancestor of the fields involved. This approach is necessary for supporting ratcheting. While validation tags (eg: +k8s:maximum=siblingField, +k8s:unionMember , etc.) may be placed on an individual field for clarity, the tag and its associated validation logic will be "hoisted" to the parent struct during code generation. This "hoisting" means the validation is treated as if it were defined on the common ancestor. By anchoring the cross-field alidation logic at the common ancestor, regardless of tag placement, the ratcheting design can more reliably determine how to perform equality checks across the relevant type nodes and decide if re-validation is necessary.
1401+
1402+
As noted in the Ratcheting section there is an additional challenge that arises if a cross-field validation rule (e.g. X < Y) is defined on a common ancestor struct/field, and an unrelated field (e.g. Z) within that same ancestor is modified. This change to Z makes the common ancestor “changed” overall, triggering re-validation of the X < Y rule. If this rule was recently evolved, it might now fail even if X and Y themselves are not modified by the user’s update. This could violate the principle “Unchanged fields do not cause update rejections”. In practice this means that the validation rules (or validation-gen generally) might have to be more explicit where each validation rule explains “I only care about these fields for ratcheting”.
1403+
1404+
For the initial implementation, this behavior will be documented, and cross-field validation rules must handle ratcheting themselves. This means that in the initial implementation of the cross-field dedicated tags referenced in the document (+k8s:unionMember, etc.), they will handle ratcheting of the fields they operate on directly. See the Ratcheting section for more information on this issue as well as longer term plans on addressing this challenge.
1405+
1406+
14271407
### Ratcheting
14281408

14291409
As Kubernetes APIs evolve, validation rules change. To minimize disruption for users with existing objects created under older rules, declarative validation will incorporate **Validation Ratcheting**. This mechanism aims to selectively bypass new or changed validation rules during object updates (`UPDATE`, `PATCH`, `APPLY`) for fields that have not been modified from their previously persisted state.

0 commit comments

Comments
 (0)