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