diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/cel_validation.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/cel_validation.go index 854a61024d0..a03615c28e3 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/cel_validation.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/cel_validation.go @@ -25,6 +25,7 @@ import ( structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/cel" ) // unbounded uses nil to represent an unbounded cardinality value. @@ -109,7 +110,7 @@ type CELTypeInfo struct { // Schema is a structural schema for this CELSchemaContext node. It must be non-nil. Schema *structuralschema.Structural // DeclType is a CEL declaration representation of Schema of this CELSchemaContext node. It must be non-nil. - DeclType *model.DeclType + DeclType *cel.DeclType } // converter converts from JSON schema to a structural schema and a CEL declType, or returns an error if the conversion @@ -224,7 +225,7 @@ type propertyTypeInfoAccessor struct { func (c propertyTypeInfoAccessor) accessTypeInfo(parentTypeInfo *CELTypeInfo) *CELTypeInfo { if parentTypeInfo.Schema.Properties != nil { propSchema := parentTypeInfo.Schema.Properties[c.propertyName] - if escapedPropName, ok := model.Escape(c.propertyName); ok { + if escapedPropName, ok := cel.Escape(c.propertyName); ok { if fieldDeclType, ok := parentTypeInfo.DeclType.Fields[escapedPropName]; ok { return &CELTypeInfo{Schema: &propSchema, DeclType: fieldDeclType.Type} } // else fields with unknown types are omitted from CEL validation entirely diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go index 92940e1ba8b..be4252c14c4 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go @@ -34,6 +34,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" utilvalidation "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" + apiservercel "k8s.io/apiserver/pkg/cel" "k8s.io/apiserver/pkg/util/webhook" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" @@ -1038,7 +1039,7 @@ func ValidateCustomResourceDefinitionOpenAPISchema(schema *apiextensions.JSONSch celContext.TotalCost.ObserveExpressionCost(fldPath.Child("x-kubernetes-validations").Index(i).Child("rule"), expressionCost) } if cr.Error != nil { - if cr.Error.Type == cel.ErrorTypeRequired { + if cr.Error.Type == apiservercel.ErrorTypeRequired { allErrs.CELErrors = append(allErrs.CELErrors, field.Required(fldPath.Child("x-kubernetes-validations").Index(i).Child("rule"), cr.Error.Detail)) } else { allErrs.CELErrors = append(allErrs.CELErrors, field.Invalid(fldPath.Child("x-kubernetes-validations").Index(i).Child("rule"), schema.XValidations[i], cr.Error.Detail)) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation.go index 6c93a557857..4a45d8f3a83 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation.go @@ -24,11 +24,13 @@ import ( "github.com/google/cel-go/cel" "github.com/google/cel-go/checker" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" - "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library" - "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/metrics" celmodel "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model" + apiservercel "k8s.io/apiserver/pkg/cel" + "k8s.io/apiserver/pkg/cel/library" + "k8s.io/apiserver/pkg/cel/metrics" ) const ( @@ -56,7 +58,7 @@ const ( // CompilationResult represents the cel compilation result for one rule type CompilationResult struct { Program cel.Program - Error *Error + Error *apiservercel.Error // If true, the compiled expression contains a reference to the identifier "oldSelf", and its corresponding rule // is implicitly a transition rule. TransitionRule bool @@ -97,7 +99,7 @@ func getBaseEnv() (*cel.Env, error) { // - nil Program, nil Error: The provided rule was empty so compilation was not attempted // // perCallLimit was added for testing purpose only. Callers should always use const PerCallLimit as input. -func Compile(s *schema.Structural, declType *celmodel.DeclType, perCallLimit uint64) ([]CompilationResult, error) { +func Compile(s *schema.Structural, declType *apiservercel.DeclType, perCallLimit uint64) ([]CompilationResult, error) { t := time.Now() defer metrics.Metrics.ObserveCompilation(time.Since(t)) if len(s.Extensions.XValidations) == 0 { @@ -106,15 +108,15 @@ func Compile(s *schema.Structural, declType *celmodel.DeclType, perCallLimit uin celRules := s.Extensions.XValidations var propDecls []cel.EnvOption - var root *celmodel.DeclType + var root *apiservercel.DeclType var ok bool baseEnv, err := getBaseEnv() if err != nil { return nil, err } - reg := celmodel.NewRegistry(baseEnv) + reg := apiservercel.NewRegistry(baseEnv) scopedTypeName := generateUniqueSelfTypeName() - rt, err := celmodel.NewRuleTypes(scopedTypeName, declType, reg) + rt, err := apiservercel.NewRuleTypes(scopedTypeName, declType, reg) if err != nil { return nil, err } @@ -158,18 +160,18 @@ func compileRule(rule apiextensions.ValidationRule, env *cel.Env, perCallLimit u } ast, issues := env.Compile(rule.Rule) if issues != nil { - compilationResult.Error = &Error{ErrorTypeInvalid, "compilation failed: " + issues.String()} + compilationResult.Error = &apiservercel.Error{apiservercel.ErrorTypeInvalid, "compilation failed: " + issues.String()} return } if ast.OutputType() != cel.BoolType { - compilationResult.Error = &Error{ErrorTypeInvalid, "cel expression must evaluate to a bool"} + compilationResult.Error = &apiservercel.Error{apiservercel.ErrorTypeInvalid, "cel expression must evaluate to a bool"} return } checkedExpr, err := cel.AstToCheckedExpr(ast) if err != nil { // should be impossible since env.Compile returned no issues - compilationResult.Error = &Error{ErrorTypeInternal, "unexpected compilation error: " + err.Error()} + compilationResult.Error = &apiservercel.Error{apiservercel.ErrorTypeInternal, "unexpected compilation error: " + err.Error()} return } for _, ref := range checkedExpr.ReferenceMap { @@ -188,12 +190,12 @@ func compileRule(rule apiextensions.ValidationRule, env *cel.Env, perCallLimit u cel.InterruptCheckFrequency(checkFrequency), ) if err != nil { - compilationResult.Error = &Error{ErrorTypeInvalid, "program instantiation failed: " + err.Error()} + compilationResult.Error = &apiservercel.Error{apiservercel.ErrorTypeInvalid, "program instantiation failed: " + err.Error()} return } costEst, err := env.EstimateCost(ast, estimator) if err != nil { - compilationResult.Error = &Error{ErrorTypeInternal, "cost estimation failed: " + err.Error()} + compilationResult.Error = &apiservercel.Error{apiservercel.ErrorTypeInternal, "cost estimation failed: " + err.Error()} return } compilationResult.MaxCost = costEst.Max @@ -210,12 +212,12 @@ func generateUniqueSelfTypeName() string { return fmt.Sprintf("selfType%d", time.Now().Nanosecond()) } -func newCostEstimator(root *celmodel.DeclType) *library.CostEstimator { +func newCostEstimator(root *apiservercel.DeclType) *library.CostEstimator { return &library.CostEstimator{SizeEstimator: &sizeEstimator{root: root}} } type sizeEstimator struct { - root *celmodel.DeclType + root *apiservercel.DeclType } func (c *sizeEstimator) EstimateSize(element checker.AstNode) *checker.SizeEstimate { 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 759d5137e6a..f9f4f567503 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 @@ -22,6 +22,8 @@ import ( "strings" "testing" + "k8s.io/apiserver/pkg/cel" + 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" @@ -79,12 +81,12 @@ func (m fnMatcher) String() string { } type errorMatcher struct { - errorType ErrorType + errorType cel.ErrorType contains string } func invalidError(contains string) validationMatcher { - return errorMatcher{errorType: ErrorTypeInvalid, contains: contains} + return errorMatcher{errorType: cel.ErrorTypeInvalid, contains: contains} } func (v errorMatcher) matches(cr CompilationResult) bool { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/README.md b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/README.md deleted file mode 100644 index 6b38d871535..00000000000 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/README.md +++ /dev/null @@ -1,4 +0,0 @@ -This directory contains a port of https://github.com/google/cel-policy-templates-go/tree/master/policy/model modified in a few ways: - -- Uses the Structural schema types -- All template related code has been removed diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/schemas.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/schemas.go index 3d2a4730a4b..b9b47caea50 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/schemas.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/schemas.go @@ -22,37 +22,16 @@ import ( "github.com/google/cel-go/cel" "github.com/google/cel-go/common/types" + apiservercel "k8s.io/apiserver/pkg/cel" + "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" ) -const ( - // the largest request that will be accepted is 3MB - // TODO(DangerOnTheRanger): wire in MaxRequestBodyBytes from apiserver/pkg/server/options/server_run_options.go to make this configurable - maxRequestSizeBytes = int64(3 * 1024 * 1024) - // OpenAPI duration strings follow RFC 3339, section 5.6 - see the comment on maxDatetimeSizeJSON - maxDurationSizeJSON = 32 - // OpenAPI datetime strings follow RFC 3339, section 5.6, and the longest possible - // such string is 9999-12-31T23:59:59.999999999Z, which has length 30 - we add 2 - // to allow for quotation marks - maxDatetimeSizeJSON = 32 - // Golang allows a string of 0 to be parsed as a duration, so that plus 2 to account for - // quotation marks makes 3 - minDurationSizeJSON = 3 - // RFC 3339 dates require YYYY-MM-DD, and then we add 2 to allow for quotation marks - dateSizeJSON = 12 - // RFC 3339 datetimes require a full date (YYYY-MM-DD) and full time (HH:MM:SS), and we add 3 for - // quotation marks like always in addition to the capital T that separates the date and time - minDatetimeSizeJSON = 21 - // "" - minStringSize = 2 - // true - minBoolSize = 4 - // 0 - minNumberSize = 1 -) +// TODO(DangerOnTheRanger): wire in MaxRequestBodyBytes from apiserver/pkg/server/options/server_run_options.go to make this configurable +const maxRequestSizeBytes = apiservercel.DefaultMaxRequestSizeBytes // SchemaDeclType converts the structural schema to a CEL declaration, or returns nil if the -// the structural schema should not be exposed in CEL expressions. +// structural schema should not be exposed in CEL expressions. // Set isResourceRoot to true for the root of a custom resource or embedded resource. // // Schemas with XPreserveUnknownFields not exposed unless they are objects. Array and "maps" schemas @@ -60,7 +39,7 @@ const ( // if their schema is not exposed. // // The CEL declaration for objects with XPreserveUnknownFields does not expose unknown fields. -func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType { +func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *apiservercel.DeclType { if s == nil { return nil } @@ -77,7 +56,7 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType { // To validate requirements on both the int and string representation: // `type(intOrStringField) == int ? intOrStringField < 5 : double(intOrStringField.replace('%', '')) < 0.5 // - dyn := newSimpleTypeWithMinSize("dyn", cel.DynType, nil, 1) // smallest value for a serialied x-kubernetes-int-or-string is 0 + dyn := apiservercel.NewSimpleTypeWithMinSize("dyn", cel.DynType, nil, 1) // smallest value for a serialized x-kubernetes-int-or-string is 0 // handle x-kubernetes-int-or-string by returning the max length/min serialized size of the largest possible string dyn.MaxElements = maxRequestSizeBytes - 2 return dyn @@ -106,7 +85,7 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType { } else { maxItems = estimateMaxArrayItemsFromMinSize(itemsType.MinSerializedSize) } - return NewListType(itemsType, maxItems) + return apiservercel.NewListType(itemsType, maxItems) } return nil case "object": @@ -119,11 +98,11 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType { } else { maxProperties = estimateMaxAdditionalPropertiesFromMinSize(propsType.MinSerializedSize) } - return NewMapType(StringType, propsType, maxProperties) + return apiservercel.NewMapType(apiservercel.StringType, propsType, maxProperties) } return nil } - fields := make(map[string]*DeclField, len(s.Properties)) + fields := make(map[string]*apiservercel.DeclField, len(s.Properties)) required := map[string]bool{} if s.ValueValidation != nil { @@ -141,14 +120,8 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType { } } if fieldType := SchemaDeclType(&prop, prop.XEmbeddedResource); fieldType != nil { - if propName, ok := Escape(name); ok { - fields[propName] = &DeclField{ - Name: propName, - Required: required[name], - Type: fieldType, - defaultValue: prop.Default.Object, - enumValues: enumValues, // Enum values are represented as strings in CEL - } + if propName, ok := apiservercel.Escape(name); ok { + fields[propName] = apiservercel.NewDeclField(propName, fieldType, required[name], enumValues, prop.Default.Object) } // the min serialized size for an object is 2 (for {}) plus the min size of all its required // properties @@ -159,14 +132,14 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType { } } } - objType := NewObjectType("object", fields) + objType := apiservercel.NewObjectType("object", fields) objType.MinSerializedSize = minSerializedSize return objType case "string": if s.ValueValidation != nil { switch s.ValueValidation.Format { case "byte": - byteWithMaxLength := newSimpleTypeWithMinSize("bytes", cel.BytesType, types.Bytes([]byte{}), minStringSize) + byteWithMaxLength := apiservercel.NewSimpleTypeWithMinSize("bytes", cel.BytesType, types.Bytes([]byte{}), apiservercel.MinStringSize) if s.ValueValidation.MaxLength != nil { byteWithMaxLength.MaxElements = zeroIfNegative(*s.ValueValidation.MaxLength) } else { @@ -174,20 +147,20 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType { } return byteWithMaxLength case "duration": - durationWithMaxLength := newSimpleTypeWithMinSize("duration", cel.DurationType, types.Duration{Duration: time.Duration(0)}, int64(minDurationSizeJSON)) + durationWithMaxLength := apiservercel.NewSimpleTypeWithMinSize("duration", cel.DurationType, types.Duration{Duration: time.Duration(0)}, int64(apiservercel.MinDurationSizeJSON)) durationWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s) return durationWithMaxLength case "date": - timestampWithMaxLength := newSimpleTypeWithMinSize("timestamp", cel.TimestampType, types.Timestamp{Time: time.Time{}}, int64(dateSizeJSON)) + timestampWithMaxLength := apiservercel.NewSimpleTypeWithMinSize("timestamp", cel.TimestampType, types.Timestamp{Time: time.Time{}}, int64(apiservercel.JSONDateSize)) timestampWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s) return timestampWithMaxLength case "date-time": - timestampWithMaxLength := newSimpleTypeWithMinSize("timestamp", cel.TimestampType, types.Timestamp{Time: time.Time{}}, int64(minDatetimeSizeJSON)) + timestampWithMaxLength := apiservercel.NewSimpleTypeWithMinSize("timestamp", cel.TimestampType, types.Timestamp{Time: time.Time{}}, int64(apiservercel.MinDatetimeSizeJSON)) timestampWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s) return timestampWithMaxLength } } - strWithMaxLength := newSimpleTypeWithMinSize("string", cel.StringType, types.String(""), minStringSize) + strWithMaxLength := apiservercel.NewSimpleTypeWithMinSize("string", cel.StringType, types.String(""), apiservercel.MinStringSize) if s.ValueValidation != nil && s.ValueValidation.MaxLength != nil { // multiply the user-provided max length by 4 in the case of an otherwise-untyped string // we do this because the OpenAPIv3 spec indicates that maxLength is specified in runes/code points, @@ -199,11 +172,11 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType { } return strWithMaxLength case "boolean": - return BoolType + return apiservercel.BoolType case "number": - return DoubleType + return apiservercel.DoubleType case "integer": - return IntType + return apiservercel.IntType } return nil } @@ -268,18 +241,18 @@ func MaxCardinality(minSize int64) uint64 { func estimateMaxStringLengthPerRequest(s *schema.Structural) int64 { if s.ValueValidation == nil || s.XIntOrString { // subtract 2 to account for "" - return (maxRequestSizeBytes - 2) + return maxRequestSizeBytes - 2 } switch s.ValueValidation.Format { case "duration": - return maxDurationSizeJSON + return apiservercel.MaxDurationSizeJSON case "date": - return dateSizeJSON + return apiservercel.JSONDateSize case "date-time": - return maxDatetimeSizeJSON + return apiservercel.MaxDatetimeSizeJSON default: // subtract 2 to account for "" - return (maxRequestSizeBytes - 2) + return maxRequestSizeBytes - 2 } } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/schemas_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/schemas_test.go index aa5c1e9a93a..04ae3a1a415 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/schemas_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/schemas_test.go @@ -25,6 +25,7 @@ import ( "google.golang.org/protobuf/proto" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" + apiservercel "k8s.io/apiserver/pkg/cel" ) func TestSchemaDeclType(t *testing.T) { @@ -86,15 +87,15 @@ func TestSchemaDeclType(t *testing.T) { func TestSchemaDeclTypes(t *testing.T) { ts := testSchema() cust := SchemaDeclType(ts, true).MaybeAssignTypeName("CustomObject") - typeMap := FieldTypeMap("CustomObject", cust) + typeMap := apiservercel.FieldTypeMap("CustomObject", cust) nested, _ := cust.FindField("nested") metadata, _ := cust.FindField("metadata") - expectedObjTypeMap := map[string]*DeclType{ + expectedObjTypeMap := map[string]*apiservercel.DeclType{ "CustomObject": cust, "CustomObject.nested": nested.Type, "CustomObject.metadata": metadata.Type, } - objTypeMap := map[string]*DeclType{} + objTypeMap := map[string]*apiservercel.DeclType{} for name, t := range typeMap { if t.IsObject() { objTypeMap[name] = t @@ -406,7 +407,7 @@ func TestEstimateMaxLengthJSON(t *testing.T) { }, }, // should be exactly equal to maxDurationSizeJSON - ExpectedMaxElements: maxDurationSizeJSON, + ExpectedMaxElements: apiservercel.MaxDurationSizeJSON, }, { Name: "dateSize", @@ -419,7 +420,7 @@ func TestEstimateMaxLengthJSON(t *testing.T) { }, }, // should be exactly equal to dateSizeJSON - ExpectedMaxElements: dateSizeJSON, + ExpectedMaxElements: apiservercel.JSONDateSize, }, { Name: "maxdatetimeSize", @@ -432,7 +433,7 @@ func TestEstimateMaxLengthJSON(t *testing.T) { }, }, // should be exactly equal to maxDatetimeSizeJSON - ExpectedMaxElements: maxDatetimeSizeJSON, + ExpectedMaxElements: apiservercel.MaxDatetimeSizeJSON, }, { Name: "maxintOrStringSize", @@ -442,7 +443,7 @@ func TestEstimateMaxLengthJSON(t *testing.T) { }, }, // should be exactly equal to maxRequestSizeBytes - 2 (to allow for quotes in the case of a string) - ExpectedMaxElements: maxRequestSizeBytes - 2, + ExpectedMaxElements: apiservercel.DefaultMaxRequestSizeBytes - 2, }, { Name: "objectDefaultFieldArray", @@ -495,7 +496,7 @@ func TestEstimateMaxLengthJSON(t *testing.T) { }, }, // note that unlike regular strings we don't have to take unicode into account, - // so we we expect the max length to be exactly equal to the user-supplied one + // so we expect the max length to be exactly equal to the user-supplied one ExpectedMaxElements: 20, }, } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/types_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/types_test.go index 9ad95ccbb26..a8102715840 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/types_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/types_test.go @@ -23,61 +23,14 @@ import ( "github.com/google/cel-go/common/types" exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" + + apiservercel "k8s.io/apiserver/pkg/cel" ) -func TestTypes_ListType(t *testing.T) { - list := NewListType(StringType, -1) - if !list.IsList() { - t.Error("list type not identifiable as list") - } - if list.TypeName() != "list" { - t.Errorf("got %s, wanted list", list.TypeName()) - } - if list.DefaultValue() == nil { - t.Error("got nil zero value for list type") - } - if list.ElemType.TypeName() != "string" { - t.Errorf("got %s, wanted elem type of string", list.ElemType.TypeName()) - } - expT, err := list.ExprType() - if err != nil { - t.Errorf("fail to get cel type: %s", err) - } - if expT.GetListType() == nil { - t.Errorf("got %v, wanted CEL list type", expT) - } -} - -func TestTypes_MapType(t *testing.T) { - mp := NewMapType(StringType, IntType, -1) - if !mp.IsMap() { - t.Error("map type not identifiable as map") - } - if mp.TypeName() != "map" { - t.Errorf("got %s, wanted map", mp.TypeName()) - } - if mp.DefaultValue() == nil { - t.Error("got nil zero value for map type") - } - if mp.KeyType.TypeName() != "string" { - t.Errorf("got %s, wanted key type of string", mp.KeyType.TypeName()) - } - if mp.ElemType.TypeName() != "int" { - t.Errorf("got %s, wanted elem type of int", mp.ElemType.TypeName()) - } - expT, err := mp.ExprType() - if err != nil { - t.Errorf("fail to get cel type: %s", err) - } - if expT.GetMapType() == nil { - t.Errorf("got %v, wanted CEL map type", expT) - } -} - func TestTypes_RuleTypesFieldMapping(t *testing.T) { stdEnv, _ := cel.NewEnv() - reg := NewRegistry(stdEnv) - rt, err := NewRuleTypes("CustomObject", SchemaDeclType(testSchema(), true), reg) + reg := apiservercel.NewRegistry(stdEnv) + rt, err := apiservercel.NewRuleTypes("CustomObject", SchemaDeclType(testSchema(), true), reg) if err != nil { t.Fatal(err) } @@ -112,22 +65,22 @@ func TestTypes_RuleTypesFieldMapping(t *testing.T) { } // Manually constructed instance of the schema. - name := NewField(1, "name") + name := apiservercel.NewField(1, "name") name.Ref = testValue(t, 2, "test-instance") - nestedVal := NewMapValue() - flags := NewField(5, "flags") - flagsVal := NewMapValue() - myFlag := NewField(6, "my_flag") + nestedVal := apiservercel.NewMapValue() + flags := apiservercel.NewField(5, "flags") + flagsVal := apiservercel.NewMapValue() + myFlag := apiservercel.NewField(6, "my_flag") myFlag.Ref = testValue(t, 7, true) flagsVal.AddField(myFlag) flags.Ref = testValue(t, 8, flagsVal) - dates := NewField(9, "dates") - dates.Ref = testValue(t, 10, NewListValue()) + dates := apiservercel.NewField(9, "dates") + dates.Ref = testValue(t, 10, apiservercel.NewListValue()) nestedVal.AddField(flags) nestedVal.AddField(dates) - nested := NewField(3, "nested") + nested := apiservercel.NewField(3, "nested") nested.Ref = testValue(t, 4, nestedVal) - mapVal := NewMapValue() + mapVal := apiservercel.NewMapValue() mapVal.AddField(name) mapVal.AddField(nested) //rule := rt.ConvertToRule(testValue(t, 11, mapVal)) @@ -156,11 +109,11 @@ func TestTypes_RuleTypesFieldMapping(t *testing.T) { } } -func testValue(t *testing.T, id int64, val interface{}) *DynValue { +func testValue(t *testing.T, id int64, val interface{}) *apiservercel.DynValue { t.Helper() - dv, err := NewDynValue(id, val) + dv, err := apiservercel.NewDynValue(id, val) if err != nil { - t.Fatalf("model.NewDynValue(%d, %v) failed: %v", id, val, err) + t.Fatalf("NewDynValue(%d, %v) failed: %v", id, val, err) } return dv } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/validation.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/validation.go index cb8e9c8ae95..a5fc5a737b6 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/validation.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/validation.go @@ -30,9 +30,10 @@ import ( apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" - "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/metrics" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/cel" + "k8s.io/apiserver/pkg/cel/metrics" ) // Validator parallels the structure of schema.Structural and includes the compiled CEL programs @@ -74,7 +75,7 @@ func NewValidator(s *schema.Structural, isResourceRoot bool, perCallLimit uint64 // 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. -func validator(s *schema.Structural, isResourceRoot bool, declType *model.DeclType, perCallLimit uint64) *Validator { +func validator(s *schema.Structural, isResourceRoot bool, declType *cel.DeclType, perCallLimit uint64) *Validator { compiledRules, err := Compile(s, declType, perCallLimit) var itemsValidator, additionalPropertiesValidator *Validator var propertiesValidators map[string]Validator @@ -85,8 +86,8 @@ func validator(s *schema.Structural, isResourceRoot bool, declType *model.DeclTy propertiesValidators = make(map[string]Validator, len(s.Properties)) for k, p := range s.Properties { prop := p - var fieldType *model.DeclType - if escapedPropName, ok := model.Escape(k); ok { + var fieldType *cel.DeclType + if escapedPropName, ok := cel.Escape(k); ok { if f, ok := declType.Fields[escapedPropName]; ok { fieldType = f.Type } else { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/values.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/values.go index ea6a6301f9e..c7e159f5527 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/values.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/values.go @@ -25,10 +25,11 @@ import ( "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/common/types/traits" - "k8s.io/apimachinery/pkg/api/equality" structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apiserver/pkg/cel" "k8s.io/kube-openapi/pkg/validation/strfmt" ) @@ -343,7 +344,7 @@ func (t *unstructuredMapList) Add(other ref.Val) ref.Val { func escapeKeyProps(idents []string) []string { result := make([]string, len(idents)) for i, prop := range idents { - if escaped, ok := model.Escape(prop); ok { + if escaped, ok := cel.Escape(prop); ok { result[i] = escaped } else { result[i] = prop @@ -644,7 +645,7 @@ func (t *unstructuredMap) Iterator() traits.Iterator { if _, ok := t.propSchema(k); ok { mapKey := k if isObject { - if escaped, ok := model.Escape(k); ok { + if escaped, ok := cel.Escape(k); ok { mapKey = escaped } } @@ -683,7 +684,7 @@ func (t *unstructuredMap) Find(key ref.Val) (ref.Val, bool) { } k := keyStr.Value().(string) if isObject { - k, ok = model.Unescape(k) + k, ok = cel.Unescape(k) if !ok { return nil, false } diff --git a/staging/src/k8s.io/apiserver/go.mod b/staging/src/k8s.io/apiserver/go.mod index 6e962b30c5a..2346da27f37 100644 --- a/staging/src/k8s.io/apiserver/go.mod +++ b/staging/src/k8s.io/apiserver/go.mod @@ -12,6 +12,7 @@ require ( github.com/evanphx/json-patch v4.12.0+incompatible github.com/fsnotify/fsnotify v1.5.4 github.com/gogo/protobuf v1.3.2 + github.com/google/cel-go v0.12.5 github.com/google/gnostic v0.5.7-v3refs github.com/google/go-cmp v0.5.9 github.com/google/gofuzz v1.1.0 @@ -36,7 +37,9 @@ require ( golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 + google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 google.golang.org/grpc v1.49.0 + google.golang.org/protobuf v1.28.1 gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/square/go-jose.v2 v2.2.2 k8s.io/api v0.0.0 @@ -56,6 +59,7 @@ require ( require ( cloud.google.com/go v0.97.0 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect + github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect @@ -95,6 +99,7 @@ require ( github.com/sirupsen/logrus v1.8.1 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/spf13/cobra v1.5.0 // indirect + github.com/stoewer/go-strcase v1.2.0 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect go.etcd.io/bbolt v1.3.6 // indirect @@ -111,8 +116,6 @@ require ( golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.8 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect - google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/staging/src/k8s.io/apiserver/go.sum b/staging/src/k8s.io/apiserver/go.sum index 89b26b34c31..2914617cd58 100644 --- a/staging/src/k8s.io/apiserver/go.sum +++ b/staging/src/k8s.io/apiserver/go.sum @@ -57,6 +57,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= +github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -220,6 +222,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/cel-go v0.12.5 h1:DmzaiSgoaqGCjtpPQWl26/gND+yRpim56H1jCVev6d8= +github.com/google/cel-go v0.12.5/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -439,6 +443,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/errors.go b/staging/src/k8s.io/apiserver/pkg/cel/errors.go similarity index 100% rename from staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/errors.go rename to staging/src/k8s.io/apiserver/pkg/cel/errors.go diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/escaping.go b/staging/src/k8s.io/apiserver/pkg/cel/escaping.go similarity index 99% rename from staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/escaping.go rename to staging/src/k8s.io/apiserver/pkg/cel/escaping.go index c9d6463dd53..705c353a2b9 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/escaping.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/escaping.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package model +package cel import ( "regexp" diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/escaping_test.go b/staging/src/k8s.io/apiserver/pkg/cel/escaping_test.go similarity index 99% rename from staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/escaping_test.go rename to staging/src/k8s.io/apiserver/pkg/cel/escaping_test.go index 699da05bf24..e4b2aa90616 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/escaping_test.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/escaping_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package model +package cel import ( "fmt" diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/cost.go b/staging/src/k8s.io/apiserver/pkg/cel/library/cost.go similarity index 100% rename from staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/cost.go rename to staging/src/k8s.io/apiserver/pkg/cel/library/cost.go diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/cost_test.go b/staging/src/k8s.io/apiserver/pkg/cel/library/cost_test.go similarity index 100% rename from staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/cost_test.go rename to staging/src/k8s.io/apiserver/pkg/cel/library/cost_test.go diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/libraries.go b/staging/src/k8s.io/apiserver/pkg/cel/library/libraries.go similarity index 100% rename from staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/libraries.go rename to staging/src/k8s.io/apiserver/pkg/cel/library/libraries.go diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/library_compatibility_test.go b/staging/src/k8s.io/apiserver/pkg/cel/library/library_compatibility_test.go similarity index 100% rename from staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/library_compatibility_test.go rename to staging/src/k8s.io/apiserver/pkg/cel/library/library_compatibility_test.go diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/lists.go b/staging/src/k8s.io/apiserver/pkg/cel/library/lists.go similarity index 100% rename from staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/lists.go rename to staging/src/k8s.io/apiserver/pkg/cel/library/lists.go diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/regex.go b/staging/src/k8s.io/apiserver/pkg/cel/library/regex.go similarity index 100% rename from staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/regex.go rename to staging/src/k8s.io/apiserver/pkg/cel/library/regex.go diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/urls.go b/staging/src/k8s.io/apiserver/pkg/cel/library/urls.go similarity index 91% rename from staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/urls.go rename to staging/src/k8s.io/apiserver/pkg/cel/library/urls.go index 94688039d50..afe80f4936a 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/urls.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/library/urls.go @@ -22,7 +22,8 @@ import ( "github.com/google/cel-go/cel" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" - "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model" + + apiservercel "k8s.io/apiserver/pkg/cel" ) // URLs provides a CEL function library extension of URL parsing functions. @@ -113,25 +114,25 @@ type urls struct{} var urlLibraryDecls = map[string][]cel.FunctionOpt{ "url": { - cel.Overload("string_to_url", []*cel.Type{cel.StringType}, model.URLType, + cel.Overload("string_to_url", []*cel.Type{cel.StringType}, apiservercel.URLType, cel.UnaryBinding(stringToUrl))}, "getScheme": { - cel.MemberOverload("url_get_scheme", []*cel.Type{model.URLType}, cel.StringType, + cel.MemberOverload("url_get_scheme", []*cel.Type{apiservercel.URLType}, cel.StringType, cel.UnaryBinding(getScheme))}, "getHost": { - cel.MemberOverload("url_get_host", []*cel.Type{model.URLType}, cel.StringType, + cel.MemberOverload("url_get_host", []*cel.Type{apiservercel.URLType}, cel.StringType, cel.UnaryBinding(getHost))}, "getHostname": { - cel.MemberOverload("url_get_hostname", []*cel.Type{model.URLType}, cel.StringType, + cel.MemberOverload("url_get_hostname", []*cel.Type{apiservercel.URLType}, cel.StringType, cel.UnaryBinding(getHostname))}, "getPort": { - cel.MemberOverload("url_get_port", []*cel.Type{model.URLType}, cel.StringType, + cel.MemberOverload("url_get_port", []*cel.Type{apiservercel.URLType}, cel.StringType, cel.UnaryBinding(getPort))}, "getEscapedPath": { - cel.MemberOverload("url_get_escaped_path", []*cel.Type{model.URLType}, cel.StringType, + cel.MemberOverload("url_get_escaped_path", []*cel.Type{apiservercel.URLType}, cel.StringType, cel.UnaryBinding(getEscapedPath))}, "getQuery": { - cel.MemberOverload("url_get_query", []*cel.Type{model.URLType}, + cel.MemberOverload("url_get_query", []*cel.Type{apiservercel.URLType}, cel.MapType(cel.StringType, cel.ListType(cel.StringType)), cel.UnaryBinding(getQuery))}, "isURL": { @@ -169,7 +170,7 @@ func stringToUrl(arg ref.Val) ref.Val { // Errors are not expected here since Parse is a more lenient parser than ParseRequestURI. return types.NewErr("URL parse error during conversion from string: %v", err) } - return model.URL{URL: u} + return apiservercel.URL{URL: u} } func getScheme(arg ref.Val) ref.Val { diff --git a/staging/src/k8s.io/apiserver/pkg/cel/limits.go b/staging/src/k8s.io/apiserver/pkg/cel/limits.go new file mode 100644 index 00000000000..7bdb958d05c --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/cel/limits.go @@ -0,0 +1,48 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cel + +const ( + // DefaultMaxRequestSizeBytes is the size of the largest request that will be accepted + DefaultMaxRequestSizeBytes = int64(3 * 1024 * 1024) + + // MaxDurationSizeJSON + // OpenAPI duration strings follow RFC 3339, section 5.6 - see the comment on maxDatetimeSizeJSON + MaxDurationSizeJSON = 32 + // MaxDatetimeSizeJSON + // OpenAPI datetime strings follow RFC 3339, section 5.6, and the longest possible + // such string is 9999-12-31T23:59:59.999999999Z, which has length 30 - we add 2 + // to allow for quotation marks + MaxDatetimeSizeJSON = 32 + // MinDurationSizeJSON + // Golang allows a string of 0 to be parsed as a duration, so that plus 2 to account for + // quotation marks makes 3 + MinDurationSizeJSON = 3 + // JSONDateSize is the size of a date serialized as part of a JSON object + // RFC 3339 dates require YYYY-MM-DD, and then we add 2 to allow for quotation marks + JSONDateSize = 12 + // MinDatetimeSizeJSON is the minimal length of a datetime formatted as RFC 3339 + // RFC 3339 datetimes require a full date (YYYY-MM-DD) and full time (HH:MM:SS), and we add 3 for + // quotation marks like always in addition to the capital T that separates the date and time + MinDatetimeSizeJSON = 21 + // MinStringSize is the size of literal "" + MinStringSize = 2 + // MinBoolSize is the length of literal true + MinBoolSize = 4 + // MinNumberSize is the length of literal 0 + MinNumberSize = 1 +) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/metrics/metrics.go b/staging/src/k8s.io/apiserver/pkg/cel/metrics/metrics.go similarity index 100% rename from staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/metrics/metrics.go rename to staging/src/k8s.io/apiserver/pkg/cel/metrics/metrics.go diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/metrics/metrics_test.go b/staging/src/k8s.io/apiserver/pkg/cel/metrics/metrics_test.go similarity index 100% rename from staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/metrics/metrics_test.go rename to staging/src/k8s.io/apiserver/pkg/cel/metrics/metrics_test.go diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/registry.go b/staging/src/k8s.io/apiserver/pkg/cel/registry.go similarity index 92% rename from staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/registry.go rename to staging/src/k8s.io/apiserver/pkg/cel/registry.go index 44f63dc1af5..1aee3a127d6 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/registry.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/registry.go @@ -14,13 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package model +package cel import ( "sync" "github.com/google/cel-go/cel" - "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" ) // Resolver declares methods to find policy templates and related configuration objects. @@ -30,12 +29,11 @@ type Resolver interface { FindType(name string) (*DeclType, bool) } -// NewRegistry create a registry for keeping track of environments, schemas, templates, and more +// NewRegistry create a registry for keeping track of environments and types // from a base cel.Env expression environment. func NewRegistry(stdExprEnv *cel.Env) *Registry { return &Registry{ exprEnvs: map[string]*cel.Env{"": stdExprEnv}, - schemas: map[string]*schema.Structural{}, types: map[string]*DeclType{ BoolType.TypeName(): BoolType, BytesType.TypeName(): BytesType, @@ -58,7 +56,6 @@ func NewRegistry(stdExprEnv *cel.Env) *Registry { type Registry struct { rwMux sync.RWMutex exprEnvs map[string]*cel.Env - schemas map[string]*schema.Structural types map[string]*DeclType } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/types.go b/staging/src/k8s.io/apiserver/pkg/cel/types.go similarity index 91% rename from staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/types.go rename to staging/src/k8s.io/apiserver/pkg/cel/types.go index 08823f94ffb..13171ad2128 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/types.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/types.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package model +package cel import ( "fmt" @@ -79,7 +79,7 @@ func NewObjectType(name string, fields map[string]*DeclField) *DeclType { return t } -func newSimpleTypeWithMinSize(name string, celType *cel.Type, zeroVal ref.Val, minSize int64) *DeclType { +func NewSimpleTypeWithMinSize(name string, celType *cel.Type, zeroVal ref.Val, minSize int64) *DeclType { return &DeclType{ name: name, celType: celType, @@ -284,6 +284,16 @@ type DeclField struct { defaultValue interface{} } +func NewDeclField(name string, declType *DeclType, required bool, enumValues []interface{}, defaultValue interface{}) *DeclField { + return &DeclField{ + Name: name, + Type: declType, + Required: required, + enumValues: enumValues, + defaultValue: defaultValue, + } +} + // TypeName returns the string type name of the field. func (f *DeclField) TypeName() string { return f.Type.TypeName() @@ -495,44 +505,44 @@ type schemaTypeProvider struct { var ( // AnyType is equivalent to the CEL 'protobuf.Any' type in that the value may have any of the // types supported. - AnyType = newSimpleTypeWithMinSize("any", cel.AnyType, nil, 1) + AnyType = NewSimpleTypeWithMinSize("any", cel.AnyType, nil, 1) // BoolType is equivalent to the CEL 'bool' type. - BoolType = newSimpleTypeWithMinSize("bool", cel.BoolType, types.False, minBoolSize) + BoolType = NewSimpleTypeWithMinSize("bool", cel.BoolType, types.False, MinBoolSize) // BytesType is equivalent to the CEL 'bytes' type. - BytesType = newSimpleTypeWithMinSize("bytes", cel.BytesType, types.Bytes([]byte{}), minStringSize) + BytesType = NewSimpleTypeWithMinSize("bytes", cel.BytesType, types.Bytes([]byte{}), MinStringSize) // DoubleType is equivalent to the CEL 'double' type which is a 64-bit floating point value. - DoubleType = newSimpleTypeWithMinSize("double", cel.DoubleType, types.Double(0), minNumberSize) + DoubleType = NewSimpleTypeWithMinSize("double", cel.DoubleType, types.Double(0), MinNumberSize) // DurationType is equivalent to the CEL 'duration' type. - DurationType = newSimpleTypeWithMinSize("duration", cel.DurationType, types.Duration{Duration: time.Duration(0)}, minDurationSizeJSON) + DurationType = NewSimpleTypeWithMinSize("duration", cel.DurationType, types.Duration{Duration: time.Duration(0)}, MinDurationSizeJSON) // DateType is equivalent to the CEL 'date' type. - DateType = newSimpleTypeWithMinSize("date", cel.TimestampType, types.Timestamp{Time: time.Time{}}, dateSizeJSON) + DateType = NewSimpleTypeWithMinSize("date", cel.TimestampType, types.Timestamp{Time: time.Time{}}, JSONDateSize) // DynType is the equivalent of the CEL 'dyn' concept which indicates that the type will be // determined at runtime rather than compile time. - DynType = newSimpleTypeWithMinSize("dyn", cel.DynType, nil, 1) + DynType = NewSimpleTypeWithMinSize("dyn", cel.DynType, nil, 1) // IntType is equivalent to the CEL 'int' type which is a 64-bit signed int. - IntType = newSimpleTypeWithMinSize("int", cel.IntType, types.IntZero, minNumberSize) + IntType = NewSimpleTypeWithMinSize("int", cel.IntType, types.IntZero, MinNumberSize) // NullType is equivalent to the CEL 'null_type'. - NullType = newSimpleTypeWithMinSize("null_type", cel.NullType, types.NullValue, 4) + NullType = NewSimpleTypeWithMinSize("null_type", cel.NullType, types.NullValue, 4) // StringType is equivalent to the CEL 'string' type which is expected to be a UTF-8 string. // StringType values may either be string literals or expression strings. - StringType = newSimpleTypeWithMinSize("string", cel.StringType, types.String(""), minStringSize) + StringType = NewSimpleTypeWithMinSize("string", cel.StringType, types.String(""), MinStringSize) // TimestampType corresponds to the well-known protobuf.Timestamp type supported within CEL. // Note that both the OpenAPI date and date-time types map onto TimestampType, so not all types // labeled as Timestamp will necessarily have the same MinSerializedSize. - TimestampType = newSimpleTypeWithMinSize("timestamp", cel.TimestampType, types.Timestamp{Time: time.Time{}}, dateSizeJSON) + TimestampType = NewSimpleTypeWithMinSize("timestamp", cel.TimestampType, types.Timestamp{Time: time.Time{}}, JSONDateSize) // UintType is equivalent to the CEL 'uint' type. - UintType = newSimpleTypeWithMinSize("uint", cel.UintType, types.Uint(0), 1) + UintType = NewSimpleTypeWithMinSize("uint", cel.UintType, types.Uint(0), 1) // ListType is equivalent to the CEL 'list' type. ListType = NewListType(AnyType, noMaxLength) diff --git a/staging/src/k8s.io/apiserver/pkg/cel/types_test.go b/staging/src/k8s.io/apiserver/pkg/cel/types_test.go new file mode 100644 index 00000000000..fef500b53f6 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/cel/types_test.go @@ -0,0 +1,79 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cel + +import ( + "testing" +) + +func TestTypes_ListType(t *testing.T) { + list := NewListType(StringType, -1) + if !list.IsList() { + t.Error("list type not identifiable as list") + } + if list.TypeName() != "list" { + t.Errorf("got %s, wanted list", list.TypeName()) + } + if list.DefaultValue() == nil { + t.Error("got nil zero value for list type") + } + if list.ElemType.TypeName() != "string" { + t.Errorf("got %s, wanted elem type of string", list.ElemType.TypeName()) + } + expT, err := list.ExprType() + if err != nil { + t.Errorf("fail to get cel type: %s", err) + } + if expT.GetListType() == nil { + t.Errorf("got %v, wanted CEL list type", expT) + } +} + +func TestTypes_MapType(t *testing.T) { + mp := NewMapType(StringType, IntType, -1) + if !mp.IsMap() { + t.Error("map type not identifiable as map") + } + if mp.TypeName() != "map" { + t.Errorf("got %s, wanted map", mp.TypeName()) + } + if mp.DefaultValue() == nil { + t.Error("got nil zero value for map type") + } + if mp.KeyType.TypeName() != "string" { + t.Errorf("got %s, wanted key type of string", mp.KeyType.TypeName()) + } + if mp.ElemType.TypeName() != "int" { + t.Errorf("got %s, wanted elem type of int", mp.ElemType.TypeName()) + } + expT, err := mp.ExprType() + if err != nil { + t.Errorf("fail to get cel type: %s", err) + } + if expT.GetMapType() == nil { + t.Errorf("got %v, wanted CEL map type", expT) + } +} + +func testValue(t *testing.T, id int64, val interface{}) *DynValue { + t.Helper() + dv, err := NewDynValue(id, val) + if err != nil { + t.Fatalf("NewDynValue(%d, %v) failed: %v", id, val, err) + } + return dv +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/url.go b/staging/src/k8s.io/apiserver/pkg/cel/url.go similarity index 99% rename from staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/url.go rename to staging/src/k8s.io/apiserver/pkg/cel/url.go index 03234d7a0ef..6800205c9a9 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/url.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/url.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package model +package cel import ( "fmt" diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/value.go b/staging/src/k8s.io/apiserver/pkg/cel/value.go similarity index 99% rename from staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/value.go rename to staging/src/k8s.io/apiserver/pkg/cel/value.go index 62affeffff3..01c7f20acc9 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/value.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/value.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package model +package cel import ( "fmt" diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/value_test.go b/staging/src/k8s.io/apiserver/pkg/cel/value_test.go similarity index 99% rename from staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/value_test.go rename to staging/src/k8s.io/apiserver/pkg/cel/value_test.go index bf5e0b7b53f..84d83bcee80 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model/value_test.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/value_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package model +package cel import ( "fmt" diff --git a/staging/src/k8s.io/apiserver/pkg/server/config.go b/staging/src/k8s.io/apiserver/pkg/server/config.go index 0248fdd0a95..1d0753ea5df 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/config.go +++ b/staging/src/k8s.io/apiserver/pkg/server/config.go @@ -366,7 +366,7 @@ func NewConfig(codecs serializer.CodecFactory) *Config { // A request body might be encoded in json, and is converted to // proto when persisted in etcd, so we allow 2x as the largest request // body size to be accepted and decoded in a write request. - // If this constant is changed, maxRequestSizeBytes in apiextensions-apiserver/pkg/apiserver/schema/cel/model/schemas.go + // If this constant is changed, DefaultMaxRequestSizeBytes in k8s.io/apiserver/pkg/cel/limits.go // should be changed to reflect the new value, if the two haven't // been wired together already somehow. MaxRequestBodyBytes: int64(3 * 1024 * 1024), diff --git a/vendor/modules.txt b/vendor/modules.txt index 92ea206bc73..7f980ea4d46 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1321,8 +1321,6 @@ k8s.io/apiextensions-apiserver/pkg/apiserver k8s.io/apiextensions-apiserver/pkg/apiserver/conversion k8s.io/apiextensions-apiserver/pkg/apiserver/schema k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel -k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library -k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/metrics k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype @@ -1492,6 +1490,9 @@ k8s.io/apiserver/pkg/authorization/authorizer k8s.io/apiserver/pkg/authorization/authorizerfactory k8s.io/apiserver/pkg/authorization/path k8s.io/apiserver/pkg/authorization/union +k8s.io/apiserver/pkg/cel +k8s.io/apiserver/pkg/cel/library +k8s.io/apiserver/pkg/cel/metrics k8s.io/apiserver/pkg/endpoints k8s.io/apiserver/pkg/endpoints/deprecation k8s.io/apiserver/pkg/endpoints/discovery