move CEL package to apiserver package.

only anything that does not require Structural
This commit is contained in:
Jiahui Feng 2022-10-07 15:02:47 -07:00
parent 575031b68f
commit 0dd316a5c1
30 changed files with 258 additions and 193 deletions

View File

@ -25,6 +25,7 @@ import (
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/cel"
) )
// unbounded uses nil to represent an unbounded cardinality value. // 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 is a structural schema for this CELSchemaContext node. It must be non-nil.
Schema *structuralschema.Structural Schema *structuralschema.Structural
// DeclType is a CEL declaration representation of Schema of this CELSchemaContext node. It must be non-nil. // 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 // 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 { func (c propertyTypeInfoAccessor) accessTypeInfo(parentTypeInfo *CELTypeInfo) *CELTypeInfo {
if parentTypeInfo.Schema.Properties != nil { if parentTypeInfo.Schema.Properties != nil {
propSchema := parentTypeInfo.Schema.Properties[c.propertyName] 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 { if fieldDeclType, ok := parentTypeInfo.DeclType.Fields[escapedPropName]; ok {
return &CELTypeInfo{Schema: &propSchema, DeclType: fieldDeclType.Type} return &CELTypeInfo{Schema: &propSchema, DeclType: fieldDeclType.Type}
} // else fields with unknown types are omitted from CEL validation entirely } // else fields with unknown types are omitted from CEL validation entirely

View File

@ -34,6 +34,7 @@ import (
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
utilvalidation "k8s.io/apimachinery/pkg/util/validation" utilvalidation "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
apiservercel "k8s.io/apiserver/pkg/cel"
"k8s.io/apiserver/pkg/util/webhook" "k8s.io/apiserver/pkg/util/webhook"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "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) celContext.TotalCost.ObserveExpressionCost(fldPath.Child("x-kubernetes-validations").Index(i).Child("rule"), expressionCost)
} }
if cr.Error != nil { 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)) allErrs.CELErrors = append(allErrs.CELErrors, field.Required(fldPath.Child("x-kubernetes-validations").Index(i).Child("rule"), cr.Error.Detail))
} else { } else {
allErrs.CELErrors = append(allErrs.CELErrors, field.Invalid(fldPath.Child("x-kubernetes-validations").Index(i).Child("rule"), schema.XValidations[i], cr.Error.Detail)) allErrs.CELErrors = append(allErrs.CELErrors, field.Invalid(fldPath.Child("x-kubernetes-validations").Index(i).Child("rule"), schema.XValidations[i], cr.Error.Detail))

View File

@ -24,11 +24,13 @@ import (
"github.com/google/cel-go/cel" "github.com/google/cel-go/cel"
"github.com/google/cel-go/checker" "github.com/google/cel-go/checker"
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema" "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" 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 ( const (
@ -56,7 +58,7 @@ const (
// CompilationResult represents the cel compilation result for one rule // CompilationResult represents the cel compilation result for one rule
type CompilationResult struct { type CompilationResult struct {
Program cel.Program Program cel.Program
Error *Error Error *apiservercel.Error
// If true, the compiled expression contains a reference to the identifier "oldSelf", and its corresponding rule // If true, the compiled expression contains a reference to the identifier "oldSelf", and its corresponding rule
// is implicitly a transition rule. // is implicitly a transition rule.
TransitionRule bool 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 // - 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. // 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() t := time.Now()
defer metrics.Metrics.ObserveCompilation(time.Since(t)) defer metrics.Metrics.ObserveCompilation(time.Since(t))
if len(s.Extensions.XValidations) == 0 { if len(s.Extensions.XValidations) == 0 {
@ -106,15 +108,15 @@ func Compile(s *schema.Structural, declType *celmodel.DeclType, perCallLimit uin
celRules := s.Extensions.XValidations celRules := s.Extensions.XValidations
var propDecls []cel.EnvOption var propDecls []cel.EnvOption
var root *celmodel.DeclType var root *apiservercel.DeclType
var ok bool var ok bool
baseEnv, err := getBaseEnv() baseEnv, err := getBaseEnv()
if err != nil { if err != nil {
return nil, err return nil, err
} }
reg := celmodel.NewRegistry(baseEnv) reg := apiservercel.NewRegistry(baseEnv)
scopedTypeName := generateUniqueSelfTypeName() scopedTypeName := generateUniqueSelfTypeName()
rt, err := celmodel.NewRuleTypes(scopedTypeName, declType, reg) rt, err := apiservercel.NewRuleTypes(scopedTypeName, declType, reg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -158,18 +160,18 @@ func compileRule(rule apiextensions.ValidationRule, env *cel.Env, perCallLimit u
} }
ast, issues := env.Compile(rule.Rule) ast, issues := env.Compile(rule.Rule)
if issues != nil { if issues != nil {
compilationResult.Error = &Error{ErrorTypeInvalid, "compilation failed: " + issues.String()} compilationResult.Error = &apiservercel.Error{apiservercel.ErrorTypeInvalid, "compilation failed: " + issues.String()}
return return
} }
if ast.OutputType() != cel.BoolType { 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 return
} }
checkedExpr, err := cel.AstToCheckedExpr(ast) checkedExpr, err := cel.AstToCheckedExpr(ast)
if err != nil { if err != nil {
// should be impossible since env.Compile returned no issues // 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 return
} }
for _, ref := range checkedExpr.ReferenceMap { for _, ref := range checkedExpr.ReferenceMap {
@ -188,12 +190,12 @@ func compileRule(rule apiextensions.ValidationRule, env *cel.Env, perCallLimit u
cel.InterruptCheckFrequency(checkFrequency), cel.InterruptCheckFrequency(checkFrequency),
) )
if err != nil { if err != nil {
compilationResult.Error = &Error{ErrorTypeInvalid, "program instantiation failed: " + err.Error()} compilationResult.Error = &apiservercel.Error{apiservercel.ErrorTypeInvalid, "program instantiation failed: " + err.Error()}
return return
} }
costEst, err := env.EstimateCost(ast, estimator) costEst, err := env.EstimateCost(ast, estimator)
if err != nil { if err != nil {
compilationResult.Error = &Error{ErrorTypeInternal, "cost estimation failed: " + err.Error()} compilationResult.Error = &apiservercel.Error{apiservercel.ErrorTypeInternal, "cost estimation failed: " + err.Error()}
return return
} }
compilationResult.MaxCost = costEst.Max compilationResult.MaxCost = costEst.Max
@ -210,12 +212,12 @@ func generateUniqueSelfTypeName() string {
return fmt.Sprintf("selfType%d", time.Now().Nanosecond()) 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}} return &library.CostEstimator{SizeEstimator: &sizeEstimator{root: root}}
} }
type sizeEstimator struct { type sizeEstimator struct {
root *celmodel.DeclType root *apiservercel.DeclType
} }
func (c *sizeEstimator) EstimateSize(element checker.AstNode) *checker.SizeEstimate { func (c *sizeEstimator) EstimateSize(element checker.AstNode) *checker.SizeEstimate {

View File

@ -22,6 +22,8 @@ import (
"strings" "strings"
"testing" "testing"
"k8s.io/apiserver/pkg/cel"
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model"
@ -79,12 +81,12 @@ func (m fnMatcher) String() string {
} }
type errorMatcher struct { type errorMatcher struct {
errorType ErrorType errorType cel.ErrorType
contains string contains string
} }
func invalidError(contains string) validationMatcher { 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 { func (v errorMatcher) matches(cr CompilationResult) bool {

View File

@ -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

View File

@ -22,37 +22,13 @@ import (
"github.com/google/cel-go/cel" "github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types"
apiservercel "k8s.io/apiserver/pkg/cel"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema" "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
)
// SchemaDeclType converts the structural schema to a CEL declaration, or returns nil if the // 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. // 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 // Schemas with XPreserveUnknownFields not exposed unless they are objects. Array and "maps" schemas
@ -60,7 +36,7 @@ const (
// if their schema is not exposed. // if their schema is not exposed.
// //
// The CEL declaration for objects with XPreserveUnknownFields does not expose unknown fields. // 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 { if s == nil {
return nil return nil
} }
@ -77,9 +53,9 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType {
// To validate requirements on both the int and string representation: // To validate requirements on both the int and string representation:
// `type(intOrStringField) == int ? intOrStringField < 5 : double(intOrStringField.replace('%', '')) < 0.5 // `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 // handle x-kubernetes-int-or-string by returning the max length/min serialized size of the largest possible string
dyn.MaxElements = maxRequestSizeBytes - 2 dyn.MaxElements = apiservercel.MaxRequestSizeBytes - 2
return dyn return dyn
} }
@ -106,7 +82,7 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType {
} else { } else {
maxItems = estimateMaxArrayItemsFromMinSize(itemsType.MinSerializedSize) maxItems = estimateMaxArrayItemsFromMinSize(itemsType.MinSerializedSize)
} }
return NewListType(itemsType, maxItems) return apiservercel.NewListType(itemsType, maxItems)
} }
return nil return nil
case "object": case "object":
@ -119,11 +95,11 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType {
} else { } else {
maxProperties = estimateMaxAdditionalPropertiesFromMinSize(propsType.MinSerializedSize) maxProperties = estimateMaxAdditionalPropertiesFromMinSize(propsType.MinSerializedSize)
} }
return NewMapType(StringType, propsType, maxProperties) return apiservercel.NewMapType(apiservercel.StringType, propsType, maxProperties)
} }
return nil return nil
} }
fields := make(map[string]*DeclField, len(s.Properties)) fields := make(map[string]*apiservercel.DeclField, len(s.Properties))
required := map[string]bool{} required := map[string]bool{}
if s.ValueValidation != nil { if s.ValueValidation != nil {
@ -141,14 +117,8 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType {
} }
} }
if fieldType := SchemaDeclType(&prop, prop.XEmbeddedResource); fieldType != nil { if fieldType := SchemaDeclType(&prop, prop.XEmbeddedResource); fieldType != nil {
if propName, ok := Escape(name); ok { if propName, ok := apiservercel.Escape(name); ok {
fields[propName] = &DeclField{ fields[propName] = apiservercel.NewDeclField(propName, fieldType, required[name], enumValues, prop.Default.Object)
Name: propName,
Required: required[name],
Type: fieldType,
defaultValue: prop.Default.Object,
enumValues: enumValues, // Enum values are represented as strings in CEL
}
} }
// the min serialized size for an object is 2 (for {}) plus the min size of all its required // the min serialized size for an object is 2 (for {}) plus the min size of all its required
// properties // properties
@ -159,14 +129,14 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType {
} }
} }
} }
objType := NewObjectType("object", fields) objType := apiservercel.NewObjectType("object", fields)
objType.MinSerializedSize = minSerializedSize objType.MinSerializedSize = minSerializedSize
return objType return objType
case "string": case "string":
if s.ValueValidation != nil { if s.ValueValidation != nil {
switch s.ValueValidation.Format { switch s.ValueValidation.Format {
case "byte": 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 { if s.ValueValidation.MaxLength != nil {
byteWithMaxLength.MaxElements = zeroIfNegative(*s.ValueValidation.MaxLength) byteWithMaxLength.MaxElements = zeroIfNegative(*s.ValueValidation.MaxLength)
} else { } else {
@ -174,20 +144,20 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType {
} }
return byteWithMaxLength return byteWithMaxLength
case "duration": 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) durationWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s)
return durationWithMaxLength return durationWithMaxLength
case "date": 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) timestampWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s)
return timestampWithMaxLength return timestampWithMaxLength
case "date-time": 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) timestampWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s)
return timestampWithMaxLength 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 { if s.ValueValidation != nil && s.ValueValidation.MaxLength != nil {
// multiply the user-provided max length by 4 in the case of an otherwise-untyped string // 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, // we do this because the OpenAPIv3 spec indicates that maxLength is specified in runes/code points,
@ -199,11 +169,11 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType {
} }
return strWithMaxLength return strWithMaxLength
case "boolean": case "boolean":
return BoolType return apiservercel.BoolType
case "number": case "number":
return DoubleType return apiservercel.DoubleType
case "integer": case "integer":
return IntType return apiservercel.IntType
} }
return nil return nil
} }
@ -259,7 +229,7 @@ func WithTypeAndObjectMeta(s *schema.Structural) *schema.Structural {
// this function. // this function.
func MaxCardinality(minSize int64) uint64 { func MaxCardinality(minSize int64) uint64 {
sz := minSize + 1 // assume at least one comma between elements sz := minSize + 1 // assume at least one comma between elements
return uint64(maxRequestSizeBytes / sz) return uint64(apiservercel.MaxRequestSizeBytes / sz)
} }
// estimateMaxStringLengthPerRequest estimates the maximum string length (in characters) // estimateMaxStringLengthPerRequest estimates the maximum string length (in characters)
@ -268,18 +238,18 @@ func MaxCardinality(minSize int64) uint64 {
func estimateMaxStringLengthPerRequest(s *schema.Structural) int64 { func estimateMaxStringLengthPerRequest(s *schema.Structural) int64 {
if s.ValueValidation == nil || s.XIntOrString { if s.ValueValidation == nil || s.XIntOrString {
// subtract 2 to account for "" // subtract 2 to account for ""
return (maxRequestSizeBytes - 2) return apiservercel.MaxRequestSizeBytes - 2
} }
switch s.ValueValidation.Format { switch s.ValueValidation.Format {
case "duration": case "duration":
return maxDurationSizeJSON return apiservercel.MaxDurationSizeJSON
case "date": case "date":
return dateSizeJSON return apiservercel.JSONDateSize
case "date-time": case "date-time":
return maxDatetimeSizeJSON return apiservercel.MaxDatetimeSizeJSON
default: default:
// subtract 2 to account for "" // subtract 2 to account for ""
return (maxRequestSizeBytes - 2) return apiservercel.MaxRequestSizeBytes - 2
} }
} }
@ -287,7 +257,7 @@ func estimateMaxStringLengthPerRequest(s *schema.Structural) int64 {
// the provided minimum serialized size that can fit into a single request. // the provided minimum serialized size that can fit into a single request.
func estimateMaxArrayItemsFromMinSize(minSize int64) int64 { func estimateMaxArrayItemsFromMinSize(minSize int64) int64 {
// subtract 2 to account for [ and ] // subtract 2 to account for [ and ]
return (maxRequestSizeBytes - 2) / (minSize + 1) return (apiservercel.MaxRequestSizeBytes - 2) / (minSize + 1)
} }
// estimateMaxAdditionalPropertiesPerRequest estimates the maximum number of additional properties // estimateMaxAdditionalPropertiesPerRequest estimates the maximum number of additional properties
@ -297,5 +267,5 @@ func estimateMaxAdditionalPropertiesFromMinSize(minSize int64) int64 {
// will all vary in length // will all vary in length
keyValuePairSize := minSize + 6 keyValuePairSize := minSize + 6
// subtract 2 to account for { and } // subtract 2 to account for { and }
return (maxRequestSizeBytes - 2) / keyValuePairSize return (apiservercel.MaxRequestSizeBytes - 2) / keyValuePairSize
} }

View File

@ -25,6 +25,7 @@ import (
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
apiservercel "k8s.io/apiserver/pkg/cel"
) )
func TestSchemaDeclType(t *testing.T) { func TestSchemaDeclType(t *testing.T) {
@ -86,15 +87,15 @@ func TestSchemaDeclType(t *testing.T) {
func TestSchemaDeclTypes(t *testing.T) { func TestSchemaDeclTypes(t *testing.T) {
ts := testSchema() ts := testSchema()
cust := SchemaDeclType(ts, true).MaybeAssignTypeName("CustomObject") cust := SchemaDeclType(ts, true).MaybeAssignTypeName("CustomObject")
typeMap := FieldTypeMap("CustomObject", cust) typeMap := apiservercel.FieldTypeMap("CustomObject", cust)
nested, _ := cust.FindField("nested") nested, _ := cust.FindField("nested")
metadata, _ := cust.FindField("metadata") metadata, _ := cust.FindField("metadata")
expectedObjTypeMap := map[string]*DeclType{ expectedObjTypeMap := map[string]*apiservercel.DeclType{
"CustomObject": cust, "CustomObject": cust,
"CustomObject.nested": nested.Type, "CustomObject.nested": nested.Type,
"CustomObject.metadata": metadata.Type, "CustomObject.metadata": metadata.Type,
} }
objTypeMap := map[string]*DeclType{} objTypeMap := map[string]*apiservercel.DeclType{}
for name, t := range typeMap { for name, t := range typeMap {
if t.IsObject() { if t.IsObject() {
objTypeMap[name] = t objTypeMap[name] = t
@ -406,7 +407,7 @@ func TestEstimateMaxLengthJSON(t *testing.T) {
}, },
}, },
// should be exactly equal to maxDurationSizeJSON // should be exactly equal to maxDurationSizeJSON
ExpectedMaxElements: maxDurationSizeJSON, ExpectedMaxElements: apiservercel.MaxDurationSizeJSON,
}, },
{ {
Name: "dateSize", Name: "dateSize",
@ -419,7 +420,7 @@ func TestEstimateMaxLengthJSON(t *testing.T) {
}, },
}, },
// should be exactly equal to dateSizeJSON // should be exactly equal to dateSizeJSON
ExpectedMaxElements: dateSizeJSON, ExpectedMaxElements: apiservercel.JSONDateSize,
}, },
{ {
Name: "maxdatetimeSize", Name: "maxdatetimeSize",
@ -432,7 +433,7 @@ func TestEstimateMaxLengthJSON(t *testing.T) {
}, },
}, },
// should be exactly equal to maxDatetimeSizeJSON // should be exactly equal to maxDatetimeSizeJSON
ExpectedMaxElements: maxDatetimeSizeJSON, ExpectedMaxElements: apiservercel.MaxDatetimeSizeJSON,
}, },
{ {
Name: "maxintOrStringSize", 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) // should be exactly equal to maxRequestSizeBytes - 2 (to allow for quotes in the case of a string)
ExpectedMaxElements: maxRequestSizeBytes - 2, ExpectedMaxElements: apiservercel.MaxRequestSizeBytes - 2,
}, },
{ {
Name: "objectDefaultFieldArray", 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, // 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, ExpectedMaxElements: 20,
}, },
} }

