Skip to content

Commit ff2a6da

Browse files
committed
addressing review comments
1 parent 39cc5ea commit ff2a6da

File tree

1 file changed

+94
-58
lines changed
  • keps/sig-api-machinery/5073-declarative-validation-with-validation-gen

1 file changed

+94
-58
lines changed

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

Lines changed: 94 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@
6262
- [List Operations](#list-operations)
6363
- [Conditional Immutability](#conditional-immutability)
6464
- [Subresource-Specific Immutability](#subresource-specific-immutability)
65+
- [Cross-Field Validation](#cross-field-validation)
66+
- [Handling Ratcheting In Cross-Field Validation Tags](#handling-ratcheting-in-cross-field-validation-tags)
6567
- [Referencing Fields in Validation-Gen For Cross-Field Validation Rules](#referencing-fields-in-validation-gen-for-cross-field-validation-rules)
6668
- [Field Reference Strategies](#field-reference-strategies)
6769
- [Field Path Strategy](#field-path-strategy)
@@ -73,8 +75,6 @@
7375
- [Status-Type Subresources](#status-type-subresources)
7476
- [Scale-Type Subresources](#scale-type-subresources)
7577
- [Streaming Subresources](#streaming-subresources)
76-
- [Cross-Field Validation](#cross-field-validation)
77-
- [Handling Ratcheting In Cross-Field Validation Tags](#handling-ratcheting-in-cross-field-validation-tags)
7878
- [Ratcheting](#ratcheting)
7979
- [Core Principles](#core-principles)
8080
- [Default Ratcheting Behavior](#default-ratcheting-behavior)
@@ -613,7 +613,7 @@ The below rules are currently implemented or are very similar to an existing val
613613
`+k8s:minimum`, `+k8s:maximum`, `+k8s:exclusiveMinimum`, `+k8s:exclusiveMaximum`
614614
</td>
615615
<td>
616-
minimum, maximum, exclusiveMinimum, exclusiveMaximum (for constants); x-kubernetes-validations (for field refs)
616+
`minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum` (for constants); x-kubernetes-validations (for field refs)
617617
</td>
618618
</tr>
619619
<tr>
@@ -751,19 +751,62 @@ The below rules are currently implemented or are very similar to an existing val
751751
</td>
752752
</tr>
753753
<tr>
754-
<td>
755-
immutable value
754+
<td style="background-color: null">
755+
immutable after set
756756
</td>
757-
<td>
758-
+k8s:immutable
757+
<td style="background-color: null">
758+
`+k8s:immutable`
759759
</td>
760-
<td>
761-
x-kubernetes-validations (CEL: !has(oldSelf...) || self... == oldSelf...)
760+
<td style="background-color: null">
761+
N/A
762+
</td>
763+
</tr>
764+
<tr>
765+
<td style="background-color: null">
766+
required once set
767+
</td>
768+
<td style="background-color: null">
769+
`+k8s:requiredOnceSet`
770+
</td>
771+
<td style="background-color: null">
772+
N/A
773+
</td>
774+
</tr>
775+
<tr>
776+
<td style="background-color: null">
777+
immutable(frozen) at creation
778+
</td>
779+
<td style="background-color: null">
780+
`+k8s:frozen`
781+
</td>
782+
<td style="background-color: null">
783+
N/A
784+
</td>
785+
</tr>
786+
<tr>
787+
<td style="background-color: null">
788+
group membership (virtual field)
789+
</td>
790+
<td style="background-color: null">
791+
`+k8s:memberOf(group: &lt;groupname>)`
792+
</td>
793+
<td style="background-color: null">
794+
N/A
795+
</td>
796+
</tr>
797+
<tr>
798+
<td style="background-color: null">
799+
list map item reference (virtual field)
800+
</td>
801+
<td style="background-color: null">
802+
`+k8s:listMapItem(list-map-key-field-name: value,...])`
803+
</td>
804+
<td style="background-color: null">
805+
N/A
762806
</td>
763807
</tr>
764808
</table>
765809

766-
767810
### Supporting Declarative Validation IDL tags On Shared Struct Fields
768811

769812
IDL tags may be used directly on type declarations and indirectly on field and type aliases. For example:
@@ -1135,6 +1178,47 @@ type PodSpec struct {
11351178
NodeName string `json:"nodeName,omitempty"`
11361179
}
11371180
```
1181+
1182+
### Cross-Field Validation
1183+
1184+
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).
1185+
This differs from single-field validation which only considers the value of the field where the validation tag is placed.
1186+
1187+
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.).
1188+
From an analysis of current validation logic in `kubernetes/kubernetes` across native types in `pkg/apis`, a number of validation categories were identified:
1189+
1190+
* Conditional Requirement/Validation
1191+
* Non-Discriminated Unions
1192+
* Discriminated Unions
1193+
* List/Map Integrity
1194+
* Comparison
1195+
* Status Condition Validation
1196+
* Format/Value Dependencies
1197+
* 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).
1198+
* Transition Rules (Immutability, Ratcheting, etc.)
1199+
* At least "oneOf" Required
1200+
* Co-occurrence Requirements
1201+
* 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).
1202+
* Complex/Custom Logic
1203+
1204+
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.
1205+
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:
1206+
1207+
* Non-Discriminated & Discriminated Unions: `+k8s:union[Member|Discriminator]`
1208+
* Comparison: `+k8s:[minimum|maximum]` w/ field ref support
1209+
* Transition Rules - Immutability: `+k8s:immutable`.
1210+
1211+
1212+
#### Handling Ratcheting In Cross-Field Validation Tags
1213+
For cross-field validations, the validation logic is evaluated at the common ancestor of the fields involved.
1214+
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.
1215+
This "hoisting" means the validation is treated as if it were defined on the common ancestor.
1216+
By anchoring the cross-field validation 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.
1217+
1218+
As noted in the "Ratcheting and Cross-Field Validation" 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 (see section for more information).
1219+
In practice this means that the validation rules (or validation-gen generally) need to be more explicit where each validation rule explains “I only use these fields as inputs" for ratcheting.
1220+
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.
1221+
11381222
### Referencing Fields in Validation-Gen For Cross-Field Validation Rules
11391223

11401224
Cross-field validation refers to validation rules that depend on the values of multiple fields within a struct or across nested structs. Cross-field validations require a method for referencing additional fields from validation-gen IDL tags.
@@ -1354,54 +1438,6 @@ is not stored, the use case is much simpler and only requires the "Subresource V
13541438

13551439
The streamed data does not require declarative validation, as it is not structured resource data.
13561440

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-
#### Handling Ratcheting In Cross-Field Validation Tags
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 validation 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 and Cross-Field Validation" 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 (see section for more information). In practice this means that the validation rules (or validation-gen generally) need to be more explicit where each validation rule explains “I only use these fields as inputs" for ratcheting. 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.
1403-
1404-
14051441
### Ratcheting
14061442

14071443
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)