update dependencies (#6267)

Signed-off-by: hongming <coder.scala@gmail.com>
This commit is contained in:
hongming
2024-11-06 10:27:06 +08:00
committed by GitHub
parent faf255a084
commit cfebd96a1f
4263 changed files with 341374 additions and 132036 deletions

View File

@@ -17,6 +17,7 @@ limitations under the License.
package cel
import (
"errors"
"fmt"
"strings"
"time"
@@ -122,10 +123,13 @@ func Compile(s *schema.Structural, declType *apiservercel.DeclType, perCallLimit
metrics.Metrics.ObserveCompilation(time.Since(t))
}()
if len(s.Extensions.XValidations) == 0 {
if len(s.XValidations) == 0 {
return nil, nil
}
celRules := s.Extensions.XValidations
if declType == nil {
return nil, errors.New("failed to convert to declType for CEL validation rules")
}
celRules := s.XValidations
oldSelfEnvSet, optionalOldSelfEnvSet, err := prepareEnvSet(baseEnvSet, declType)
if err != nil {
@@ -282,7 +286,7 @@ func compileRule(s *schema.Structural, rule apiextensions.ValidationRule, envSet
compilationResult.MessageExpressionMaxCost = costEst.Max
}
if rule.FieldPath != "" {
validFieldPath, err := ValidFieldPath(rule.FieldPath, s)
validFieldPath, _, err := ValidFieldPath(rule.FieldPath, s)
if err == nil {
compilationResult.NormalizedRuleFieldPath = validFieldPath.String()
}

View File

@@ -62,6 +62,9 @@ func (s *Structural) Pattern() string {
}
func (s *Structural) Items() common.Schema {
if s.Structural.Items == nil {
return nil
}
return &Structural{Structural: s.Structural.Items}
}
@@ -279,11 +282,18 @@ func nestedValueValidationToStructural(nvv *schema.NestedValueValidation) *Struc
newProperties[k] = *nestedValueValidationToStructural(&v).Structural
}
var newAdditionalProperties *schema.StructuralOrBool
if nvv.AdditionalProperties != nil {
newAdditionalProperties = &schema.StructuralOrBool{Structural: nestedValueValidationToStructural(nvv.AdditionalProperties).Structural}
}
return &Structural{
Structural: &schema.Structural{
Items: newItems,
Properties: newProperties,
ValueValidation: &nvv.ValueValidation,
Items: newItems,
Properties: newProperties,
AdditionalProperties: newAdditionalProperties,
ValueValidation: &nvv.ValueValidation,
ValidationExtensions: nvv.ValidationExtensions,
},
}
}

View File

@@ -49,9 +49,11 @@ func WithTypeAndObjectMeta(s *schema.Structural) *schema.Structural {
return s
}
result := &schema.Structural{
Generic: s.Generic,
Extensions: s.Extensions,
ValueValidation: s.ValueValidation,
AdditionalProperties: s.AdditionalProperties,
Generic: s.Generic,
Extensions: s.Extensions,
ValueValidation: s.ValueValidation,
ValidationExtensions: s.ValidationExtensions,
}
props := make(map[string]schema.Structural, len(s.Properties))
for k, prop := range s.Properties {

View File

@@ -31,9 +31,6 @@ import (
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter"
"k8s.io/klog/v2"
"k8s.io/utils/ptr"
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model"
@@ -45,6 +42,8 @@ import (
"k8s.io/apiserver/pkg/cel/metrics"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/apiserver/pkg/warning"
"k8s.io/klog/v2"
"k8s.io/utils/ptr"
celconfig "k8s.io/apiserver/pkg/apis/cel"
)
@@ -52,12 +51,15 @@ import (
// Validator parallels the structure of schema.Structural and includes the compiled CEL programs
// for the x-kubernetes-validations of each schema node.
type Validator struct {
Items *Validator
Properties map[string]Validator
Items *Validator
Properties map[string]Validator
AllOfValidators []*Validator
AdditionalProperties *Validator
compiledRules []CompilationResult
Schema *schema.Structural
uncompiledRules []apiextensions.ValidationRule
compiledRules []CompilationResult
// Program compilation is pre-checked at CRD creation/update time, so we don't expect compilation to fail
// they are recompiled and added to this type, and it does, it is an internal bug.
@@ -83,26 +85,47 @@ func NewValidator(s *schema.Structural, isResourceRoot bool, perCallLimit uint64
if !hasXValidations(s) {
return nil
}
return validator(s, isResourceRoot, model.SchemaDeclType(s, isResourceRoot), perCallLimit)
return validator(s, s, isResourceRoot, model.SchemaDeclType(s, isResourceRoot), perCallLimit)
}
// validator creates a Validator for all x-kubernetes-validations at the level of the provided schema and lower and
// returns the Validator if any x-kubernetes-validations exist in the schema, or nil if no x-kubernetes-validations
// exist. declType is expected to be a CEL DeclType corresponding to the structural schema.
// perCallLimit was added for testing purpose only. Callers should always use const PerCallLimit from k8s.io/apiserver/pkg/apis/cel/config.go as input.
func validator(s *schema.Structural, isResourceRoot bool, declType *cel.DeclType, perCallLimit uint64) *Validator {
compiledRules, err := Compile(s, declType, perCallLimit, environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()), StoredExpressionsEnvLoader())
func validator(validationSchema, nodeSchema *schema.Structural, isResourceRoot bool, declType *cel.DeclType, perCallLimit uint64) *Validator {
compilationSchema := *nodeSchema
compilationSchema.XValidations = validationSchema.XValidations
compiledRules, err := Compile(&compilationSchema, declType, perCallLimit, environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true), StoredExpressionsEnvLoader())
var itemsValidator, additionalPropertiesValidator *Validator
var propertiesValidators map[string]Validator
if s.Items != nil {
itemsValidator = validator(s.Items, s.Items.XEmbeddedResource, declType.ElemType, perCallLimit)
var allOfValidators []*Validator
var elemType *cel.DeclType
if declType != nil {
elemType = declType.ElemType
} else {
elemType = declType
}
if len(s.Properties) > 0 {
propertiesValidators = make(map[string]Validator, len(s.Properties))
for k, p := range s.Properties {
prop := p
if validationSchema.Items != nil && nodeSchema.Items != nil {
itemsValidator = validator(validationSchema.Items, nodeSchema.Items, nodeSchema.Items.XEmbeddedResource, elemType, perCallLimit)
}
if len(validationSchema.Properties) > 0 {
propertiesValidators = make(map[string]Validator, len(validationSchema.Properties))
for k, validationProperty := range validationSchema.Properties {
nodeProperty, ok := nodeSchema.Properties[k]
if !ok {
// Can only add value validations for fields that are on the
// structural spine of the schema.
continue
}
var fieldType *cel.DeclType
if escapedPropName, ok := cel.Escape(k); ok {
if declType == nil {
continue
}
if f, ok := declType.Fields[escapedPropName]; ok {
fieldType = f.Type
} else {
@@ -112,20 +135,32 @@ func validator(s *schema.Structural, isResourceRoot bool, declType *cel.DeclType
} else {
// field may be absent from declType if the property name is unescapable, in which case we should convert
// the field value type to a DeclType.
fieldType = model.SchemaDeclType(&prop, prop.XEmbeddedResource)
fieldType = model.SchemaDeclType(&nodeProperty, nodeProperty.XEmbeddedResource)
if fieldType == nil {
continue
}
}
if p := validator(&prop, prop.XEmbeddedResource, fieldType, perCallLimit); p != nil {
if p := validator(&validationProperty, &nodeProperty, nodeProperty.XEmbeddedResource, fieldType, perCallLimit); p != nil {
propertiesValidators[k] = *p
}
}
}
if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil {
additionalPropertiesValidator = validator(s.AdditionalProperties.Structural, s.AdditionalProperties.Structural.XEmbeddedResource, declType.ElemType, perCallLimit)
if validationSchema.AdditionalProperties != nil && validationSchema.AdditionalProperties.Structural != nil &&
nodeSchema.AdditionalProperties != nil && nodeSchema.AdditionalProperties.Structural != nil {
additionalPropertiesValidator = validator(validationSchema.AdditionalProperties.Structural, nodeSchema.AdditionalProperties.Structural, nodeSchema.AdditionalProperties.Structural.XEmbeddedResource, elemType, perCallLimit)
}
if len(compiledRules) > 0 || err != nil || itemsValidator != nil || additionalPropertiesValidator != nil || len(propertiesValidators) > 0 {
if validationSchema.ValueValidation != nil && len(validationSchema.ValueValidation.AllOf) > 0 {
allOfValidators = make([]*Validator, 0, len(validationSchema.ValueValidation.AllOf))
for _, allOf := range validationSchema.ValueValidation.AllOf {
allOfValidator := validator(nestedToStructural(&allOf), nodeSchema, isResourceRoot, declType, perCallLimit)
if allOfValidator != nil {
allOfValidators = append(allOfValidators, allOfValidator)
}
}
}
if len(compiledRules) > 0 || err != nil || itemsValidator != nil || additionalPropertiesValidator != nil || len(propertiesValidators) > 0 || len(allOfValidators) > 0 {
activationFactory := validationActivationWithoutOldSelf
for _, rule := range compiledRules {
if rule.UsesOldSelf {
@@ -136,12 +171,15 @@ func validator(s *schema.Structural, isResourceRoot bool, declType *cel.DeclType
return &Validator{
compiledRules: compiledRules,
uncompiledRules: validationSchema.XValidations,
compilationErr: err,
isResourceRoot: isResourceRoot,
Items: itemsValidator,
AdditionalProperties: additionalPropertiesValidator,
Properties: propertiesValidators,
AllOfValidators: allOfValidators,
celActivationFactory: activationFactory,
Schema: nodeSchema,
}
}
@@ -164,13 +202,13 @@ func WithRatcheting(correlation *common.CorrelatedObject) Option {
// If the validation rules exceed the costBudget, subsequent evaluations will be skipped, the list of errs returned will not be empty, and a negative remainingBudget will be returned.
// Most callers can ignore the returned remainingBudget value unless another validate call is going to be made
// context is passed for supporting context cancellation during cel validation
func (s *Validator) Validate(ctx context.Context, fldPath *field.Path, sts *schema.Structural, obj, oldObj interface{}, costBudget int64, opts ...Option) (errs field.ErrorList, remainingBudget int64) {
func (s *Validator) Validate(ctx context.Context, fldPath *field.Path, _ *schema.Structural, obj, oldObj interface{}, costBudget int64, opts ...Option) (errs field.ErrorList, remainingBudget int64) {
opt := options{}
for _, o := range opts {
o(&opt)
}
return s.validate(ctx, fldPath, sts, obj, oldObj, opt.ratchetingOptions, costBudget)
return s.validate(ctx, fldPath, obj, oldObj, opt.ratchetingOptions, costBudget)
}
// ratchetingOptions stores the current correlation object and the nearest
@@ -210,8 +248,16 @@ func (r ratchetingOptions) shouldRatchetError() bool {
func (r ratchetingOptions) key(field string) ratchetingOptions {
if r.currentCorrelation == nil {
return r
} else if r.nearestParentCorrelation == nil && (field == "apiVersion" || field == "kind") {
// We cannot ratchet changes to the APIVersion and kind fields field since
// they aren't visible. (both old and new are converted to the same type)
//
return ratchetingOptions{}
}
// nearestParentCorrelation is always non-nil except for the root node.
// The below line ensures that the next nearestParentCorrelation is set
// to a non-nil r.currentCorrelation
return ratchetingOptions{currentCorrelation: r.currentCorrelation.Key(field), nearestParentCorrelation: r.currentCorrelation}
}
@@ -226,7 +272,36 @@ func (r ratchetingOptions) index(idx int) ratchetingOptions {
return ratchetingOptions{currentCorrelation: r.currentCorrelation.Index(idx), nearestParentCorrelation: r.currentCorrelation}
}
func (s *Validator) validate(ctx context.Context, fldPath *field.Path, sts *schema.Structural, obj, oldObj interface{}, correlation ratchetingOptions, costBudget int64) (errs field.ErrorList, remainingBudget int64) {
func nestedToStructural(nested *schema.NestedValueValidation) *schema.Structural {
if nested == nil {
return nil
}
structuralConversion := &schema.Structural{
ValueValidation: &nested.ValueValidation,
ValidationExtensions: nested.ValidationExtensions,
Generic: nested.ForbiddenGenerics,
Extensions: nested.ForbiddenExtensions,
Items: nestedToStructural(nested.Items),
}
if len(nested.Properties) > 0 {
structuralConversion.Properties = make(map[string]schema.Structural, len(nested.Properties))
for k, v := range nested.Properties {
structuralConversion.Properties[k] = *nestedToStructural(&v)
}
}
if nested.AdditionalProperties != nil {
structuralConversion.AdditionalProperties = &schema.StructuralOrBool{
Structural: nestedToStructural(nested.AdditionalProperties),
}
}
return structuralConversion
}
func (s *Validator) validate(ctx context.Context, fldPath *field.Path, obj, oldObj interface{}, correlation ratchetingOptions, costBudget int64) (errs field.ErrorList, remainingBudget int64) {
t := time.Now()
defer func() {
metrics.Metrics.ObserveEvaluation(time.Since(t))
@@ -236,23 +311,37 @@ func (s *Validator) validate(ctx context.Context, fldPath *field.Path, sts *sche
return nil, remainingBudget
}
errs, remainingBudget = s.validateExpressions(ctx, fldPath, sts, obj, oldObj, correlation, remainingBudget)
errs, remainingBudget = s.validateExpressions(ctx, fldPath, obj, oldObj, correlation, remainingBudget)
if remainingBudget < 0 {
return errs, remainingBudget
}
// If the schema has allOf, recurse through those elements to see if there
// are any validation rules that need to be evaluated.
for _, allOfValidator := range s.AllOfValidators {
var allOfErrs field.ErrorList
// Pass options with nil currentCorrelation to mirror schema ratcheting
// behavior which does not ratchet allOf errors. This may change in the
// future for allOf.
allOfErrs, remainingBudget = allOfValidator.validate(ctx, fldPath, obj, oldObj, ratchetingOptions{nearestParentCorrelation: correlation.nearestParentCorrelation}, remainingBudget)
errs = append(errs, allOfErrs...)
if remainingBudget < 0 {
return errs, remainingBudget
}
}
switch obj := obj.(type) {
case []interface{}:
oldArray, _ := oldObj.([]interface{})
var arrayErrs field.ErrorList
arrayErrs, remainingBudget = s.validateArray(ctx, fldPath, sts, obj, oldArray, correlation, remainingBudget)
arrayErrs, remainingBudget = s.validateArray(ctx, fldPath, obj, oldArray, correlation, remainingBudget)
errs = append(errs, arrayErrs...)
return errs, remainingBudget
case map[string]interface{}:
oldMap, _ := oldObj.(map[string]interface{})
var mapErrs field.ErrorList
mapErrs, remainingBudget = s.validateMap(ctx, fldPath, sts, obj, oldMap, correlation, remainingBudget)
mapErrs, remainingBudget = s.validateMap(ctx, fldPath, obj, oldMap, correlation, remainingBudget)
errs = append(errs, mapErrs...)
return errs, remainingBudget
}
@@ -260,7 +349,9 @@ func (s *Validator) validate(ctx context.Context, fldPath *field.Path, sts *sche
return errs, remainingBudget
}
func (s *Validator) validateExpressions(ctx context.Context, fldPath *field.Path, sts *schema.Structural, obj, oldObj interface{}, correlation ratchetingOptions, costBudget int64) (errs field.ErrorList, remainingBudget int64) {
func (s *Validator) validateExpressions(ctx context.Context, fldPath *field.Path, obj, oldObj interface{}, correlation ratchetingOptions, costBudget int64) (errs field.ErrorList, remainingBudget int64) {
sts := s.Schema
// guard against oldObj being a non-nil interface with a nil value
if oldObj != nil {
v := reflect.ValueOf(oldObj)
@@ -295,7 +386,7 @@ func (s *Validator) validateExpressions(ctx context.Context, fldPath *field.Path
}
activation, optionalOldSelfActivation := s.celActivationFactory(sts, obj, oldObj)
for i, compiled := range s.compiledRules {
rule := sts.XValidations[i]
rule := s.uncompiledRules[i]
if compiled.Error != nil {
errs = append(errs, field.Invalid(fldPath, sts.Type, fmt.Sprintf("rule compile error: %v", compiled.Error)))
continue
@@ -361,8 +452,9 @@ func (s *Validator) validateExpressions(ctx context.Context, fldPath *field.Path
continue
}
if evalResult != types.True {
currentFldPath := fldPath
if len(compiled.NormalizedRuleFieldPath) > 0 {
fldPath = fldPath.Child(compiled.NormalizedRuleFieldPath)
currentFldPath = currentFldPath.Child(compiled.NormalizedRuleFieldPath)
}
addErr := func(e *field.Error) {
@@ -377,22 +469,22 @@ func (s *Validator) validateExpressions(ctx context.Context, fldPath *field.Path
messageExpression, newRemainingBudget, msgErr := evalMessageExpression(ctx, compiled.MessageExpression, rule.MessageExpression, activation, remainingBudget)
if msgErr != nil {
if msgErr.Type == cel.ErrorTypeInternal {
addErr(field.InternalError(fldPath, msgErr))
addErr(field.InternalError(currentFldPath, msgErr))
return errs, -1
} else if msgErr.Type == cel.ErrorTypeInvalid {
addErr(field.Invalid(fldPath, sts.Type, msgErr.Error()))
addErr(field.Invalid(currentFldPath, sts.Type, msgErr.Error()))
return errs, -1
} else {
klog.V(2).ErrorS(msgErr, "messageExpression evaluation failed")
addErr(fieldErrorForReason(fldPath, sts.Type, ruleMessageOrDefault(rule), rule.Reason))
addErr(fieldErrorForReason(currentFldPath, sts.Type, ruleMessageOrDefault(rule), rule.Reason))
remainingBudget = newRemainingBudget
}
} else {
addErr(fieldErrorForReason(fldPath, sts.Type, messageExpression, rule.Reason))
addErr(fieldErrorForReason(currentFldPath, sts.Type, messageExpression, rule.Reason))
remainingBudget = newRemainingBudget
}
} else {
addErr(fieldErrorForReason(fldPath, sts.Type, ruleMessageOrDefault(rule), rule.Reason))
addErr(fieldErrorForReason(currentFldPath, sts.Type, ruleMessageOrDefault(rule), rule.Reason))
}
}
}
@@ -432,9 +524,30 @@ func unescapeSingleQuote(s string) (string, error) {
return unescaped, err
}
type validFieldPathOptions struct {
allowArrayNotation bool
}
// ValidFieldPathOption provides vararg options for ValidFieldPath.
type ValidFieldPathOption func(*validFieldPathOptions)
// WithFieldPathAllowArrayNotation sets of array annotation ('[<index or map key>]') is allowed
// in field paths.
// Defaults to true
func WithFieldPathAllowArrayNotation(allow bool) ValidFieldPathOption {
return func(options *validFieldPathOptions) {
options.allowArrayNotation = allow
}
}
// ValidFieldPath validates that jsonPath is a valid JSON Path containing only field and map accessors
// that are valid for the given schema, and returns a field.Path representation of the validated jsonPath or an error.
func ValidFieldPath(jsonPath string, schema *schema.Structural) (validFieldPath *field.Path, err error) {
func ValidFieldPath(jsonPath string, schema *schema.Structural, options ...ValidFieldPathOption) (validFieldPath *field.Path, foundSchema *schema.Structural, err error) {
opts := &validFieldPathOptions{allowArrayNotation: true}
for _, opt := range options {
opt(opts)
}
appendToPath := func(name string, isNamed bool) error {
if !isNamed {
validFieldPath = validFieldPath.Key(name)
@@ -495,16 +608,19 @@ func ValidFieldPath(jsonPath string, schema *schema.Structural) (validFieldPath
tok = scanner.Text()
switch tok {
case "[":
if !opts.allowArrayNotation {
return nil, nil, fmt.Errorf("array notation is not allowed")
}
if !scanner.Scan() {
return nil, fmt.Errorf("unexpected end of JSON path")
return nil, nil, fmt.Errorf("unexpected end of JSON path")
}
tok = scanner.Text()
if len(tok) < 2 || tok[0] != '\'' || tok[len(tok)-1] != '\'' {
return nil, fmt.Errorf("expected single quoted string but got %s", tok)
return nil, nil, fmt.Errorf("expected single quoted string but got %s", tok)
}
unescaped, err := unescapeSingleQuote(tok[1 : len(tok)-1])
if err != nil {
return nil, fmt.Errorf("invalid string literal: %v", err)
return nil, nil, fmt.Errorf("invalid string literal: %w", err)
}
if schema.Properties != nil {
@@ -512,21 +628,21 @@ func ValidFieldPath(jsonPath string, schema *schema.Structural) (validFieldPath
} else if schema.AdditionalProperties != nil {
isNamed = false
} else {
return nil, fmt.Errorf("does not refer to a valid field")
return nil, nil, fmt.Errorf("does not refer to a valid field")
}
if err := appendToPath(unescaped, isNamed); err != nil {
return nil, err
return nil, nil, err
}
if !scanner.Scan() {
return nil, fmt.Errorf("unexpected end of JSON path")
return nil, nil, fmt.Errorf("unexpected end of JSON path")
}
tok = scanner.Text()
if tok != "]" {
return nil, fmt.Errorf("expected ] but got %s", tok)
return nil, nil, fmt.Errorf("expected ] but got %s", tok)
}
case ".":
if !scanner.Scan() {
return nil, fmt.Errorf("unexpected end of JSON path")
return nil, nil, fmt.Errorf("unexpected end of JSON path")
}
tok = scanner.Text()
if schema.Properties != nil {
@@ -534,16 +650,17 @@ func ValidFieldPath(jsonPath string, schema *schema.Structural) (validFieldPath
} else if schema.AdditionalProperties != nil {
isNamed = false
} else {
return nil, fmt.Errorf("does not refer to a valid field")
return nil, nil, fmt.Errorf("does not refer to a valid field")
}
if err := appendToPath(tok, isNamed); err != nil {
return nil, err
return nil, nil, err
}
default:
return nil, fmt.Errorf("expected [ or . but got: %s", tok)
return nil, nil, fmt.Errorf("expected [ or . but got: %s", tok)
}
}
return validFieldPath, nil
return validFieldPath, schema, nil
}
func fieldErrorForReason(fldPath *field.Path, value interface{}, detail string, reason *apiextensions.FieldValueErrorReason) *field.Error {
@@ -686,7 +803,7 @@ func (a *validationActivation) Parent() interpreter.Activation {
return nil
}
func (s *Validator) validateMap(ctx context.Context, fldPath *field.Path, sts *schema.Structural, obj, oldObj map[string]interface{}, correlation ratchetingOptions, costBudget int64) (errs field.ErrorList, remainingBudget int64) {
func (s *Validator) validateMap(ctx context.Context, fldPath *field.Path, obj, oldObj map[string]interface{}, correlation ratchetingOptions, costBudget int64) (errs field.ErrorList, remainingBudget int64) {
remainingBudget = costBudget
if remainingBudget < 0 {
return errs, remainingBudget
@@ -695,9 +812,9 @@ func (s *Validator) validateMap(ctx context.Context, fldPath *field.Path, sts *s
return nil, remainingBudget
}
correlatable := MapIsCorrelatable(sts.XMapType)
correlatable := MapIsCorrelatable(s.Schema.XMapType)
if s.AdditionalProperties != nil && sts.AdditionalProperties != nil && sts.AdditionalProperties.Structural != nil {
if s.AdditionalProperties != nil {
for k, v := range obj {
var oldV interface{}
if correlatable {
@@ -705,25 +822,24 @@ func (s *Validator) validateMap(ctx context.Context, fldPath *field.Path, sts *s
}
var err field.ErrorList
err, remainingBudget = s.AdditionalProperties.validate(ctx, fldPath.Key(k), sts.AdditionalProperties.Structural, v, oldV, correlation.key(k), remainingBudget)
err, remainingBudget = s.AdditionalProperties.validate(ctx, fldPath.Key(k), v, oldV, correlation.key(k), remainingBudget)
errs = append(errs, err...)
if remainingBudget < 0 {
return errs, remainingBudget
}
}
}
if s.Properties != nil && sts.Properties != nil {
if s.Properties != nil {
for k, v := range obj {
stsProp, stsOk := sts.Properties[k]
sub, ok := s.Properties[k]
if ok && stsOk {
if ok {
var oldV interface{}
if correlatable {
oldV = oldObj[k] // +k8s:verify-mutation:reason=clone
}
var err field.ErrorList
err, remainingBudget = sub.validate(ctx, fldPath.Child(k), &stsProp, v, oldV, correlation.key(k), remainingBudget)
err, remainingBudget = sub.validate(ctx, fldPath.Child(k), v, oldV, correlation.key(k), remainingBudget)
errs = append(errs, err...)
if remainingBudget < 0 {
return errs, remainingBudget
@@ -735,19 +851,19 @@ func (s *Validator) validateMap(ctx context.Context, fldPath *field.Path, sts *s
return errs, remainingBudget
}
func (s *Validator) validateArray(ctx context.Context, fldPath *field.Path, sts *schema.Structural, obj, oldObj []interface{}, correlation ratchetingOptions, costBudget int64) (errs field.ErrorList, remainingBudget int64) {
func (s *Validator) validateArray(ctx context.Context, fldPath *field.Path, obj, oldObj []interface{}, correlation ratchetingOptions, costBudget int64) (errs field.ErrorList, remainingBudget int64) {
remainingBudget = costBudget
if remainingBudget < 0 {
return errs, remainingBudget
}
if s.Items != nil && sts.Items != nil {
if s.Items != nil {
// only map-type lists support self-oldSelf correlation for cel rules. if this isn't a
// map-type list, then makeMapList returns an implementation that always returns nil
correlatableOldItems := makeMapList(sts, oldObj)
correlatableOldItems := makeMapList(s.Schema, oldObj)
for i := range obj {
var err field.ErrorList
err, remainingBudget = s.Items.validate(ctx, fldPath.Index(i), sts.Items, obj[i], correlatableOldItems.Get(obj[i]), correlation.index(i), remainingBudget)
err, remainingBudget = s.Items.validate(ctx, fldPath.Index(i), obj[i], correlatableOldItems.Get(obj[i]), correlation.index(i), remainingBudget)
errs = append(errs, err...)
if remainingBudget < 0 {
return errs, remainingBudget

View File

@@ -22,17 +22,24 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
)
// validateStructuralCompleteness checks that every specified field or array in s is also specified
// outside of value validation.
func validateStructuralCompleteness(s *Structural, fldPath *field.Path) field.ErrorList {
// validateStructuralCompleteness checks that all value validations in s have
// a structural counterpart so that every value validation applies to a value
// with a known schema:
// - validations for specific properties must have that property (or additionalProperties under an option) structurally defined
// - additionalProperties validations must have additionalProperties defined in the structural portion of the schema corresponding to that node
// - Items validations must have also have a corresponding items structurally
//
// The "structural" portion of the schema refers to all nodes in the
// schema traversible without following any NestedValueValidations.
func validateStructuralCompleteness(s *Structural, fldPath *field.Path, opts ValidationOptions) field.ErrorList {
if s == nil {
return nil
}
return validateValueValidationCompleteness(s.ValueValidation, s, fldPath, fldPath)
return validateValueValidationCompleteness(s.ValueValidation, s, fldPath, fldPath, opts)
}
func validateValueValidationCompleteness(v *ValueValidation, s *Structural, sPath, vPath *field.Path) field.ErrorList {
func validateValueValidationCompleteness(v *ValueValidation, s *Structural, sPath, vPath *field.Path, opts ValidationOptions) field.ErrorList {
if v == nil {
return nil
}
@@ -42,21 +49,21 @@ func validateValueValidationCompleteness(v *ValueValidation, s *Structural, sPat
allErrs := field.ErrorList{}
allErrs = append(allErrs, validateNestedValueValidationCompleteness(v.Not, s, sPath, vPath.Child("not"))...)
allErrs = append(allErrs, validateNestedValueValidationCompleteness(v.Not, s, sPath, vPath.Child("not"), opts)...)
for i := range v.AllOf {
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&v.AllOf[i], s, sPath, vPath.Child("allOf").Index(i))...)
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&v.AllOf[i], s, sPath, vPath.Child("allOf").Index(i), opts)...)
}
for i := range v.AnyOf {
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&v.AnyOf[i], s, sPath, vPath.Child("anyOf").Index(i))...)
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&v.AnyOf[i], s, sPath, vPath.Child("anyOf").Index(i), opts)...)
}
for i := range v.OneOf {
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&v.OneOf[i], s, sPath, vPath.Child("oneOf").Index(i))...)
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&v.OneOf[i], s, sPath, vPath.Child("oneOf").Index(i), opts)...)
}
return allErrs
}
func validateNestedValueValidationCompleteness(v *NestedValueValidation, s *Structural, sPath, vPath *field.Path) field.ErrorList {
func validateNestedValueValidationCompleteness(v *NestedValueValidation, s *Structural, sPath, vPath *field.Path, opts ValidationOptions) field.ErrorList {
if v == nil {
return nil
}
@@ -66,17 +73,34 @@ func validateNestedValueValidationCompleteness(v *NestedValueValidation, s *Stru
allErrs := field.ErrorList{}
allErrs = append(allErrs, validateValueValidationCompleteness(&v.ValueValidation, s, sPath, vPath)...)
allErrs = append(allErrs, validateNestedValueValidationCompleteness(v.Items, s.Items, sPath.Child("items"), vPath.Child("items"))...)
allErrs = append(allErrs, validateValueValidationCompleteness(&v.ValueValidation, s, sPath, vPath, opts)...)
allErrs = append(allErrs, validateNestedValueValidationCompleteness(v.Items, s.Items, sPath.Child("items"), vPath.Child("items"), opts)...)
var sAdditionalPropertiesSchema *Structural
if s.AdditionalProperties != nil {
sAdditionalPropertiesSchema = s.AdditionalProperties.Structural
}
for k, vFld := range v.Properties {
if sFld, ok := s.Properties[k]; !ok {
allErrs = append(allErrs, field.Required(sPath.Child("properties").Key(k), fmt.Sprintf("because it is defined in %s", vPath.Child("properties").Key(k))))
if sAdditionalPropertiesSchema == nil || !opts.AllowValidationPropertiesWithAdditionalProperties {
allErrs = append(allErrs, field.Required(sPath.Child("properties").Key(k), fmt.Sprintf("because it is defined in %s", vPath.Child("properties").Key(k))))
} else {
// Allow validations on specific properties if there exists an
// additionalProperties structural schema specified instead of
// direct properties
// NOTE: This does not allow `additionalProperties: true` structural
// schema to be combined with specific property validations.
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&vFld, sAdditionalPropertiesSchema, sPath.Child("additionalProperties"), vPath.Child("properties").Key(k), opts)...)
}
} else {
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&vFld, &sFld, sPath.Child("properties").Key(k), vPath.Child("properties").Key(k))...)
allErrs = append(allErrs, validateNestedValueValidationCompleteness(&vFld, &sFld, sPath.Child("properties").Key(k), vPath.Child("properties").Key(k), opts)...)
}
}
// don't check additionalProperties as this is not allowed (and checked during validation)
if v.AdditionalProperties != nil && opts.AllowNestedAdditionalProperties {
allErrs = append(allErrs, validateNestedValueValidationCompleteness(v.AdditionalProperties, sAdditionalPropertiesSchema, sPath.Child("additionalProperties"), vPath.Child("additionalProperties"), opts)...)
}
return allErrs
}

View File

@@ -18,6 +18,7 @@ package schema
import (
"fmt"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)
@@ -62,10 +63,16 @@ func NewStructural(s *apiextensions.JSONSchemaProps) (*Structural, error) {
return nil, err
}
vx, err := newValidationExtensions(s)
if err != nil {
return nil, err
}
ss := &Structural{
Generic: *g,
Extensions: *x,
ValueValidation: vv,
Generic: *g,
Extensions: *x,
ValueValidation: vv,
ValidationExtensions: *vx,
}
if s.Items != nil {
@@ -91,6 +98,18 @@ func NewStructural(s *apiextensions.JSONSchemaProps) (*Structural, error) {
}
}
if s.AdditionalProperties != nil {
if s.AdditionalProperties.Schema != nil {
additionalPropertiesSchema, err := NewStructural(s.AdditionalProperties.Schema)
if err != nil {
return nil, err
}
ss.AdditionalProperties = &StructuralOrBool{Structural: additionalPropertiesSchema, Bool: true}
} else {
ss.AdditionalProperties = &StructuralOrBool{Bool: s.AdditionalProperties.Allows}
}
}
return ss, nil
}
@@ -108,18 +127,6 @@ func newGenerics(s *apiextensions.JSONSchemaProps) (*Generic, error) {
g.Default = JSON{interface{}(*s.Default)}
}
if s.AdditionalProperties != nil {
if s.AdditionalProperties.Schema != nil {
ss, err := NewStructural(s.AdditionalProperties.Schema)
if err != nil {
return nil, err
}
g.AdditionalProperties = &StructuralOrBool{Structural: ss, Bool: true}
} else {
g.AdditionalProperties = &StructuralOrBool{Bool: s.AdditionalProperties.Allows}
}
}
return g, nil
}
@@ -205,10 +212,16 @@ func newNestedValueValidation(s *apiextensions.JSONSchemaProps) (*NestedValueVal
return nil, err
}
vx, err := newValidationExtensions(s)
if err != nil {
return nil, err
}
v := &NestedValueValidation{
ValueValidation: *vv,
ForbiddenGenerics: *g,
ForbiddenExtensions: *x,
ValueValidation: *vv,
ValidationExtensions: *vx,
ForbiddenGenerics: *g,
ForbiddenExtensions: *x,
}
if s.Items != nil {
@@ -232,6 +245,18 @@ func newNestedValueValidation(s *apiextensions.JSONSchemaProps) (*NestedValueVal
v.Properties[k] = *nvv
}
}
if s.AdditionalProperties != nil {
if s.AdditionalProperties.Schema != nil {
additionalPropertiesSchema, err := newNestedValueValidation(s.AdditionalProperties.Schema)
if err != nil {
return nil, err
}
v.AdditionalProperties = additionalPropertiesSchema
} else if s.AdditionalProperties.Allows {
v.AdditionalProperties = &NestedValueValidation{}
}
}
return v, nil
}
@@ -248,9 +273,6 @@ func newExtensions(s *apiextensions.JSONSchemaProps) (*Extensions, error) {
XListType: s.XListType,
XMapType: s.XMapType,
}
if err := apiextensionsv1.Convert_apiextensions_ValidationRules_To_v1_ValidationRules(&s.XValidations, &ret.XValidations, nil); err != nil {
return nil, err
}
if s.XPreserveUnknownFields != nil {
if !*s.XPreserveUnknownFields {
@@ -262,6 +284,19 @@ func newExtensions(s *apiextensions.JSONSchemaProps) (*Extensions, error) {
return ret, nil
}
func newValidationExtensions(s *apiextensions.JSONSchemaProps) (*ValidationExtensions, error) {
if s == nil {
return nil, nil
}
ret := &ValidationExtensions{}
if err := apiextensionsv1.Convert_apiextensions_ValidationRules_To_v1_ValidationRules(&s.XValidations, &ret.XValidations, nil); err != nil {
return nil, err
}
return ret, nil
}
// validateUnsupportedFields checks that those fields rejected by validation are actually unset.
func validateUnsupportedFields(s *apiextensions.JSONSchemaProps) error {
if len(s.ID) > 0 {

View File

@@ -37,10 +37,16 @@ func (s *Structural) ToKubeOpenAPI() *spec.Schema {
ret.Properties[k] = *v.ToKubeOpenAPI()
}
}
if s.AdditionalProperties != nil {
ret.AdditionalProperties = &spec.SchemaOrBool{
Allows: s.AdditionalProperties.Bool,
Schema: s.AdditionalProperties.Structural.ToKubeOpenAPI(),
}
}
s.Generic.toKubeOpenAPI(ret)
s.Extensions.toKubeOpenAPI(ret)
s.ValueValidation.toKubeOpenAPI(ret)
s.ValidationExtensions.toKubeOpenAPI(ret)
return ret
}
@@ -53,12 +59,6 @@ func (g *Generic) toKubeOpenAPI(ret *spec.Schema) {
ret.Type = spec.StringOrArray{g.Type}
}
ret.Nullable = g.Nullable
if g.AdditionalProperties != nil {
ret.AdditionalProperties = &spec.SchemaOrBool{
Allows: g.AdditionalProperties.Bool,
Schema: g.AdditionalProperties.Structural.ToKubeOpenAPI(),
}
}
ret.Description = g.Description
ret.Title = g.Title
ret.Default = g.Default.Object
@@ -87,6 +87,13 @@ func (x *Extensions) toKubeOpenAPI(ret *spec.Schema) {
if x.XMapType != nil {
ret.VendorExtensible.AddExtension("x-kubernetes-map-type", *x.XMapType)
}
}
func (x *ValidationExtensions) toKubeOpenAPI(ret *spec.Schema) {
if x == nil {
return
}
if len(x.XValidations) > 0 {
ret.VendorExtensible.AddExtension("x-kubernetes-validations", x.XValidations)
}
@@ -138,6 +145,7 @@ func (vv *NestedValueValidation) toKubeOpenAPI() *spec.Schema {
ret := &spec.Schema{}
vv.ValueValidation.toKubeOpenAPI(ret)
vv.ValidationExtensions.toKubeOpenAPI(ret)
if vv.Items != nil {
ret.Items = &spec.SchemaOrArray{Schema: vv.Items.toKubeOpenAPI()}
}
@@ -149,6 +157,5 @@ func (vv *NestedValueValidation) toKubeOpenAPI() *spec.Schema {
}
vv.ForbiddenGenerics.toKubeOpenAPI(ret) // normally empty. Exception: int-or-string
vv.ForbiddenExtensions.toKubeOpenAPI(ret) // shouldn't do anything
return ret
}

View File

@@ -25,11 +25,13 @@ import (
// Structural represents a structural schema.
type Structural struct {
Items *Structural
Properties map[string]Structural
Items *Structural
Properties map[string]Structural
AdditionalProperties *StructuralOrBool
Generic
Extensions
ValidationExtensions
ValueValidation *ValueValidation
}
@@ -51,11 +53,10 @@ type Generic struct {
// It can be object, array, number, integer, boolean, string.
// It is optional only if x-kubernetes-preserve-unknown-fields
// or x-kubernetes-int-or-string is true.
Type string
Title string
Default JSON
AdditionalProperties *StructuralOrBool
Nullable bool
Type string
Title string
Default JSON
Nullable bool
}
// +k8s:deepcopy-gen=true
@@ -128,7 +129,13 @@ type Extensions struct {
// Atomic maps will be entirely replaced when updated.
// +optional
XMapType *string
}
// +k8s:deepcopy-gen=true
// ValidationExtensions contains the Kubernetes OpenAPI v3 extensions that are
// used for validation rather than structure.
type ValidationExtensions struct {
// x-kubernetes-validations describes a list of validation rules for expression validation.
// Use the v1 struct since this gets serialized as an extension.
XValidations apiextensionsv1.ValidationRules
@@ -166,9 +173,11 @@ type ValueValidation struct {
// under a logical junctor, and catch all structs for generic and vendor extensions schema fields.
type NestedValueValidation struct {
ValueValidation
ValidationExtensions
Items *NestedValueValidation
Properties map[string]NestedValueValidation
Items *NestedValueValidation
Properties map[string]NestedValueValidation
AdditionalProperties *NestedValueValidation
// Anything set in the following will make the scheme
// non-structural, with the exception of these two patterns if

View File

@@ -42,6 +42,22 @@ const (
fieldLevel
)
type ValidationOptions struct {
// AllowNestedAdditionalProperties allows additionalProperties to be specified in
// nested contexts (allOf, anyOf, oneOf, not).
AllowNestedAdditionalProperties bool
// AllowNestedXValidations allows x-kubernetes-validations to be specified in
// nested contexts (allOf, anyOf, oneOf, not).
AllowNestedXValidations bool
// AllowValidationPropertiesWithAdditionalProperties allows
// value validations on specific properties that are structually
// defined by additionalProperties. i.e. additionalProperties in main structural
// schema, but properties within an allOf.
AllowValidationPropertiesWithAdditionalProperties bool
}
// ValidateStructural checks that s is a structural schema with the invariants:
//
// * structurality: both `ForbiddenGenerics` and `ForbiddenExtensions` only have zero values, with the two exceptions for IntOrString.
@@ -61,10 +77,21 @@ const (
// * metadata at the root can only restrict the name and generateName, and not be specified at all in nested contexts.
// * additionalProperties at the root is not allowed.
func ValidateStructural(fldPath *field.Path, s *Structural) field.ErrorList {
return ValidateStructuralWithOptions(fldPath, s, ValidationOptions{
// This would widen the schema for CRD if set to true, so first few releases will still
// not admit any. But it can still be used by libraries and
// declarative validation for native types
AllowNestedAdditionalProperties: false,
AllowNestedXValidations: false,
AllowValidationPropertiesWithAdditionalProperties: false,
})
}
func ValidateStructuralWithOptions(fldPath *field.Path, s *Structural, opts ValidationOptions) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, validateStructuralInvariants(s, rootLevel, fldPath)...)
allErrs = append(allErrs, validateStructuralCompleteness(s, fldPath)...)
allErrs = append(allErrs, validateStructuralInvariants(s, rootLevel, fldPath, opts)...)
allErrs = append(allErrs, validateStructuralCompleteness(s, fldPath, opts)...)
// sort error messages. Otherwise, the errors slice will change every time due to
// maps in the types and randomized iteration.
@@ -76,7 +103,7 @@ func ValidateStructural(fldPath *field.Path, s *Structural) field.ErrorList {
}
// validateStructuralInvariants checks the invariants of a structural schema.
func validateStructuralInvariants(s *Structural, lvl level, fldPath *field.Path) field.ErrorList {
func validateStructuralInvariants(s *Structural, lvl level, fldPath *field.Path, opts ValidationOptions) field.ErrorList {
if s == nil {
return nil
}
@@ -86,11 +113,21 @@ func validateStructuralInvariants(s *Structural, lvl level, fldPath *field.Path)
if s.Type == "array" && s.Items == nil {
allErrs = append(allErrs, field.Required(fldPath.Child("items"), "must be specified"))
}
allErrs = append(allErrs, validateStructuralInvariants(s.Items, itemLevel, fldPath.Child("items"))...)
allErrs = append(allErrs, validateStructuralInvariants(s.Items, itemLevel, fldPath.Child("items"), opts)...)
for k, v := range s.Properties {
allErrs = append(allErrs, validateStructuralInvariants(&v, fieldLevel, fldPath.Child("properties").Key(k))...)
allErrs = append(allErrs, validateStructuralInvariants(&v, fieldLevel, fldPath.Child("properties").Key(k), opts)...)
}
if s.AdditionalProperties != nil {
if lvl == rootLevel {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalProperties"), "must not be used at the root"))
}
if s.AdditionalProperties.Structural != nil {
allErrs = append(allErrs, validateStructuralInvariants(s.AdditionalProperties.Structural, fieldLevel, fldPath.Child("additionalProperties"), opts)...)
}
}
allErrs = append(allErrs, validateGeneric(&s.Generic, lvl, fldPath)...)
allErrs = append(allErrs, validateExtensions(&s.Extensions, fldPath)...)
@@ -106,7 +143,7 @@ func validateStructuralInvariants(s *Structural, lvl level, fldPath *field.Path)
skipAnyOf := isIntOrStringAnyOfPattern(s)
skipFirstAllOfAnyOf := isIntOrStringAllOfPattern(s)
allErrs = append(allErrs, validateValueValidation(s.ValueValidation, skipAnyOf, skipFirstAllOfAnyOf, lvl, fldPath)...)
allErrs = append(allErrs, validateValueValidation(s.ValueValidation, skipAnyOf, skipFirstAllOfAnyOf, lvl, fldPath, opts)...)
checkMetadata := (lvl == rootLevel) || s.XEmbeddedResource
@@ -207,18 +244,7 @@ func validateGeneric(g *Generic, lvl level, fldPath *field.Path) field.ErrorList
return nil
}
allErrs := field.ErrorList{}
if g.AdditionalProperties != nil {
if lvl == rootLevel {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalProperties"), "must not be used at the root"))
}
if g.AdditionalProperties.Structural != nil {
allErrs = append(allErrs, validateStructuralInvariants(g.AdditionalProperties.Structural, fieldLevel, fldPath.Child("additionalProperties"))...)
}
}
return allErrs
return nil
}
// validateExtensions checks Kubernetes vendor extensions of a structural schema.
@@ -236,16 +262,23 @@ func validateExtensions(x *Extensions, fldPath *field.Path) field.ErrorList {
}
// validateValueValidation checks the value validation in a structural schema.
func validateValueValidation(v *ValueValidation, skipAnyOf, skipFirstAllOfAnyOf bool, lvl level, fldPath *field.Path) field.ErrorList {
func validateValueValidation(v *ValueValidation, skipAnyOf, skipFirstAllOfAnyOf bool, lvl level, fldPath *field.Path, opts ValidationOptions) field.ErrorList {
if v == nil {
return nil
}
allErrs := field.ErrorList{}
// We still unconditionally forbid XValidations in quantifiers, the only
// quantifier that is allowed to have right now is AllOf. This is due to
// implementation constraints - the SchemaValidator would need to become
// aware of CEL to properly implement the other quantifiers.
optsWithCELDisabled := opts
optsWithCELDisabled.AllowNestedXValidations = false
if !skipAnyOf {
for i := range v.AnyOf {
allErrs = append(allErrs, validateNestedValueValidation(&v.AnyOf[i], false, false, lvl, fldPath.Child("anyOf").Index(i))...)
allErrs = append(allErrs, validateNestedValueValidation(&v.AnyOf[i], false, false, lvl, fldPath.Child("anyOf").Index(i), optsWithCELDisabled)...)
}
}
@@ -254,14 +287,14 @@ func validateValueValidation(v *ValueValidation, skipAnyOf, skipFirstAllOfAnyOf
if skipFirstAllOfAnyOf && i == 0 {
skipAnyOf = true
}
allErrs = append(allErrs, validateNestedValueValidation(&v.AllOf[i], skipAnyOf, false, lvl, fldPath.Child("allOf").Index(i))...)
allErrs = append(allErrs, validateNestedValueValidation(&v.AllOf[i], skipAnyOf, false, lvl, fldPath.Child("allOf").Index(i), opts)...)
}
for i := range v.OneOf {
allErrs = append(allErrs, validateNestedValueValidation(&v.OneOf[i], false, false, lvl, fldPath.Child("oneOf").Index(i))...)
allErrs = append(allErrs, validateNestedValueValidation(&v.OneOf[i], false, false, lvl, fldPath.Child("oneOf").Index(i), optsWithCELDisabled)...)
}
allErrs = append(allErrs, validateNestedValueValidation(v.Not, false, false, lvl, fldPath.Child("not"))...)
allErrs = append(allErrs, validateNestedValueValidation(v.Not, false, false, lvl, fldPath.Child("not"), optsWithCELDisabled)...)
if len(v.Pattern) > 0 {
if _, err := regexp.Compile(v.Pattern); err != nil {
@@ -273,25 +306,27 @@ func validateValueValidation(v *ValueValidation, skipAnyOf, skipFirstAllOfAnyOf
}
// validateNestedValueValidation checks the nested value validation under a logic junctor in a structural schema.
func validateNestedValueValidation(v *NestedValueValidation, skipAnyOf, skipAllOfAnyOf bool, lvl level, fldPath *field.Path) field.ErrorList {
func validateNestedValueValidation(v *NestedValueValidation, skipAnyOf, skipAllOfAnyOf bool, lvl level, fldPath *field.Path, opts ValidationOptions) field.ErrorList {
if v == nil {
return nil
}
allErrs := field.ErrorList{}
allErrs = append(allErrs, validateValueValidation(&v.ValueValidation, skipAnyOf, skipAllOfAnyOf, lvl, fldPath)...)
allErrs = append(allErrs, validateNestedValueValidation(v.Items, false, false, lvl, fldPath.Child("items"))...)
allErrs = append(allErrs, validateValueValidation(&v.ValueValidation, skipAnyOf, skipAllOfAnyOf, lvl, fldPath, opts)...)
allErrs = append(allErrs, validateNestedValueValidation(v.Items, false, false, lvl, fldPath.Child("items"), opts)...)
for k, fld := range v.Properties {
allErrs = append(allErrs, validateNestedValueValidation(&fld, false, false, fieldLevel, fldPath.Child("properties").Key(k))...)
allErrs = append(allErrs, validateNestedValueValidation(&fld, false, false, fieldLevel, fldPath.Child("properties").Key(k), opts)...)
}
if len(v.ForbiddenGenerics.Type) > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("type"), "must be empty to be structural"))
}
if v.ForbiddenGenerics.AdditionalProperties != nil {
if v.AdditionalProperties != nil && !opts.AllowNestedAdditionalProperties {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalProperties"), "must be undefined to be structural"))
} else {
allErrs = append(allErrs, validateNestedValueValidation(v.AdditionalProperties, false, false, lvl, fldPath.Child("additionalProperties"), opts)...)
}
if v.ForbiddenGenerics.Default.Object != nil {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("default"), "must be undefined to be structural"))
@@ -324,7 +359,7 @@ func validateNestedValueValidation(v *NestedValueValidation, skipAnyOf, skipAllO
if v.ForbiddenExtensions.XMapType != nil {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-map-type"), "must be undefined to be structural"))
}
if len(v.ForbiddenExtensions.XValidations) > 0 {
if len(v.ValidationExtensions.XValidations) > 0 && !opts.AllowNestedXValidations {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-validations"), "must be empty to be structural"))
}

View File

@@ -50,8 +50,8 @@ func (m *Visitor) visitStructural(s *Structural) bool {
s.Properties[k] = v
}
}
if s.Generic.AdditionalProperties != nil && s.Generic.AdditionalProperties.Structural != nil {
m.visitStructural(s.Generic.AdditionalProperties.Structural)
if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil {
m.visitStructural(s.AdditionalProperties.Structural)
}
if s.ValueValidation != nil {
for i := range s.ValueValidation.AllOf {
@@ -86,8 +86,8 @@ func (m *Visitor) visitNestedValueValidation(vv *NestedValueValidation) bool {
vv.Properties[k] = v
}
}
if vv.ForbiddenGenerics.AdditionalProperties != nil && vv.ForbiddenGenerics.AdditionalProperties.Structural != nil {
m.visitStructural(vv.ForbiddenGenerics.AdditionalProperties.Structural)
if vv.AdditionalProperties != nil {
m.visitNestedValueValidation(vv.AdditionalProperties)
}
for i := range vv.ValueValidation.AllOf {
m.visitNestedValueValidation(&vv.ValueValidation.AllOf[i])

View File

@@ -43,13 +43,6 @@ func (in *Extensions) DeepCopyInto(out *Extensions) {
*out = new(string)
**out = **in
}
if in.XValidations != nil {
in, out := &in.XValidations, &out.XValidations
*out = make(v1.ValidationRules, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
@@ -67,11 +60,6 @@ func (in *Extensions) DeepCopy() *Extensions {
func (in *Generic) DeepCopyInto(out *Generic) {
*out = *in
out.Default = in.Default.DeepCopy()
if in.AdditionalProperties != nil {
in, out := &in.AdditionalProperties, &out.AdditionalProperties
*out = new(StructuralOrBool)
(*in).DeepCopyInto(*out)
}
return
}
@@ -89,6 +77,7 @@ func (in *Generic) DeepCopy() *Generic {
func (in *NestedValueValidation) DeepCopyInto(out *NestedValueValidation) {
*out = *in
in.ValueValidation.DeepCopyInto(&out.ValueValidation)
in.ValidationExtensions.DeepCopyInto(&out.ValidationExtensions)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = new(NestedValueValidation)
@@ -101,6 +90,11 @@ func (in *NestedValueValidation) DeepCopyInto(out *NestedValueValidation) {
(*out)[key] = *val.DeepCopy()
}
}
if in.AdditionalProperties != nil {
in, out := &in.AdditionalProperties, &out.AdditionalProperties
*out = new(NestedValueValidation)
(*in).DeepCopyInto(*out)
}
in.ForbiddenGenerics.DeepCopyInto(&out.ForbiddenGenerics)
in.ForbiddenExtensions.DeepCopyInto(&out.ForbiddenExtensions)
return
@@ -131,8 +125,14 @@ func (in *Structural) DeepCopyInto(out *Structural) {
(*out)[key] = *val.DeepCopy()
}
}
if in.AdditionalProperties != nil {
in, out := &in.AdditionalProperties, &out.AdditionalProperties
*out = new(StructuralOrBool)
(*in).DeepCopyInto(*out)
}
in.Generic.DeepCopyInto(&out.Generic)
in.Extensions.DeepCopyInto(&out.Extensions)
in.ValidationExtensions.DeepCopyInto(&out.ValidationExtensions)
if in.ValueValidation != nil {
in, out := &in.ValueValidation, &out.ValueValidation
*out = new(ValueValidation)
@@ -172,6 +172,29 @@ func (in *StructuralOrBool) DeepCopy() *StructuralOrBool {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ValidationExtensions) DeepCopyInto(out *ValidationExtensions) {
*out = *in
if in.XValidations != nil {
in, out := &in.XValidations, &out.XValidations
*out = make(v1.ValidationRules, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidationExtensions.
func (in *ValidationExtensions) DeepCopy() *ValidationExtensions {
if in == nil {
return nil
}
out := new(ValidationExtensions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ValueValidation) DeepCopyInto(out *ValueValidation) {
*out = *in