View File

@ -23,61 +23,14 @@ import (
"github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" 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) { func TestTypes_RuleTypesFieldMapping(t *testing.T) {
stdEnv, _ := cel.NewEnv() stdEnv, _ := cel.NewEnv()
reg := NewRegistry(stdEnv) reg := apiservercel.NewRegistry(stdEnv)
rt, err := NewRuleTypes("CustomObject", SchemaDeclType(testSchema(), true), reg) rt, err := apiservercel.NewRuleTypes("CustomObject", SchemaDeclType(testSchema(), true), reg)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -112,22 +65,22 @@ func TestTypes_RuleTypesFieldMapping(t *testing.T) {
} }
// Manually constructed instance of the schema. // Manually constructed instance of the schema.
name := NewField(1, "name") name := apiservercel.NewField(1, "name")
name.Ref = testValue(t, 2, "test-instance") name.Ref = testValue(t, 2, "test-instance")
nestedVal := NewMapValue() nestedVal := apiservercel.NewMapValue()
flags := NewField(5, "flags") flags := apiservercel.NewField(5, "flags")
flagsVal := NewMapValue() flagsVal := apiservercel.NewMapValue()
myFlag := NewField(6, "my_flag") myFlag := apiservercel.NewField(6, "my_flag")
myFlag.Ref = testValue(t, 7, true) myFlag.Ref = testValue(t, 7, true)
flagsVal.AddField(myFlag) flagsVal.AddField(myFlag)
flags.Ref = testValue(t, 8, flagsVal) flags.Ref = testValue(t, 8, flagsVal)
dates := NewField(9, "dates") dates := apiservercel.NewField(9, "dates")
dates.Ref = testValue(t, 10, NewListValue()) dates.Ref = testValue(t, 10, apiservercel.NewListValue())
nestedVal.AddField(flags) nestedVal.AddField(flags)
nestedVal.AddField(dates) nestedVal.AddField(dates)
nested := NewField(3, "nested") nested := apiservercel.NewField(3, "nested")
nested.Ref = testValue(t, 4, nestedVal) nested.Ref = testValue(t, 4, nestedVal)
mapVal := NewMapValue() mapVal := apiservercel.NewMapValue()
mapVal.AddField(name) mapVal.AddField(name)
mapVal.AddField(nested) mapVal.AddField(nested)
//rule := rt.ConvertToRule(testValue(t, 11, mapVal)) //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() t.Helper()
dv, err := NewDynValue(id, val) dv, err := apiservercel.NewDynValue(id, val)
if err != nil { 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 return dv
} }

