feat: kubesphere 4.0 (#6115)
* feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> * feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> --------- Signed-off-by: ci-bot <ci-bot@kubesphere.io> Co-authored-by: ks-ci-bot <ks-ci-bot@example.com> Co-authored-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
committed by
GitHub
parent
b5015ec7b9
commit
447a51f08b
336
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/cel_validation.go
generated
vendored
Normal file
336
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/cel_validation.go
generated
vendored
Normal file
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
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 validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
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.
|
||||
var unbounded *uint64 = nil
|
||||
|
||||
// CELSchemaContext keeps track of data used by x-kubernetes-validations rules for a specific schema node.
|
||||
type CELSchemaContext struct {
|
||||
// withinValidationRuleScope is true if the schema at the current level or above have x-kubernetes-validations rules. typeInfo
|
||||
// should only be populated for schema nodes where this is true.
|
||||
withinValidationRuleScope bool
|
||||
|
||||
// typeInfo is lazily loaded for schema nodes withinValidationRuleScope and may be
|
||||
// populated one of two possible ways:
|
||||
// 1. Using a typeInfoAccessor to access it from the parent's type info. This is a cheap operation and should be
|
||||
// used when a schema at a higher level already has type info.
|
||||
// 2. Using a converter to construct type info from the jsonSchema. This is an expensive operation.
|
||||
typeInfo *CELTypeInfo
|
||||
// typeInfoErr is any cached error resulting from an attempt to lazily load typeInfo.
|
||||
typeInfoErr error
|
||||
|
||||
// parent is the context of the parent schema node, or nil if this is the context for the root schema node.
|
||||
parent *CELSchemaContext
|
||||
// typeInfoAccessor provides a way to access the type info of this schema node from the parent CELSchemaContext.
|
||||
// nil if not extraction is possible, or the parent is nil.
|
||||
typeInfoAccessor typeInfoAccessor
|
||||
|
||||
// jsonSchema is the schema for this CELSchemaContext node. It must be non-nil.
|
||||
jsonSchema *apiextensions.JSONSchemaProps
|
||||
// converter converts a JSONSchemaProps to CELTypeInfo.
|
||||
// Tests that check how many conversions are performed during CRD validation wrap DefaultConverter
|
||||
// with a converter that counts how many conversion operations.
|
||||
converter converter
|
||||
|
||||
// MaxCardinality represents a limit to the number of data elements that can exist for the current
|
||||
// schema based on MaxProperties or MaxItems limits present on parent schemas, If all parent
|
||||
// map and array schemas have MaxProperties or MaxItems limits declared MaxCardinality is
|
||||
// an int pointer representing the product of these limits. If least one parent map or list schema
|
||||
// does not have a MaxProperties or MaxItems limits set, the MaxCardinality is nil, indicating
|
||||
// that the parent schemas offer no bound to the number of times a data element for the current
|
||||
// schema can exist.
|
||||
MaxCardinality *uint64
|
||||
// TotalCost accumulates the x-kubernetes-validators estimated rule cost total for an entire custom resource
|
||||
// definition. A single TotalCost is allocated for each CustomResourceDefinition and passed through the stack as the
|
||||
// CustomResourceDefinition's OpenAPIv3 schema is recursively validated.
|
||||
TotalCost *TotalCost
|
||||
}
|
||||
|
||||
// TypeInfo returns the CELTypeInfo for this CELSchemaContext node. Returns nil, nil if this CELSchemaContext is nil,
|
||||
// or if current level or above does not have x-kubernetes-validations rules. The returned type info is shared and
|
||||
// should not be modified by the caller.
|
||||
func (c *CELSchemaContext) TypeInfo() (*CELTypeInfo, error) {
|
||||
if c == nil || !c.withinValidationRuleScope {
|
||||
return nil, nil
|
||||
}
|
||||
if c.typeInfo != nil || c.typeInfoErr != nil {
|
||||
return c.typeInfo, c.typeInfoErr // return already computed result if available
|
||||
}
|
||||
|
||||
// If able to get the type info from the parent's type info, prefer this approach
|
||||
// since it is more efficient.
|
||||
if c.parent != nil {
|
||||
parentTypeInfo, parentErr := c.parent.TypeInfo()
|
||||
if parentErr != nil {
|
||||
c.typeInfoErr = parentErr
|
||||
return nil, parentErr
|
||||
}
|
||||
if parentTypeInfo != nil && c.typeInfoAccessor != nil {
|
||||
c.typeInfo = c.typeInfoAccessor.accessTypeInfo(parentTypeInfo)
|
||||
if c.typeInfo != nil {
|
||||
return c.typeInfo, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
// If unable to get the type info from the parent, convert the jsonSchema to type info.
|
||||
// This is expensive for large schemas.
|
||||
c.typeInfo, c.typeInfoErr = c.converter(c.jsonSchema, c.parent == nil || c.jsonSchema.XEmbeddedResource)
|
||||
return c.typeInfo, c.typeInfoErr
|
||||
}
|
||||
|
||||
// CELTypeInfo represents all the typeInfo needed by CEL to compile x-kubernetes-validations rules for a schema node.
|
||||
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 *cel.DeclType
|
||||
}
|
||||
|
||||
// converter converts from JSON schema to a structural schema and a CEL declType, or returns an error if the conversion
|
||||
// fails. This should be defaultConverter except in tests where it is useful to wrap it with a converter that tracks
|
||||
// how many conversions have been performed.
|
||||
type converter func(schema *apiextensions.JSONSchemaProps, isRoot bool) (*CELTypeInfo, error)
|
||||
|
||||
func defaultConverter(schema *apiextensions.JSONSchemaProps, isRoot bool) (*CELTypeInfo, error) {
|
||||
structural, err := structuralschema.NewStructural(schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
declType := model.SchemaDeclType(structural, isRoot)
|
||||
if declType == nil {
|
||||
return nil, fmt.Errorf("unable to convert structural schema to CEL declarations")
|
||||
}
|
||||
return &CELTypeInfo{structural, declType}, nil
|
||||
}
|
||||
|
||||
// RootCELContext constructs CELSchemaContext for the given root schema.
|
||||
func RootCELContext(schema *apiextensions.JSONSchemaProps) *CELSchemaContext {
|
||||
rootCardinality := uint64(1)
|
||||
r := &CELSchemaContext{
|
||||
jsonSchema: schema,
|
||||
withinValidationRuleScope: len(schema.XValidations) > 0,
|
||||
MaxCardinality: &rootCardinality,
|
||||
TotalCost: &TotalCost{},
|
||||
converter: defaultConverter,
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// ChildPropertyContext returns nil, nil if this CELSchemaContext is nil, otherwise constructs and returns a
|
||||
// CELSchemaContext for propertyName.
|
||||
func (c *CELSchemaContext) ChildPropertyContext(propSchema *apiextensions.JSONSchemaProps, propertyName string) *CELSchemaContext {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.childContext(propSchema, propertyTypeInfoAccessor{propertyName: propertyName})
|
||||
}
|
||||
|
||||
// ChildAdditionalPropertiesContext returns nil, nil if this CELSchemaContext is nil, otherwise it constructs and returns
|
||||
// a CELSchemaContext for the properties of an object if this CELSchemaContext is an object.
|
||||
// schema must be non-nil and have a non-nil schema.AdditionalProperties.
|
||||
func (c *CELSchemaContext) ChildAdditionalPropertiesContext(propsSchema *apiextensions.JSONSchemaProps) *CELSchemaContext {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.childContext(propsSchema, additionalItemsTypeInfoAccessor{})
|
||||
}
|
||||
|
||||
// ChildItemsContext returns nil, nil if this CELSchemaContext is nil, otherwise it constructs and returns a CELSchemaContext
|
||||
// for the items of an array if this CELSchemaContext is an array.
|
||||
func (c *CELSchemaContext) ChildItemsContext(itemsSchema *apiextensions.JSONSchemaProps) *CELSchemaContext {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.childContext(itemsSchema, itemsTypeInfoAccessor{})
|
||||
}
|
||||
|
||||
// childContext returns nil, nil if this CELSchemaContext is nil, otherwise it constructs a new CELSchemaContext for the
|
||||
// given child schema of the current schema context.
|
||||
// accessor optionally provides a way to access CELTypeInfo of the child from the current schema context's CELTypeInfo.
|
||||
// childContext returns a CELSchemaContext where the MaxCardinality is multiplied by the
|
||||
// factor that the schema increases the cardinality of its children. If the CELSchemaContext's
|
||||
// MaxCardinality is unbounded (nil) or the factor that the schema increase the cardinality
|
||||
// is unbounded, the resulting CELSchemaContext's MaxCardinality is also unbounded.
|
||||
func (c *CELSchemaContext) childContext(child *apiextensions.JSONSchemaProps, accessor typeInfoAccessor) *CELSchemaContext {
|
||||
result := &CELSchemaContext{
|
||||
parent: c,
|
||||
typeInfoAccessor: accessor,
|
||||
withinValidationRuleScope: c.withinValidationRuleScope,
|
||||
TotalCost: c.TotalCost,
|
||||
MaxCardinality: unbounded,
|
||||
converter: c.converter,
|
||||
}
|
||||
if child != nil {
|
||||
result.jsonSchema = child
|
||||
if len(child.XValidations) > 0 {
|
||||
result.withinValidationRuleScope = true
|
||||
}
|
||||
}
|
||||
if c.jsonSchema == nil {
|
||||
// nil schemas can be passed since we call ChildSchemaContext
|
||||
// before ValidateCustomResourceDefinitionOpenAPISchema performs its nil check
|
||||
return result
|
||||
}
|
||||
if c.MaxCardinality == unbounded {
|
||||
return result
|
||||
}
|
||||
maxElements := extractMaxElements(c.jsonSchema)
|
||||
if maxElements == unbounded {
|
||||
return result
|
||||
}
|
||||
result.MaxCardinality = uint64ptr(multiplyWithOverflowGuard(*c.MaxCardinality, *maxElements))
|
||||
return result
|
||||
}
|
||||
|
||||
type typeInfoAccessor interface {
|
||||
// accessTypeInfo looks up type information for a child schema from a non-nil parentTypeInfo and returns it,
|
||||
// or returns nil if the child schema information is not accessible. For example, a nil
|
||||
// return value is expected when a property name is unescapable in CEL.
|
||||
// The caller MUST ensure the provided parentTypeInfo is non-nil.
|
||||
accessTypeInfo(parentTypeInfo *CELTypeInfo) *CELTypeInfo
|
||||
}
|
||||
|
||||
type propertyTypeInfoAccessor struct {
|
||||
// propertyName is the property name in the parent schema that this schema is declared at.
|
||||
propertyName string
|
||||
}
|
||||
|
||||
func (c propertyTypeInfoAccessor) accessTypeInfo(parentTypeInfo *CELTypeInfo) *CELTypeInfo {
|
||||
if parentTypeInfo.Schema.Properties != nil {
|
||||
propSchema := parentTypeInfo.Schema.Properties[c.propertyName]
|
||||
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
|
||||
} // fields with unescapable names are expected to be absent
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type itemsTypeInfoAccessor struct{}
|
||||
|
||||
func (c itemsTypeInfoAccessor) accessTypeInfo(parentTypeInfo *CELTypeInfo) *CELTypeInfo {
|
||||
if parentTypeInfo.Schema.Items != nil {
|
||||
itemsSchema := parentTypeInfo.Schema.Items
|
||||
itemsDeclType := parentTypeInfo.DeclType.ElemType
|
||||
return &CELTypeInfo{Schema: itemsSchema, DeclType: itemsDeclType}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type additionalItemsTypeInfoAccessor struct{}
|
||||
|
||||
func (c additionalItemsTypeInfoAccessor) accessTypeInfo(parentTypeInfo *CELTypeInfo) *CELTypeInfo {
|
||||
if parentTypeInfo.Schema.AdditionalProperties != nil {
|
||||
propsSchema := parentTypeInfo.Schema.AdditionalProperties.Structural
|
||||
valuesDeclType := parentTypeInfo.DeclType.ElemType
|
||||
return &CELTypeInfo{Schema: propsSchema, DeclType: valuesDeclType}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TotalCost tracks the total cost of evaluating all the x-kubernetes-validations rules of a CustomResourceDefinition.
|
||||
type TotalCost struct {
|
||||
// Total accumulates the x-kubernetes-validations estimated rule cost total.
|
||||
Total uint64
|
||||
// MostExpensive accumulates the top 4 most expensive rules contributing to the Total. Only rules
|
||||
// that accumulate at least 1% of total cost limit are included.
|
||||
MostExpensive []RuleCost
|
||||
}
|
||||
|
||||
// ObserveExpressionCost accumulates the cost of evaluating a -kubernetes-validations rule.
|
||||
func (c *TotalCost) ObserveExpressionCost(path *field.Path, cost uint64) {
|
||||
if math.MaxUint64-c.Total < cost {
|
||||
c.Total = math.MaxUint64
|
||||
} else {
|
||||
c.Total += cost
|
||||
}
|
||||
|
||||
if cost < StaticEstimatedCRDCostLimit/100 { // ignore rules that contribute < 1% of total cost limit
|
||||
return
|
||||
}
|
||||
c.MostExpensive = append(c.MostExpensive, RuleCost{Path: path, Cost: cost})
|
||||
sort.Slice(c.MostExpensive, func(i, j int) bool {
|
||||
// sort in descending order so the most expensive rule is first
|
||||
return c.MostExpensive[i].Cost > c.MostExpensive[j].Cost
|
||||
})
|
||||
if len(c.MostExpensive) > 4 {
|
||||
c.MostExpensive = c.MostExpensive[:4]
|
||||
}
|
||||
}
|
||||
|
||||
// RuleCost represents the cost of evaluating a single x-kubernetes-validations rule.
|
||||
type RuleCost struct {
|
||||
Path *field.Path
|
||||
Cost uint64
|
||||
}
|
||||
|
||||
// extractMaxElements returns the factor by which the schema increases the cardinality
|
||||
// (number of possible data elements) of its children. If schema is a map and has
|
||||
// MaxProperties or an array has MaxItems, the int pointer of the max value is returned.
|
||||
// If schema is a map or array and does not have MaxProperties or MaxItems,
|
||||
// unbounded (nil) is returned to indicate that there is no limit to the possible
|
||||
// number of data elements imposed by the current schema. If the schema is an object, 1 is
|
||||
// returned to indicate that there is no increase to the number of possible data elements
|
||||
// for its children. Primitives do not have children, but 1 is returned for simplicity.
|
||||
func extractMaxElements(schema *apiextensions.JSONSchemaProps) *uint64 {
|
||||
switch schema.Type {
|
||||
case "object":
|
||||
if schema.AdditionalProperties != nil {
|
||||
if schema.MaxProperties != nil {
|
||||
maxProps := uint64(zeroIfNegative(*schema.MaxProperties))
|
||||
return &maxProps
|
||||
}
|
||||
return unbounded
|
||||
}
|
||||
// return 1 to indicate that all fields of an object exist at most one for
|
||||
// each occurrence of the object they are fields of
|
||||
return uint64ptr(1)
|
||||
case "array":
|
||||
if schema.MaxItems != nil {
|
||||
maxItems := uint64(zeroIfNegative(*schema.MaxItems))
|
||||
return &maxItems
|
||||
}
|
||||
return unbounded
|
||||
default:
|
||||
return uint64ptr(1)
|
||||
}
|
||||
}
|
||||
|
||||
func zeroIfNegative(v int64) int64 {
|
||||
if v < 0 {
|
||||
return 0
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func uint64ptr(i uint64) *uint64 {
|
||||
return &i
|
||||
}
|
||||
1800
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go
generated
vendored
Normal file
1800
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user