diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation_test.go index b4e8e24fc12..2d56211281e 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation_test.go @@ -989,6 +989,35 @@ func genStringWithRule(rule string) func(maxLength *int64) *schema.Structural { } } +// genEnumWithRuleAndValues creates a function that accepts an optional maxLength +// with given validation rule and a set of enum values, following the convention of existing tests. +// The test has two checks, first with maxLength unset to check if maxLength can be concluded from enums, +// second with maxLength set to ensure it takes precedence. +func genEnumWithRuleAndValues(rule string, values ...string) func(maxLength *int64) *schema.Structural { + enums := make([]schema.JSON, 0, len(values)) + for _, v := range values { + enums = append(enums, schema.JSON{Object: v}) + } + return func(maxLength *int64) *schema.Structural { + return &schema.Structural{ + Generic: schema.Generic{ + Type: "string", + }, + ValueValidation: &schema.ValueValidation{ + MaxLength: maxLength, + Enum: enums, + }, + Extensions: schema.Extensions{ + XValidations: apiextensions.ValidationRules{ + { + Rule: rule, + }, + }, + }, + } + } +} + func genBytesWithRule(rule string) func(maxLength *int64) *schema.Structural { return func(maxLength *int64) *schema.Structural { return &schema.Structural{ @@ -1744,6 +1773,13 @@ func TestCostEstimation(t *testing.T) { setMaxElements: 42, expectedSetCost: 8, }, + { + name: "enums with maxLength equals to the longest possible value", + schemaGenerator: genEnumWithRuleAndValues("self.contains('A')", "A", "B", "C", "LongValue"), + expectedCalcCost: 2, + setMaxElements: 1000, + expectedSetCost: 401, + }, } for _, testCase := range cases { t.Run(testCase.name, func(t *testing.T) { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go index 0fdac7635b9..b09d32c2749 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go @@ -306,10 +306,10 @@ func (a customResourceStrategy) MatchCustomResourceDefinitionStorage(label label } } -// OpenAPIv3 type/maxLength/maxItems/MaxProperties/required/wrong type field validation failures are viewed as blocking err for CEL validation +// OpenAPIv3 type/maxLength/maxItems/MaxProperties/required/enum violation/wrong type field validation failures are viewed as blocking err for CEL validation func hasBlockingErr(errs field.ErrorList) (bool, *field.Error) { for _, err := range errs { - if err.Type == field.ErrorTypeRequired || err.Type == field.ErrorTypeTooLong || err.Type == field.ErrorTypeTooMany || err.Type == field.ErrorTypeTypeInvalid { + if err.Type == field.ErrorTypeNotSupported || err.Type == field.ErrorTypeRequired || err.Type == field.ErrorTypeTooLong || err.Type == field.ErrorTypeTooMany || err.Type == field.ErrorTypeTypeInvalid { return true, field.Invalid(nil, nil, "some validation rules were not checked because the object was invalid; correct the existing errors to complete validation") } } diff --git a/staging/src/k8s.io/apiserver/pkg/cel/common/schemas.go b/staging/src/k8s.io/apiserver/pkg/cel/common/schemas.go index 3fdd3a6c8ba..19392babeb2 100644 --- a/staging/src/k8s.io/apiserver/pkg/cel/common/schemas.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/common/schemas.go @@ -165,7 +165,11 @@ func SchemaDeclType(s Schema, isResourceRoot bool) *apiservercel.DeclType { // unicode code point can be up to 4 bytes long) strWithMaxLength.MaxElements = zeroIfNegative(*s.MaxLength()) * 4 } else { - strWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s) + if len(s.Enum()) > 0 { + strWithMaxLength.MaxElements = estimateMaxStringEnumLength(s) + } else { + strWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s) + } } return strWithMaxLength case "boolean": @@ -239,6 +243,19 @@ func estimateMaxStringLengthPerRequest(s Schema) int64 { } } +// estimateMaxStringLengthPerRequest estimates the maximum string length (in characters) +// that has a set of enum values. +// The result of the estimation is the length of the longest possible value. +func estimateMaxStringEnumLength(s Schema) int64 { + var maxLength int64 + for _, v := range s.Enum() { + if s, ok := v.(string); ok && int64(len(s)) > maxLength { + maxLength = int64(len(s)) + } + } + return maxLength +} + // estimateMaxArrayItemsPerRequest estimates the maximum number of array items with // the provided minimum serialized size that can fit into a single request. func estimateMaxArrayItemsFromMinSize(minSize int64) int64 {