View File

@ -30,9 +30,10 @@ import (
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema" "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/apiextensions-apiserver/pkg/apiserver/schema/cel/model"
"k8s.io/apimachinery/pkg/util/validation/field" "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 // 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 // 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 // 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. // 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) compiledRules, err := Compile(s, declType, perCallLimit)
var itemsValidator, additionalPropertiesValidator *Validator var itemsValidator, additionalPropertiesValidator *Validator
var propertiesValidators map[string]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)) propertiesValidators = make(map[string]Validator, len(s.Properties))
for k, p := range s.Properties { for k, p := range s.Properties {
prop := p prop := p
var fieldType *model.DeclType var fieldType *cel.DeclType
if escapedPropName, ok := model.Escape(k); ok { if escapedPropName, ok := cel.Escape(k); ok {
if f, ok := declType.Fields[escapedPropName]; ok { if f, ok := declType.Fields[escapedPropName]; ok {
fieldType = f.Type fieldType = f.Type
} else { } else {

View File

@ -25,10 +25,11 @@ import (
"github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits" "github.com/google/cel-go/common/types/traits"
"k8s.io/apimachinery/pkg/api/equality"
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model" "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" "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 { func escapeKeyProps(idents []string) []string {
result := make([]string, len(idents)) result := make([]string, len(idents))
for i, prop := range idents { for i, prop := range idents {
if escaped, ok := model.Escape(prop); ok { if escaped, ok := cel.Escape(prop); ok {
result[i] = escaped result[i] = escaped
} else { } else {
result[i] = prop result[i] = prop
@ -644,7 +645,7 @@ func (t *unstructuredMap) Iterator() traits.Iterator {
if _, ok := t.propSchema(k); ok { if _, ok := t.propSchema(k); ok {
mapKey := k mapKey := k
if isObject { if isObject {
if escaped, ok := model.Escape(k); ok { if escaped, ok := cel.Escape(k); ok {
mapKey = escaped mapKey = escaped
} }
} }
@ -683,7 +684,7 @@ func (t *unstructuredMap) Find(key ref.Val) (ref.Val, bool) {
} }
k := keyStr.Value().(string) k := keyStr.Value().(string)
if isObject { if isObject {
k, ok = model.Unescape(k) k, ok = cel.Unescape(k)
if !ok { if !ok {
return nil, false return nil, false
} }

View File

@ -15,6 +15,7 @@ require (
github.com/google/gnostic v0.5.7-v3refs github.com/google/gnostic v0.5.7-v3refs
github.com/google/go-cmp v0.5.9 github.com/google/go-cmp v0.5.9
github.com/google/gofuzz v1.1.0 github.com/google/gofuzz v1.1.0
github.com/google/cel-go v0.12.5
github.com/google/uuid v1.1.2 github.com/google/uuid v1.1.2
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package model package cel
import ( import (
"regexp" "regexp"

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package model package cel
import ( import (
"fmt" "fmt"

View File

@ -22,7 +22,8 @@ import (
"github.com/google/cel-go/cel" "github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref" "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. // URLs provides a CEL function library extension of URL parsing functions.
@ -113,25 +114,25 @@ type urls struct{}
var urlLibraryDecls = map[string][]cel.FunctionOpt{ var urlLibraryDecls = map[string][]cel.FunctionOpt{
"url": { "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))}, cel.UnaryBinding(stringToUrl))},
"getScheme": { "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))}, cel.UnaryBinding(getScheme))},
"getHost": { "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))}, cel.UnaryBinding(getHost))},
"getHostname": { "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))}, cel.UnaryBinding(getHostname))},
"getPort": { "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))}, cel.UnaryBinding(getPort))},
"getEscapedPath": { "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))}, cel.UnaryBinding(getEscapedPath))},
"getQuery": { "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.MapType(cel.StringType, cel.ListType(cel.StringType)),
cel.UnaryBinding(getQuery))}, cel.UnaryBinding(getQuery))},
"isURL": { "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. // 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 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 { func getScheme(arg ref.Val) ref.Val {

View File

@ -0,0 +1,49 @@
/*
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 (
// MaxRequestSizeBytes is 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)
// 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
)

View File

@ -14,13 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package model package cel
import ( import (
"sync" "sync"
"github.com/google/cel-go/cel" "github.com/google/cel-go/cel"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
) )
// Resolver declares methods to find policy templates and related configuration objects. // Resolver declares methods to find policy templates and related configuration objects.
@ -30,12 +29,11 @@ type Resolver interface {
FindType(name string) (*DeclType, bool) 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. // from a base cel.Env expression environment.
func NewRegistry(stdExprEnv *cel.Env) *Registry { func NewRegistry(stdExprEnv *cel.Env) *Registry {
return &Registry{ return &Registry{
exprEnvs: map[string]*cel.Env{"": stdExprEnv}, exprEnvs: map[string]*cel.Env{"": stdExprEnv},
schemas: map[string]*schema.Structural{},
types: map[string]*DeclType{ types: map[string]*DeclType{
BoolType.TypeName(): BoolType, BoolType.TypeName(): BoolType,
BytesType.TypeName(): BytesType, BytesType.TypeName(): BytesType,
@ -58,7 +56,6 @@ func NewRegistry(stdExprEnv *cel.Env) *Registry {
type Registry struct { type Registry struct {
rwMux sync.RWMutex rwMux sync.RWMutex
exprEnvs map[string]*cel.Env exprEnvs map[string]*cel.Env
schemas map[string]*schema.Structural
types map[string]*DeclType types map[string]*DeclType
} }

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package model package cel
import ( import (
"fmt" "fmt"
@ -79,7 +79,7 @@ func NewObjectType(name string, fields map[string]*DeclField) *DeclType {
return t 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{ return &DeclType{
name: name, name: name,
celType: celType, celType: celType,
@ -284,6 +284,16 @@ type DeclField struct {
defaultValue interface{} 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. // TypeName returns the string type name of the field.
func (f *DeclField) TypeName() string { func (f *DeclField) TypeName() string {
return f.Type.TypeName() return f.Type.TypeName()
@ -495,44 +505,44 @@ type schemaTypeProvider struct {
var ( var (
// AnyType is equivalent to the CEL 'protobuf.Any' type in that the value may have any of the // AnyType is equivalent to the CEL 'protobuf.Any' type in that the value may have any of the
// types supported. // 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 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 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 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 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 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 // DynType is the equivalent of the CEL 'dyn' concept which indicates that the type will be
// determined at runtime rather than compile time. // 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 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 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 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 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. // 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 // 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. // 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 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 is equivalent to the CEL 'list' type.
ListType = NewListType(AnyType, noMaxLength) ListType = NewListType(AnyType, noMaxLength)

View File

@ -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
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package model package cel
import ( import (
"fmt" "fmt"

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package model package cel
import ( import (
"fmt" "fmt"

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package model package cel
import ( import (
"fmt" "fmt"