Skip to content

Commit a5dda5d

Browse files
authored
Merge pull request kubernetes#130349 from jpbetz/validation-gen-pr1
KEP-5073: Declarative Validation: Add validation generator
2 parents 63ef2dd + a2f47e6 commit a5dda5d

File tree

287 files changed

+22382
-36
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

287 files changed

+22382
-36
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ require (
212212
gopkg.in/inf.v0 v0.9.1 // indirect
213213
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
214214
gopkg.in/yaml.v3 v3.0.1 // indirect
215-
k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 // indirect
215+
k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 // indirect
216216
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
217217
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
218218
sigs.k8s.io/kustomize/api v0.19.0 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -654,8 +654,8 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
654654
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
655655
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
656656
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
657-
k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 h1:si3PfKm8dDYxgfbeA6orqrtLkvvIeH8UqffFJDl0bz4=
658-
k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
657+
k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 h1:2OX19X59HxDprNCVrWi6jb7LW1PoqTlYqEq5H2oetog=
658+
k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
659659
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
660660
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
661661
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg=

hack/update-codegen.sh

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,11 @@ fi
5252
# Generate a list of directories we don't want to play in.
5353
DIRS_TO_AVOID=()
5454
kube::util::read-array DIRS_TO_AVOID < <(
55-
git ls-files -cmo --exclude-standard -- ':!:vendor/*' ':(glob)*/**/go.work' \
55+
git ls-files -cmo --exclude-standard \
56+
-- \
57+
':!:vendor/*' \
58+
':(glob)*/**/go.work' \
59+
':(glob)**/_codegenignore/**' \
5660
| while read -r F; do \
5761
echo ':!:'"$(dirname "${F}")"; \
5862
done
@@ -62,15 +66,20 @@ function git_find() {
6266
# Similar to find but faster and easier to understand. We want to include
6367
# modified and untracked files because this might be running against code
6468
# which is not tracked by git yet.
65-
git ls-files -cmo --exclude-standard ':!:vendor/*' "${DIRS_TO_AVOID[@]}" "$@"
69+
git ls-files -cmo --exclude-standard \
70+
':!:vendor/*' \
71+
"${DIRS_TO_AVOID[@]}" \
72+
"$@"
6673
}
6774

6875
function git_grep() {
6976
# We want to include modified and untracked files because this might be
7077
# running against code which is not tracked by git yet.
7178
# We need vendor exclusion added at the end since it has to be part of
7279
# the pathspecs which are specified last.
73-
git grep --untracked "$@" ':!:vendor/*' "${DIRS_TO_AVOID[@]}"
80+
git grep --untracked "$@" \
81+
':!:vendor/*' \
82+
"${DIRS_TO_AVOID[@]}"
7483
}
7584

7685
# Generate a list of all files that have a `+k8s:` comment-tag. This will be
@@ -380,6 +389,74 @@ function codegen::defaults() {
380389
fi
381390
}
382391

392+
# Validation generation
393+
#
394+
# Any package that wants validation functions generated must include a
395+
# comment-tag in column 0 of one file of the form:
396+
# // +k8s:validation-gen=<VALUE>
397+
#
398+
# The <VALUE> depends on context:
399+
# on packages:
400+
# *: all exported types are candidates for having validation generated
401+
# FIELDNAME: any type with a field of this name is a candidate for
402+
# having validation generated
403+
# on types:
404+
# true: always generate validation for this type
405+
# false: never generate validation for this type
406+
function codegen::validation() {
407+
# Build the tool.
408+
GOPROXY=off go install \
409+
k8s.io/code-generator/cmd/validation-gen
410+
411+
# TODO: Where do we want these output? It should be somewhere internal..
412+
# The result file, in each pkg, of validation generation.
413+
local output_file="${GENERATED_FILE_PREFIX}validations.go"
414+
415+
# All directories that request any form of validation generation.
416+
if [[ "${DBG_CODEGEN}" == 1 ]]; then
417+
kube::log::status "DBG: finding all +k8s:validation-gen tags"
418+
fi
419+
local tag_dirs=()
420+
kube::util::read-array tag_dirs < <( \
421+
grep -l --null '+k8s:validation-gen=' "${ALL_K8S_TAG_FILES[@]}" \
422+
| while read -r -d $'\0' F; do dirname "${F}"; done \
423+
| sort -u)
424+
if [[ "${DBG_CODEGEN}" == 1 ]]; then
425+
kube::log::status "DBG: found ${#tag_dirs[@]} +k8s:validation-gen tagged dirs"
426+
fi
427+
428+
local tag_pkgs=()
429+
for dir in "${tag_dirs[@]}"; do
430+
tag_pkgs+=("./$dir")
431+
done
432+
433+
local extra_pkgs=(
434+
k8s.io/apimachinery/pkg/apis/meta/v1
435+
)
436+
437+
kube::log::status "Generating validation code for ${#tag_pkgs[@]} targets"
438+
if [[ "${DBG_CODEGEN}" == 1 ]]; then
439+
kube::log::status "DBG: running validation-gen for:"
440+
for dir in "${tag_dirs[@]}"; do
441+
kube::log::status "DBG: $dir"
442+
done
443+
fi
444+
445+
git_find -z ':(glob)**'/"${output_file}" | xargs -0 rm -f
446+
447+
validation-gen \
448+
-v "${KUBE_VERBOSE}" \
449+
--go-header-file "${BOILERPLATE_FILENAME}" \
450+
--output-file "${output_file}" \
451+
$(printf -- " --extra-pkg %s" "${extra_pkgs[@]}") \
452+
"${tag_pkgs[@]}" \
453+
"$@"
454+
455+
if [[ "${DBG_CODEGEN}" == 1 ]]; then
456+
kube::log::status "Generated validation code"
457+
fi
458+
}
459+
383460
# Conversion generation
384461

385462
# Any package that wants conversion functions generated into it must

staging/publishing/import-restrictions.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
- k8s.io/code-generator
6262
- k8s.io/kube-openapi
6363
- k8s.io/klog
64+
- k8s.io/utils/ptr
6465

6566
- baseImportPath: "./staging/src/k8s.io/component-base"
6667
allowedImports:

staging/src/k8s.io/apiextensions-apiserver/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ require (
121121
gopkg.in/inf.v0 v0.9.1 // indirect
122122
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
123123
gopkg.in/yaml.v3 v3.0.1 // indirect
124-
k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 // indirect
124+
k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 // indirect
125125
k8s.io/kms v0.0.0 // indirect
126126
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
127127
)

staging/src/k8s.io/apiextensions-apiserver/go.sum

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package operation
18+
19+
import "k8s.io/apimachinery/pkg/util/sets"
20+
21+
// Operation provides contextual information about a validation request and the API
22+
// operation being validated.
23+
// This type is intended for use with generate validation code and may be enhanced
24+
// in the future to include other information needed to validate requests.
25+
type Operation struct {
26+
// Type is the category of operation being validated. This does not
27+
// differentiate between HTTP verbs like PUT and PATCH, but rather merges
28+
// those into a single "Update" category.
29+
Type Type
30+
31+
// Options declare the options enabled for validation.
32+
//
33+
// Options should be set according to a resource validation strategy before validation
34+
// is performed, and must be treated as read-only during validation.
35+
//
36+
// Options are identified by string names. Option string names may match the name of a feature
37+
// gate, in which case the presence of the name in the set indicates that the feature is
38+
// considered enabled for the resource being validated. Note that a resource may have a
39+
// feature enabled even when the feature gate is disabled. This can happen when feature is
40+
// already in-use by a resource, often because the feature gate was enabled when the
41+
// resource first began using the feature.
42+
//
43+
// Unset options are disabled/false.
44+
Options sets.Set[string]
45+
}
46+
47+
// Code is the request operation to be validated.
48+
type Type uint32
49+
50+
const (
51+
// Create indicates the request being validated is for a resource create operation.
52+
Create Type = iota
53+
54+
// Update indicates the request being validated is for a resource update operation.
55+
Update
56+
)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package safe
18+
19+
// Field takes a pointer to any value (which may or may not be nil) and
20+
// a function that traverses to a target type R (a typical use case is to dereference a field),
21+
// and returns the result of the traversal, or the zero value of the target type.
22+
// This is roughly equivalent to "value != nil ? fn(value) : zero-value" in languages that support the ternary operator.
23+
func Field[V any, R any](value *V, fn func(*V) R) R {
24+
if value == nil {
25+
var zero R
26+
return zero
27+
}
28+
o := fn(value)
29+
return o
30+
}
31+
32+
// Cast takes any value, attempts to cast it to T, and returns the T value if
33+
// the cast is successful, or else the zero value of T.
34+
func Cast[T any](value any) T {
35+
result, _ := value.(T)
36+
return result
37+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# API validation
2+
3+
This package holds functions which validate fields and types in the Kubernetes
4+
API. It may be useful beyond API validation, but this is the primary goal.
5+
6+
Most of the public functions here have signatures which adhere to the following
7+
pattern, which is assumed by automation and code-generation:
8+
9+
```
10+
import (
11+
"context"
12+
"k8s.io/apimachinery/pkg/api/operation"
13+
"k8s.io/apimachinery/pkg/util/validation/field"
14+
)
15+
16+
func <Name>(ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue <ValueType>, <OtherArgs...>) field.ErrorList
17+
```
18+
19+
The name of validator functions should consider that callers will generally be
20+
spelling out the package name and the function name, and so should aim for
21+
legibility. E.g. `validate.Concept()`.
22+
23+
The `ctx` argument is Go's usual Context.
24+
25+
The `opCtx` argument provides information about the API operation in question.
26+
27+
The `fldPath` argument indicates the path to the field in question, to be used
28+
in errors.
29+
30+
The `value` and `oldValue` arguments are the thing(s) being validated. For
31+
CREATE operations (`opCtx.Operation == operation.Create`), the `oldValue`
32+
argument will be nil. Many validators functions only look at the current value
33+
(`value`) and disregard `oldValue`.
34+
35+
The `value` and `oldValue` arguments are always nilable - pointers to primitive
36+
types, slices of any type, or maps of any type. Validator functions should
37+
avoid dereferencing nil. Callers are expected to not pass a nil `value` unless the
38+
API field itself was nilable. `oldValue` is always nil for CREATE operations and
39+
is also nil for UPDATE operations if the `value` is not correlated with an `oldValue`.
40+
41+
Simple content-validators may have no `<OtherArgs>`, but validator functions
42+
may take additional arguments. Some validator functions will be built as
43+
generics, e.g. to allow any integer type or to handle arbitrary slices.
44+
45+
Examples:
46+
47+
```
48+
// NonEmpty validates that a string is not empty.
49+
func NonEmpty(ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ *string) field.ErrorList
50+
51+
// Even validates that a slice has an even number of items.
52+
func Even[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ []T) field.ErrorList
53+
54+
// KeysMaxLen validates that all of the string keys in a map are under the
55+
// specified length.
56+
func KeysMaxLen[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ map[string]T, maxLen int) field.ErrorList
57+
```
58+
59+
Validator functions always return an `ErrorList` where each item is a distinct
60+
validation failure and a zero-length return value (not just nil) indicates
61+
success.
62+
63+
Good validation failure messages follow the Kubernetes API conventions, for
64+
example using "must" instead of "should".
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package validate
18+
19+
import (
20+
"context"
21+
22+
"k8s.io/apimachinery/pkg/api/operation"
23+
"k8s.io/apimachinery/pkg/util/validation/field"
24+
)
25+
26+
// ValidateFunc is a function that validates a value, possibly considering the
27+
// old value (if any).
28+
type ValidateFunc[T any] func(ctx context.Context, op operation.Operation, fldPath *field.Path, newValue, oldValue T) field.ErrorList

0 commit comments

Comments
 (0)