Merge pull request #112926 from jiahuif-forks/refactor/cel-out-of-apiextensions

split and move CEL package
This commit is contained in:
Kubernetes Prow Robot 2022-10-12 15:03:03 -07:00 committed by GitHub
commit 61ca612cbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 268 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,16 @@ 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 // TODO(DangerOnTheRanger): wire in MaxRequestBodyBytes from apiserver/pkg/server/options/server_run_options.go to make this configurable
maxRequestSizeBytes = int64(3 * 1024 * 1024) const maxRequestSizeBytes = apiservercel.DefaultMaxRequestSizeBytes
// 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 +39,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,7 +56,7 @@ 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 = maxRequestSizeBytes - 2
return dyn return dyn
@ -106,7 +85,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 +98,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 +120,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 +132,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 +147,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 +172,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
} }
@ -268,18 +241,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 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 maxRequestSizeBytes - 2
} }
} }

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.DefaultMaxRequestSizeBytes - 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

@ -12,6 +12,7 @@ require (
github.com/evanphx/json-patch v4.12.0+incompatible github.com/evanphx/json-patch v4.12.0+incompatible
github.com/fsnotify/fsnotify v1.5.4 github.com/fsnotify/fsnotify v1.5.4
github.com/gogo/protobuf v1.3.2 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/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
@ -36,7 +37,9 @@ require (
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 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/grpc v1.49.0
google.golang.org/protobuf v1.28.1
gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/square/go-jose.v2 v2.2.2 gopkg.in/square/go-jose.v2 v2.2.2
k8s.io/api v0.0.0 k8s.io/api v0.0.0
@ -56,6 +59,7 @@ require (
require ( require (
cloud.google.com/go v0.97.0 // indirect cloud.google.com/go v0.97.0 // indirect
github.com/NYTimes/gziphandler v1.1.1 // 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/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // 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/sirupsen/logrus v1.8.1 // indirect
github.com/soheilhy/cmux v0.1.5 // indirect github.com/soheilhy/cmux v0.1.5 // indirect
github.com/spf13/cobra v1.5.0 // 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/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
go.etcd.io/bbolt v1.3.6 // 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/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.8 // indirect golang.org/x/text v0.3.8 // indirect
google.golang.org/appengine v1.6.7 // 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/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

View File

@ -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-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 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/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/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-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= 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.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= 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/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 h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 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 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/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/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.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

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,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
)

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"

View File

@ -366,7 +366,7 @@ func NewConfig(codecs serializer.CodecFactory) *Config {
// A request body might be encoded in json, and is converted to // 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 // proto when persisted in etcd, so we allow 2x as the largest request
// body size to be accepted and decoded in a write 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 // should be changed to reflect the new value, if the two haven't
// been wired together already somehow. // been wired together already somehow.
MaxRequestBodyBytes: int64(3 * 1024 * 1024), MaxRequestBodyBytes: int64(3 * 1024 * 1024),

5
vendor/modules.txt vendored
View File

@ -1321,8 +1321,6 @@ k8s.io/apiextensions-apiserver/pkg/apiserver
k8s.io/apiextensions-apiserver/pkg/apiserver/conversion k8s.io/apiextensions-apiserver/pkg/apiserver/conversion
k8s.io/apiextensions-apiserver/pkg/apiserver/schema k8s.io/apiextensions-apiserver/pkg/apiserver/schema
k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel 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/cel/model
k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting
k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype 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/authorizerfactory
k8s.io/apiserver/pkg/authorization/path k8s.io/apiserver/pkg/authorization/path
k8s.io/apiserver/pkg/authorization/union 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
k8s.io/apiserver/pkg/endpoints/deprecation k8s.io/apiserver/pkg/endpoints/deprecation
k8s.io/apiserver/pkg/endpoints/discovery k8s.io/apiserver/pkg/endpoints/discovery