Skip to content

Commit abd9ff7

Browse files
committedJan 30, 2025
resolving KEP comments round kubernetes#3

File tree

1 file changed

+70
-62
lines changed
  • keps/sig-api-machinery/5073-declarative-validation-with-validation-gen

1 file changed

+70
-62
lines changed
 

Diff for: ‎keps/sig-api-machinery/5073-declarative-validation-with-validation-gen/README.md

+70-62
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,17 @@
4242
- [<code>validation-gen</code> Implementation Plan](#validation-gen-implementation-plan)
4343
- [Catalog of Supported Validation Rules &amp; Associated IDL Tags](#catalog-of-supported-validation-rules--associated-idl-tags)
4444
- [Supporting Declarative Validation IDL tags On Shared Struct Fields](#supporting-declarative-validation-idl-tags-on-shared-struct-fields)
45-
- [<code>validation-gen</code> One-deep typedef support](#validation-gen-one-deep-typedef-support)
46-
- [Proposed Options](#proposed-options)
4745
- [<code>subfield</code> IDL Tag](#subfield-idl-tag)
46+
- [<code>validation-gen</code> One-deep typedef support](#validation-gen-one-deep-typedef-support)
47+
- [Proposed Solutions](#proposed-solutions)
4848
- [Migration Plan](#migration-plan)
4949
- [Phase1: Initialization (Responsibility of Contributors Implementing the KEP)](#phase1-initialization-responsibility-of-contributors-implementing-the-kep)
5050
- [Phase2: Scaling the Migration (Responsibility of Contributors Implementing the KEP and broader community)](#phase2-scaling-the-migration-responsibility-of-contributors-implementing-the-kep-and-broader-community)
5151
- [Phase3: Finalization and GA (Core Team and community)](#phase3-finalization-and-ga-core-team-and-community)
5252
- [Tagging and Validating Against Internal vs Versioned Types](#tagging-and-validating-against-internal-vs-versioned-types)
5353
- [Handling Zero Values in Declarative Validation](#handling-zero-values-in-declarative-validation)
5454
- [Difficulties with <code>+required</code> and <code>+default</code>](#difficulties-with-required-and-default)
55-
- [Proposed Solutions](#proposed-solutions)
55+
- [Proposed Solutions](#proposed-solutions-1)
5656
- [Addressing the Problem with Valid Zero Values Using the Linter](#addressing-the-problem-with-valid-zero-values-using-the-linter)
5757
- [Ratcheting](#ratcheting)
5858
- [Test Plan](#test-plan)
@@ -145,7 +145,7 @@ Declarative validation will also benefit Kubernetes users:
145145

146146
* Adding new fields and associated validation rules becomes a simpler process of adding IDL tags to the field definition, rather than writing and maintaining validation functions. This reduces potential inconsistencies and bugs. Testing is also streamlined as centralized rules and frameworks enable the use of test fixtures across k8s types making creating tests simpler and faster to implement for contributors.
147147
* Creating k8s APIs becomes faster as developers can focus on the API’s structure and behavior, leaving the validation boilerplate to be generated automatically. This speeds up development and encourages experimentation with new APIs.
148-
* Validation is performed on versioned types (assuming recommended approach is from design below), so error message and attributed field path is more likely to be relevant to the user. (Though no known cases of misattributed field errors have yet occurred)
148+
* Validation is performed on versioned types (assuming recommended approach is used from design below), so error message and attributed field path is more likely to be relevant to the user. (ex: some fields in autoscaling/v2 will be mis-identified with current approach but resolved w/ Declarative Validation + versioned types)
149149

150150
Additionally Declarative Validation can be built upon to support features such as:
151151

@@ -160,7 +160,7 @@ Please feel free to try out the [prototype](https://github.com/jpbetz/kubernetes
160160

161161
* Eliminate 90% of net-new hand-written validation within 5 kube releases (target start: v1.33)
162162
* Convert 50% of existing hand-written validation within 5 kube releases (target start: v1.33)
163-
* If we lose steam and abandon this, we can roll it back relatively easily.
163+
* Migrate in such a way that if contributors lose steam and abandon this, we can roll it back relatively easily.
164164
* `types.go` files become the de-facto IDL of Kubernetes for native types. It is worth noting that `+enum` support, `+default` support and similar enhancements all moved our API development forward in this direction. This enhancement is an attempt to continue that story arc.
165165
* API Validations are readable and manageable. The IDL should be a joy to use for API authors, reviewers and maintainers. Common complex relationships have simple-to-express idioms in the IDL.
166166
* Reduce API development costs for API authors and reviewers. Reduce long term maintainer costs. Reclaiming time from core project contributors.
@@ -169,7 +169,6 @@ Please feel free to try out the [prototype](https://github.com/jpbetz/kubernetes
169169
* Enable development of linters and other API tool chains that use API validation rules and metadata. Further reducing development effort and risk of incorrect validation rules.
170170
* Retain native (or nearly native) performance.
171171
* Improve testing rigor by being vastly easier to test.
172-
* Migrate in such a way that if contributors lose steam and abandon this, we can roll it back relatively easily.
173172

174173
### Non-Goals
175174

@@ -217,14 +216,14 @@ The previous Declarative Validation proposal ([KEP-4153](https://github.com/kube
217216

218217
In order to properly support the User Stories for “Kubernetes developer wishes to add a field to an existing API version” and “Kubernetes developer adds a new version an API” it is important that `validation-gen` and the associated tooling for IDL tags have robust UX that immediately notifies users when tags are not used properly. To support this `validation-gen` will have options for validators subject to when they register so that validation authors can express how their associated IDL tag should be used and the framework will error if a user uses an IDL tag incorrectly. See this related WIP PR [here](https://github.com/jpbetz/kubernetes/pull/89) adding such functionality to the prototype for an example of what this might look like.
219218

220-
The goal is that when a user makes a mistake in authoring IDL tags we give a meaning error. Users are not expected to know the underlying system or be an insider on the project to successfully use Declarative Validation.
219+
The goal is that when a user makes a mistake in authoring IDL tags we give a meaningful error. Users are not expected to know the underlying system or be an insider on the project to successfully use Declarative Validation.
221220

222221
### Introduce new validation tests and test framework
223222

224223
New validation tests will be created as part of the migration process for contributors migrating fields using `validation-gen`. Additionally, a test framework and associated migration test utilities will be created as part of `validation-gen` to leverage the new centralized validation rules and to ensure validation consistency across hand-written and declarative validation rules. See the [Test Plan](?tab=t.0#bookmark=id.a86sekfgbuen) section for more details.
225224

226225
#### New Validations Vs Migrating Validations
227-
`validation-gen`
226+
FIXME...
228227

229228
#### New Validation Tests
230229
As part of the process for migrating fields, contributors will scrutinize and improve as needed the current validation tests. No field can go thru migration without a robust test for the field being migrated, which proves that it is validated correctly before the change and after. Many existing tests are not sufficient to verify 100% equivalency and need retooling. This allows us to de-risk migration problems by scrutinizing the current tests and enhancing them.
@@ -501,87 +500,86 @@ The below rules are currently implemented or are very similar to an existing val
501500
string format
502501
</td>
503502
<td>
504-
<code>+k8s:format={format name}</code>
503+
`+k8s:format={format name}`
505504
</td>
506505
<td>
507-
<code>format</code>
506+
`format`
508507
</td>
509508
</tr>
510509
<tr>
511510
<td>
512511
size limits
513512
</td>
514513
<td>
515-
<code>+k8s:min{Length,Items}</code>, <code>+k8s:max{Length,Items}</code>
514+
`+k8s:min{Length,Items}`, `+k8s:max{Length,Items}`
516515
</td>
517516
<td>
518-
<code>min{Length,Items}</code>, <code>max{Length,Items}</code>
517+
`min{Length,Items}`, `max{Length,Items}`
519518
</td>
520519
</tr>
521520
<tr>
522521
<td>
523522
numeric limits
524523
</td>
525524
<td>
526-
<code>+k8s:minimum</code>, <code>+k8s:maximum</code>, <code>+k8s:exclusiveMinimum</code>, <code>+k8s:exclusiveMaximum</code>
525+
`+k8s:minimum`, `+k8s:maximum`, `+k8s:exclusiveMinimum`, `+k8s:exclusiveMaximum`
527526
</td>
528527
<td>
529-
<code>minimum</code>, <code>maximum</code>, <code>exclusiveMinimum</code>, <code>exclusiveMaximum</code>
528+
`minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`
530529
</td>
531530
</tr>
532531
<tr>
533532
<td>
534533
required fields
535534
</td>
536535
<td>
537-
<code>+k8s:optional</code>
536+
`+k8s:optional`
538537
<p>
539-
540-
<code>+k8s:required</code>
538+
`+k8s:required`
541539
</td>
542540
<td>
543-
<code>required</code>
541+
`required`
544542
</td>
545543
</tr>
546544
<tr>
547545
<td>
548546
enum values
549547
</td>
550548
<td>
551-
<code>+k8s:enum</code>
549+
`+k8s:enum`
552550
</td>
553551
<td>
554-
<code>enum</code>
552+
`enum`
555553
</td>
556554
</tr>
557555
<tr>
558556
<td style="background-color: null">
559557
Union values
560558
</td>
561559
<td style="background-color: null">
562-
<code>+k8s:unionMember</code> \
563-
<code>+k8s:unionDiscriminator</code>
560+
`+k8s:unionMember` \
561+
`+k8s:unionDiscriminator`
564562
</td>
565563
<td style="background-color: null">
566-
<code>oneOf,anyOf,allOf</code>
564+
`oneOf,anyOf,allOf`
567565
</td>
568566
</tr>
569567
<tr>
570568
<td style="background-color: null">
571569
forbidden values
572570
</td>
573571
<td style="background-color: null">
574-
<code>+k8s:forbidden</code>
572+
`+k8s:forbidden`
575573
</td>
576-
<td style="background-color: #ffff00">
574+
<td style="background-color: #null">
577575
</td>
578576
</tr>
579577
<tr>
580578
<td style="background-color: null">
581579
feature gate is enabled
582580
</td>
583581
<td style="background-color: null">
584-
<code>+k8s:ifOptionEnabled(FeatureX)=&lt;if-enabled-validator-tag></code>
582+
`+k8s:ifOptionEnabled(FeatureX)=&lt;if-enabled-validator-tag>`
585583
</td>
586584
<td style="background-color: null">
587585
N/A
@@ -592,7 +590,7 @@ The below rules are currently implemented or are very similar to an existing val
592590
feature gate is disabled
593591
</td>
594592
<td style="background-color: null">
595-
<code>+k8s:ifOptionDisabled(FeatureX)=&lt;if-disabled-validator-tag></code>
593+
`+k8s:ifOptionDisabled(FeatureX)=&lt;if-disabled-validator-tag>`
596594
</td>
597595
<td style="background-color: null">
598596
N/A
@@ -603,7 +601,7 @@ The below rules are currently implemented or are very similar to an existing val
603601
validate each key
604602
</td>
605603
<td style="background-color: null">
606-
<code>+k8s:eachKey=&lt;eachKey-validator-tag></code>
604+
`+k8s:eachKey=&lt;eachKey-validator-tag>`
607605
</td>
608606
<td style="background-color: null">
609607
N/A
@@ -614,7 +612,7 @@ The below rules are currently implemented or are very similar to an existing val
614612
validate each value
615613
</td>
616614
<td style="background-color: null">
617-
<code>+k8s:eachVal=&lt;eachVal-validator-tag></code>
615+
`+k8s:eachVal=&lt;eachVal-validator-tag>`
618616
</td>
619617
<td style="background-color: null">
620618
N/A
@@ -625,18 +623,18 @@ The below rules are currently implemented or are very similar to an existing val
625623
uniqueness
626624
</td>
627625
<td style="background-color: null">
628-
<code>+k8s:listType=&lt;type></code>
626+
`+k8s:listType=&lt;type>`
629627
</td>
630628
<td style="background-color: null">
631-
<code>x-kubernetes-list-type</code>
629+
`x-kubernetes-list-type`
632630
</td>
633631
</tr>
634632
<tr>
635633
<td style="background-color: null">
636634
shared struct fields (subfield)
637635
</td>
638636
<td style="background-color: null">
639-
<code>+k8s:subfield(subField-json-name)=&lt;subfield-validator-tag></code>
637+
`+k8s:subfield(subField-json-name)=&lt;subfield-validator-tag>`
640638
</td>
641639
<td style="background-color: null">
642640
N/A
@@ -663,32 +661,32 @@ The below rules are not currently implemented in the [validation-gen prototype](
663661
regex matches
664662
</td>
665663
<td>
666-
<code>+k8s:pattern</code>
664+
`+k8s:pattern`
667665
</td>
668666
<td>
669-
<code>pattern</code>
667+
`pattern`
670668
</td>
671669
</tr>
672670
<tr>
673671
<td>
674672
cross field validation
675673
</td>
676674
<td>
677-
<code>TBD
675+
`TBD
678676
</td>
679677
<td>
680-
<code>x-kubernetes-validations</code>
678+
`x-kubernetes-validations`
681679
</td>
682680
</tr>
683681
<tr>
684682
<td>
685683
<a href="https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#transition-rules">transition rules</a>
686684
</td>
687685
<td>
688-
<code>TBD
686+
`TBD
689687
</td>
690688
<td>
691-
<code>x-kubernetes-validations</code>
689+
`x-kubernetes-validations`
692690
</td>
693691
</tr>
694692
</table>
@@ -722,6 +720,24 @@ type Bar struct {
722720

723721
Shared types present a challenge. For example, different Kubernetes resources have different validation rules for `metadata.name` and `metadata.generateName`. But all resources share the `ObjectMeta` type.
724722

723+
#### `subfield` IDL Tag
724+
725+
To handle this case, we provide an IDL tag - `k8s:subfield(&lt;field-json-name>)` which can be used to specify a `subfield` validation to add to parent which validates against the the `subfield` value:
726+
727+
```go
728+
type Struct struct {
729+
// +k8s:subfield(name)=+k8s:format=dns-label
730+
metav1.ObjectMeta
731+
}
732+
```
733+
734+
This will also support chaining of subfield calls with other validators (including subfield) which allows for setting subfield validations on arbitrarily deep nested fields of shared structs. An exaggerated example showcasing this is below: \
735+
736+
```go
737+
// +k8s:subfield(sliceField)=+k8s:eachVal=+k8s:subfield(stringField)=+k8s:<desired-validaton-rule>
738+
```
739+
740+
725741
#### `validation-gen` One-deep typedef support
726742

727743
<<[UNRESOLVED is it ok that `validation-gen`'s currently design only supports one-deep typedef support? There are potential solutions to extend this, see below]>>
@@ -747,7 +763,7 @@ type Struct struct {
747763

748764
In the above example, FooField would be validated as a DNS label and have at least 4 characters which is expected. What might also be expected though is that BarField would be validated as a DNS label, have at least 4 characters, and have no more than 16 characters. INCORRECT! Due to Go's type system, the relationship of type Foo -> string is represented, but type Bar -> type Foo -> string is flattened to type Bar -> string. This leads to a currently open question around the severity of this potential UX issue as well potential solutions on how this could be mitigated if needed.
749765

750-
##### Proposed Options
766+
##### Proposed Solutions
751767

752768
1. **Have it well documented that `validation-gen`only support one-deep typedef:
753769
* Pro: Doesn't require any additional architectural changes to `validation-gen`.
@@ -763,22 +779,7 @@ In the above example, FooField would be validated as a DNS label and have at lea
763779
* Pro: Better UX for users as they are notified not to use IDL tags that might lead to unintended outcomes when adding IDL tags
764780
* Con: Requires implementing the chain of typedefs logic.
765781

766-
#### `subfield` IDL Tag
767-
768-
To handle this case, we provide an IDL tag - `k8s:subfield(&lt;field-json-name>)` which can be used to specify a `subfield` validation to add to parent which validates against the the `subfield` value:
769-
770-
```go
771-
type Struct struct {
772-
// +k8s:subfield(name)=+k8s:format=dns-label
773-
metav1.ObjectMeta
774-
}
775-
```
776-
777-
This will also support chaining of subfield calls with other validators (including subfield) which allows for setting subfield validations on arbitrarily deep nested fields of shared structs. An exaggerated example showcasing this is below: \
778-
779-
```go
780-
// +k8s:subfield(sliceField)=+k8s:eachVal=+k8s:subfield(stringField)=+k8s:<desired-validaton-rule>
781-
```
782+
<<[/UNRESOLVED]>>
782783

783784
### Migration Plan
784785

@@ -885,20 +886,23 @@ The straightforward approach of using the `+required` tag to enforce the presenc
885886

886887
#### Proposed Solutions
887888

888-
1. **Tri-State mutually exclusive options (Recommended): <code>+optional</code>, <code>+default</code>, <code>+required</code>:</strong>
889-
* Treat <code>+optional</code>, <code>+default</code>, and <code>+required</code> as mutually exclusive options.
890-
* Fields that allow valid zero values and have defaults would be explicitly tagged with neither <code>+optional</code> nor <code>+required</code>.
889+
1. <strong>Tri-State mutually exclusive options (Recommended): `+optional`, `+default`, `+required`:</strong>
890+
* Treat `+optional`, `+default`, and `+required` as mutually exclusive options.
891+
* Fields that allow valid zero values and have defaults would be explicitly tagged with neither `+optional` nor `+required`.
891892
* Validation logic would need to be aware of this and handle zero values appropriately for such fields.
892893
* <strong>Drawback:</strong> This approach requires a linter to enforce the tri-state rule and prevent invalid combinations.
893894
* <strong>Benefit:</strong> Simplifies the mental model by making the relationship between optionality, defaults, and requiredness explicit.
894-
2. <strong><code>optional-default: zero-allowed</code> Tag:</strong>
895+
2. <strong>`optional-default: zero-allowed` Tag:</strong>
895896
* A new tag could be introduced to signify that a zero value is permissible, even with a default.
896897
* <strong>Drawback:</strong> Adds complexity by introducing another tag and complicates the mental model.
897898
3. <strong>Compile-Time or Runtime Default Value Check:</strong>
898-
* <strong>Compile-Time Check:</strong> During code generation, the <code>+default</code> tag could be parsed, and if it refers to a zero value, validation logic could be adjusted accordingly.
899+
* <strong>Compile-Time Check:</strong> During code generation, the `+default` tag could be parsed, and if it refers to a zero value, validation logic could be adjusted accordingly.
899900
* <strong>Drawback:</strong> Complex implementation, requires more information to be available during code generation.
900901
* <strong>Runtime Check:</strong> Validation logic could check if the provided default value is a zero value and skip certain checks.
901902
* <strong>Drawback:</strong> Considered overly-complicated ("gross") and potentially impacts performance.
903+
* <strong>Benefit:</strong> Closest to correct.
904+
4. <strong>Make `+optional` on non-pointer fields be advisory:</strong>
905+
* If we find an optional string field, the optional tag can be used as documentation, but the actual validation will rely on the format-checking (e.g. dns-label). To an end user this means that what used to be a "field is required" error now becomes a "not a dns-label" error. Only slightly worse.
902906

903907
#### Addressing the Problem with Valid Zero Values Using the Linter
904908

@@ -1019,12 +1023,14 @@ func TestVersionedValidationByFuzzing(t *testing.T) {
10191023
```
10201024
10211025
###### strategy_test.go vs validation_test.go
1026+
<<[UNRESOLVED should we move current validation_test.go logic to strategy_test.go as it makes more sense generally and aids this project? ]>>
1027+
10221028
10231029
Currently, validation logic and associated tests are logically split across `validation.go` and `strategy.go`. The hand-written validation functions and associated tests reside in `validation.go` and `validation_test.go` while `strategy.go` determines when to invoke these functions during API object creation and updates. With the introduction of declarative validation (controlled by the `DeclarativeValidation` feature gate) the current logic split is worth considering moving from `validation_test.go` to `strategy_test.go` as the current logic split in the test is unfavorable for the migration as it requires additional plumbing work.
10241030
10251031
The current approach in the prototype experiment to migrate ReplicationController involves directly injecting declarative validation and equivalency tests into `validation_test.go`. This is achieved by conditionally appending calls to `rest.ValidateDeclaratively` within the existing test cases, based on the `DeclarativeValidation` feature gate's status. This allows for a direct comparison of the outputs between hand-written and declarative validation within the same test framework.
10261032
1027-
**Moving the feature gate check and declarative validation logic to <code>strategy_test.go</code> could offer several advantages</strong>:
1033+
**Moving the feature gate check and declarative validation logic to `strategy_test.go` could offer several advantages</strong>:
10281034
10291035
* **Reduced Test Duplication:** `validation_test.go` could be simplified, as it would no longer need to handle both hand-written and declarative validation paths.
10301036
* **Clearer Separation of Concerns:** `strategy.go` would be responsible for determining _when_ to validate, while `validation.go` would handle _how_ to validate.
@@ -1036,6 +1042,8 @@ However, this approach also has potential drawbacks:
10361042
10371043
Given the low complexity of moving this code prior to the changes, the enhanced logic split of moving the code, and the reduced work for the migration that moving this code would have currently the plan for Declarative Validation is that the current `validation_test.go` tests are moved to `strategy_test.go` in a PR prior to the migration PR.
10381044
1045+
<<[/UNRESOLVED]>>
1046+
10391047
###### Error Message Equivalence
10401048
10411049
Some error messages will be phrased differently but preserve the same semantic meaning when converted to the declarative validation system. For our testing to check if errors are changed we have two options:
@@ -1071,7 +1079,7 @@ To ensure that Declarative Validation and some of the identified potential perfo
10711079
10721080
##### DeclarativeValidation
10731081
1074-
* `validation-gen` code generator supports the full set of necessary IDL tags for 1:1 porting of handwritten validation to declarative validation
1082+
* `validation-gen` code generator supports the full set of necessary IDL tags for 1:1 porting of handwritten validation to declarative validation
10751083
* Have plumbed all previous validation_test.go unit tests to run against declarative validation schemas.
10761084
* All Unit and Integration tests pass with no errors or only well-understood exceptional errors sourced from a file in the repository
10771085
* Linter finalized with complete set of linter rules

0 commit comments

Comments
 (0)
Please sign in to comment.