build: move e2e dependencies into e2e/go.mod

Several packages are only used while running the e2e suite. These
packages are less important to update, as the they can not influence the
final executable that is part of the Ceph-CSI container-image.

By moving these dependencies out of the main Ceph-CSI go.mod, it is
easier to identify if a reported CVE affects Ceph-CSI, or only the
testing (like most of the Kubernetes CVEs).

Signed-off-by: Niels de Vos <ndevos@ibm.com>
This commit is contained in:
Niels de Vos
2025-03-04 08:57:28 +01:00
committed by mergify[bot]
parent 15da101b1b
commit bec6090996
8047 changed files with 1407827 additions and 3453 deletions

11
e2e/vendor/k8s.io/apiserver/pkg/cel/OWNERS generated vendored Normal file
View File

@ -0,0 +1,11 @@
# See the OWNERS docs at https://go.k8s.io/owners
# Kubernetes CEL library authors and maintainers
approvers:
- jpbetz
- cici37
- jiahuif
reviewers:
- jpbetz
- cici37
- jiahuif

87
e2e/vendor/k8s.io/apiserver/pkg/cel/cidr.go generated vendored Normal file
View File

@ -0,0 +1,87 @@
/*
Copyright 2023 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 (
"fmt"
"math"
"net/netip"
"reflect"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
)
// CIDR provides a CEL representation of an network address.
type CIDR struct {
netip.Prefix
}
var (
CIDRType = cel.OpaqueType("net.CIDR")
)
// ConvertToNative implements ref.Val.ConvertToNative.
func (d CIDR) ConvertToNative(typeDesc reflect.Type) (any, error) {
if reflect.TypeOf(d.Prefix).AssignableTo(typeDesc) {
return d.Prefix, nil
}
if reflect.TypeOf("").AssignableTo(typeDesc) {
return d.Prefix.String(), nil
}
return nil, fmt.Errorf("type conversion error from 'CIDR' to '%v'", typeDesc)
}
// ConvertToType implements ref.Val.ConvertToType.
func (d CIDR) ConvertToType(typeVal ref.Type) ref.Val {
switch typeVal {
case CIDRType:
return d
case types.TypeType:
return CIDRType
case types.StringType:
return types.String(d.Prefix.String())
}
return types.NewErr("type conversion error from '%s' to '%s'", CIDRType, typeVal)
}
// Equal implements ref.Val.Equal.
func (d CIDR) Equal(other ref.Val) ref.Val {
otherD, ok := other.(CIDR)
if !ok {
return types.ValOrErr(other, "no such overload")
}
return types.Bool(d.Prefix == otherD.Prefix)
}
// Type implements ref.Val.Type.
func (d CIDR) Type() ref.Type {
return CIDRType
}
// Value implements ref.Val.Value.
func (d CIDR) Value() any {
return d.Prefix
}
// Size returns the size of the CIDR prefix address in bytes.
// Used in the size estimation of the runtime cost.
func (d CIDR) Size() ref.Val {
return types.Int(int(math.Ceil(float64(d.Prefix.Bits()) / 8)))
}

106
e2e/vendor/k8s.io/apiserver/pkg/cel/common/adaptor.go generated vendored Normal file
View File

@ -0,0 +1,106 @@
/*
Copyright 2023 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 common
// Schema is the adapted type for an OpenAPI schema that CEL uses.
// This schema does not cover all OpenAPI fields but only these CEL requires
// are exposed as getters.
type Schema interface {
// Type returns the OpenAPI type.
// Multiple types are not supported. It should return
// empty string if no type is specified.
Type() string
// Format returns the OpenAPI format. May be empty
Format() string
// Items returns the OpenAPI items. or nil of this field does not exist or
// contains no schema.
Items() Schema
// Properties returns the OpenAPI properties, or nil if this field does not
// exist.
// The values of the returned map are of the adapted type.
Properties() map[string]Schema
// AdditionalProperties returns the OpenAPI additional properties field,
// or nil if this field does not exist.
AdditionalProperties() SchemaOrBool
// Default returns the OpenAPI default field, or nil if this field does not exist.
Default() any
Validations
KubeExtensions
// WithTypeAndObjectMeta returns a schema that has the type and object meta set.
// the type includes "kind", "apiVersion" field
// the "metadata" field requires "name" and "generateName" to be set
// The original schema must not be mutated. Make a copy if necessary.
WithTypeAndObjectMeta() Schema
}
// Validations contains OpenAPI validation that the CEL library uses.
type Validations interface {
Pattern() string
Minimum() *float64
IsExclusiveMinimum() bool
Maximum() *float64
IsExclusiveMaximum() bool
MultipleOf() *float64
MinItems() *int64
MaxItems() *int64
MinLength() *int64
MaxLength() *int64
MinProperties() *int64
MaxProperties() *int64
Required() []string
Enum() []any
Nullable() bool
UniqueItems() bool
AllOf() []Schema
OneOf() []Schema
AnyOf() []Schema
Not() Schema
}
// KubeExtensions contains Kubernetes-specific extensions to the OpenAPI schema.
type KubeExtensions interface {
IsXIntOrString() bool
IsXEmbeddedResource() bool
IsXPreserveUnknownFields() bool
XListType() string
XListMapKeys() []string
XMapType() string
XValidations() []ValidationRule
}
// ValidationRule represents a single x-kubernetes-validations rule.
type ValidationRule interface {
Rule() string
Message() string
MessageExpression() string
FieldPath() string
}
// SchemaOrBool contains either a schema or a boolean indicating if the object
// can contain any fields.
type SchemaOrBool interface {
Schema() Schema
Allows() bool
}

334
e2e/vendor/k8s.io/apiserver/pkg/cel/common/equality.go generated vendored Normal file
View File

@ -0,0 +1,334 @@
/*
Copyright 2023 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 common
import (
"reflect"
"time"
)
// CorrelatedObject represents a node in a tree of objects that are being
// validated. It is used to keep track of the old value of an object during
// traversal of the new value. It is also used to cache the results of
// DeepEqual comparisons between the old and new values of objects.
//
// All receiver functions support being called on `nil` to support ergonomic
// recursive descent. The nil `CorrelatedObject` represents an uncorrelatable
// node in the tree.
//
// CorrelatedObject is not thread-safe. It is the responsibility of the caller
// to handle concurrency, if any.
type CorrelatedObject struct {
// Currently correlated old value during traversal of the schema/object
OldValue interface{}
// Value being validated
Value interface{}
// Schema used for validation of this value. The schema is also used
// to determine how to correlate the old object.
Schema Schema
// Duration spent on ratcheting validation for this object and all of its
// children.
Duration *time.Duration
// Scratch space below, may change during validation
// Cached comparison result of DeepEqual of `value` and `thunk.oldValue`
comparisonResult *bool
// Cached map representation of a map-type list, or nil if not map-type list
mapList MapList
// Children spawned by a call to `Validate` on this object
// key is either a string or an index, depending upon whether `value` is
// a map or a list, respectively.
//
// The list of children may be incomplete depending upon if the internal
// logic of kube-openapi's SchemaValidator short-circuited before
// reaching all of the children.
//
// It should be expected to have an entry for either all of the children, or
// none of them.
children map[interface{}]*CorrelatedObject
}
func NewCorrelatedObject(new, old interface{}, schema Schema) *CorrelatedObject {
d := time.Duration(0)
return &CorrelatedObject{
OldValue: old,
Value: new,
Schema: schema,
Duration: &d,
}
}
// If OldValue or Value is not a list, or the index is out of bounds of the
// Value list, returns nil
// If oldValue is a list, this considers the x-list-type to decide how to
// correlate old values:
//
// If listType is map, creates a map representation of the list using the designated
// map-keys, caches it for future calls, and returns the map value, or nil if
// the correlated key is not in the old map
//
// Otherwise, if the list type is not correlatable this funcion returns nil.
func (r *CorrelatedObject) correlateOldValueForChildAtNewIndex(index int) interface{} {
oldAsList, ok := r.OldValue.([]interface{})
if !ok {
return nil
}
asList, ok := r.Value.([]interface{})
if !ok {
return nil
} else if len(asList) <= index {
// Cannot correlate out of bounds index
return nil
}
listType := r.Schema.XListType()
switch listType {
case "map":
// Look up keys for this index in current object
currentElement := asList[index]
oldList := r.mapList
if oldList == nil {
oldList = MakeMapList(r.Schema, oldAsList)
r.mapList = oldList
}
return oldList.Get(currentElement)
case "set":
// Are sets correlatable? Only if the old value equals the current value.
// We might be able to support this, but do not currently see a lot
// of value
// (would allow you to add/remove items from sets with ratcheting but not change them)
return nil
case "":
fallthrough
case "atomic":
// Atomic lists are the default are not correlatable by item
// Ratcheting is not available on a per-index basis
return nil
default:
// Unrecognized list type. Assume non-correlatable.
return nil
}
}
// CachedDeepEqual is equivalent to reflect.DeepEqual, but caches the
// results in the tree of ratchetInvocationScratch objects on the way:
//
// For objects and arrays, this function will make a best effort to make
// use of past DeepEqual checks performed by this Node's children, if available.
//
// If a lazy computation could not be found for all children possibly due
// to validation logic short circuiting and skipping the children, then
// this function simply defers to reflect.DeepEqual.
func (r *CorrelatedObject) CachedDeepEqual() (res bool) {
start := time.Now()
defer func() {
if r != nil && r.Duration != nil {
*r.Duration += time.Since(start)
}
}()
if r == nil {
// Uncorrelatable node is not considered equal to its old value
return false
} else if r.comparisonResult != nil {
return *r.comparisonResult
}
defer func() {
r.comparisonResult = &res
}()
if r.Value == nil && r.OldValue == nil {
return true
} else if r.Value == nil || r.OldValue == nil {
return false
}
oldAsArray, oldIsArray := r.OldValue.([]interface{})
newAsArray, newIsArray := r.Value.([]interface{})
oldAsMap, oldIsMap := r.OldValue.(map[string]interface{})
newAsMap, newIsMap := r.Value.(map[string]interface{})
// If old and new are not the same type, they are not equal
if (oldIsArray != newIsArray) || oldIsMap != newIsMap {
return false
}
// Objects are known to be same type of (map, slice, or primitive)
switch {
case oldIsArray:
// Both arrays case. oldIsArray == newIsArray
if len(oldAsArray) != len(newAsArray) {
return false
}
for i := range newAsArray {
child := r.Index(i)
if child == nil {
if r.mapList == nil {
// Treat non-correlatable array as a unit with reflect.DeepEqual
return reflect.DeepEqual(oldAsArray, newAsArray)
}
// If array is correlatable, but old not found. Just short circuit
// comparison
return false
} else if !child.CachedDeepEqual() {
// If one child is not equal the entire object is not equal
return false
}
}
return true
case oldIsMap:
// Both maps case. oldIsMap == newIsMap
if len(oldAsMap) != len(newAsMap) {
return false
}
for k := range newAsMap {
child := r.Key(k)
if child == nil {
// Un-correlatable child due to key change.
// Objects are not equal.
return false
} else if !child.CachedDeepEqual() {
// If one child is not equal the entire object is not equal
return false
}
}
return true
default:
// Primitive: use reflect.DeepEqual
return reflect.DeepEqual(r.OldValue, r.Value)
}
}
// Key returns the child of the receiver with the given name.
// Returns nil if the given name is does not exist in the new object, or its
// value is not correlatable to an old value.
// If receiver is nil or if the new value is not an object/map, returns nil.
func (r *CorrelatedObject) Key(field string) *CorrelatedObject {
start := time.Now()
defer func() {
if r != nil && r.Duration != nil {
*r.Duration += time.Since(start)
}
}()
if r == nil || r.Schema == nil {
return nil
} else if existing, exists := r.children[field]; exists {
return existing
}
// Find correlated old value
oldAsMap, okOld := r.OldValue.(map[string]interface{})
newAsMap, okNew := r.Value.(map[string]interface{})
if !okOld || !okNew {
return nil
}
oldValueForField, okOld := oldAsMap[field]
newValueForField, okNew := newAsMap[field]
if !okOld || !okNew {
return nil
}
var propertySchema Schema
if prop, exists := r.Schema.Properties()[field]; exists {
propertySchema = prop
} else if addP := r.Schema.AdditionalProperties(); addP != nil && addP.Schema() != nil {
propertySchema = addP.Schema()
} else {
return nil
}
if r.children == nil {
r.children = make(map[interface{}]*CorrelatedObject, len(newAsMap))
}
res := &CorrelatedObject{
OldValue: oldValueForField,
Value: newValueForField,
Schema: propertySchema,
Duration: r.Duration,
}
r.children[field] = res
return res
}
// Index returns the child of the receiver at the given index.
// Returns nil if the given index is out of bounds, or its value is not
// correlatable to an old value.
// If receiver is nil or if the new value is not an array, returns nil.
func (r *CorrelatedObject) Index(i int) *CorrelatedObject {
start := time.Now()
defer func() {
if r != nil && r.Duration != nil {
*r.Duration += time.Since(start)
}
}()
if r == nil || r.Schema == nil {
return nil
} else if existing, exists := r.children[i]; exists {
return existing
}
asList, ok := r.Value.([]interface{})
if !ok || len(asList) <= i {
return nil
}
oldValueForIndex := r.correlateOldValueForChildAtNewIndex(i)
if oldValueForIndex == nil {
return nil
}
var itemSchema Schema
if i := r.Schema.Items(); i != nil {
itemSchema = i
} else {
return nil
}
if r.children == nil {
r.children = make(map[interface{}]*CorrelatedObject, len(asList))
}
res := &CorrelatedObject{
OldValue: oldValueForIndex,
Value: asList[i],
Schema: itemSchema,
Duration: r.Duration,
}
r.children[i] = res
return res
}

177
e2e/vendor/k8s.io/apiserver/pkg/cel/common/maplist.go generated vendored Normal file
View File

@ -0,0 +1,177 @@
/*
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 common
import (
"fmt"
"strings"
)
// MapList provides a "lookup by key" operation for lists (arrays) with x-kubernetes-list-type=map.
type MapList interface {
// Get returns the first element having given key, for all
// x-kubernetes-list-map-keys, to the provided object. If the provided object isn't itself a valid MapList element,
// get returns nil.
Get(interface{}) interface{}
}
type keyStrategy interface {
// CompositeKeyFor returns a composite key for the provided object, if possible, and a
// boolean that indicates whether or not a key could be generated for the provided object.
CompositeKeyFor(map[string]interface{}) (interface{}, bool)
}
// singleKeyStrategy is a cheaper strategy for associative lists that have exactly one key.
type singleKeyStrategy struct {
key string
}
// CompositeKeyFor directly returns the value of the single key to
// use as a composite key.
func (ks *singleKeyStrategy) CompositeKeyFor(obj map[string]interface{}) (interface{}, bool) {
v, ok := obj[ks.key]
if !ok {
return nil, false
}
switch v.(type) {
case bool, float64, int64, string:
return v, true
default:
return nil, false // non-scalar
}
}
// multiKeyStrategy computes a composite key of all key values.
type multiKeyStrategy struct {
sts Schema
}
// CompositeKeyFor returns a composite key computed from the values of all
// keys.
func (ks *multiKeyStrategy) CompositeKeyFor(obj map[string]interface{}) (interface{}, bool) {
const keyDelimiter = "\x00" // 0 byte should never appear in the composite key except as delimiter
var delimited strings.Builder
for _, key := range ks.sts.XListMapKeys() {
v, ok := obj[key]
if !ok {
return nil, false
}
switch v.(type) {
case bool:
fmt.Fprintf(&delimited, keyDelimiter+"%t", v)
case float64:
fmt.Fprintf(&delimited, keyDelimiter+"%f", v)
case int64:
fmt.Fprintf(&delimited, keyDelimiter+"%d", v)
case string:
fmt.Fprintf(&delimited, keyDelimiter+"%q", v)
default:
return nil, false // values must be scalars
}
}
return delimited.String(), true
}
// emptyMapList is a MapList containing no elements.
type emptyMapList struct{}
func (emptyMapList) Get(interface{}) interface{} {
return nil
}
type mapListImpl struct {
sts Schema
ks keyStrategy
// keyedItems contains all lazily keyed map items
keyedItems map[interface{}]interface{}
// unkeyedItems contains all map items that have not yet been keyed
unkeyedItems []interface{}
}
func (a *mapListImpl) Get(obj interface{}) interface{} {
mobj, ok := obj.(map[string]interface{})
if !ok {
return nil
}
key, ok := a.ks.CompositeKeyFor(mobj)
if !ok {
return nil
}
if match, ok := a.keyedItems[key]; ok {
return match
}
// keep keying items until we either find a match or run out of unkeyed items
for len(a.unkeyedItems) > 0 {
// dequeue an unkeyed item
item := a.unkeyedItems[0]
a.unkeyedItems = a.unkeyedItems[1:]
// key the item
mitem, ok := item.(map[string]interface{})
if !ok {
continue
}
itemKey, ok := a.ks.CompositeKeyFor(mitem)
if !ok {
continue
}
if _, exists := a.keyedItems[itemKey]; !exists {
a.keyedItems[itemKey] = mitem
}
// if it matches, short-circuit
if itemKey == key {
return mitem
}
}
return nil
}
func makeKeyStrategy(sts Schema) keyStrategy {
listMapKeys := sts.XListMapKeys()
if len(listMapKeys) == 1 {
key := listMapKeys[0]
return &singleKeyStrategy{
key: key,
}
}
return &multiKeyStrategy{
sts: sts,
}
}
// MakeMapList returns a queryable interface over the provided x-kubernetes-list-type=map
// keyedItems. If the provided schema is _not_ an array with x-kubernetes-list-type=map, returns an
// empty mapList.
func MakeMapList(sts Schema, items []interface{}) (rv MapList) {
if sts.Type() != "array" || sts.XListType() != "map" || len(sts.XListMapKeys()) == 0 || len(items) == 0 {
return emptyMapList{}
}
ks := makeKeyStrategy(sts)
return &mapListImpl{
sts: sts,
ks: ks,
keyedItems: map[interface{}]interface{}{},
unkeyedItems: items,
}
}

274
e2e/vendor/k8s.io/apiserver/pkg/cel/common/schemas.go generated vendored Normal file
View File

@ -0,0 +1,274 @@
/*
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 common
import (
"time"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
apiservercel "k8s.io/apiserver/pkg/cel"
"k8s.io/kube-openapi/pkg/validation/spec"
)
const maxRequestSizeBytes = apiservercel.DefaultMaxRequestSizeBytes
// SchemaDeclType converts the structural schema to a CEL declaration, or returns nil if the
// 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
// are not exposed if their items or additionalProperties schemas are not exposed. Object Properties are not exposed
// if their schema is not exposed.
//
// The CEL declaration for objects with XPreserveUnknownFields does not expose unknown fields.
func SchemaDeclType(s Schema, isResourceRoot bool) *apiservercel.DeclType {
if s == nil {
return nil
}
if s.IsXIntOrString() {
// schemas using XIntOrString are not required to have a type.
// intOrStringType represents the x-kubernetes-int-or-string union type in CEL expressions.
// In CEL, the type is represented as dynamic value, which can be thought of as a union type of all types.
// All type checking for XIntOrString is deferred to runtime, so all access to values of this type must
// be guarded with a type check, e.g.:
//
// To require that the string representation be a percentage:
// `type(intOrStringField) == string && intOrStringField.matches(r'(\d+(\.\d+)?%)')`
// To validate requirements on both the int and string representation:
// `type(intOrStringField) == int ? intOrStringField < 5 : double(intOrStringField.replace('%', '')) < 0.5
//
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
}
// We ignore XPreserveUnknownFields since we don't support validation rules on
// data that we don't have schema information for.
if isResourceRoot {
// 'apiVersion', 'kind', 'metadata.name' and 'metadata.generateName' are always accessible to validator rules
// at the root of resources, even if not specified in the schema.
// This includes the root of a custom resource and the root of XEmbeddedResource objects.
s = s.WithTypeAndObjectMeta()
}
switch s.Type() {
case "array":
if s.Items() != nil {
itemsType := SchemaDeclType(s.Items(), s.Items().IsXEmbeddedResource())
if itemsType == nil {
return nil
}
var maxItems int64
if s.MaxItems() != nil {
maxItems = zeroIfNegative(*s.MaxItems())
} else {
maxItems = estimateMaxArrayItemsFromMinSize(itemsType.MinSerializedSize)
}
return apiservercel.NewListType(itemsType, maxItems)
}
return nil
case "object":
if s.AdditionalProperties() != nil && s.AdditionalProperties().Schema() != nil {
propsType := SchemaDeclType(s.AdditionalProperties().Schema(), s.AdditionalProperties().Schema().IsXEmbeddedResource())
if propsType != nil {
var maxProperties int64
if s.MaxProperties() != nil {
maxProperties = zeroIfNegative(*s.MaxProperties())
} else {
maxProperties = estimateMaxAdditionalPropertiesFromMinSize(propsType.MinSerializedSize)
}
return apiservercel.NewMapType(apiservercel.StringType, propsType, maxProperties)
}
return nil
}
fields := make(map[string]*apiservercel.DeclField, len(s.Properties()))
required := map[string]bool{}
if s.Required() != nil {
for _, f := range s.Required() {
required[f] = true
}
}
// an object will always be serialized at least as {}, so account for that
minSerializedSize := int64(2)
for name, prop := range s.Properties() {
var enumValues []interface{}
if prop.Enum() != nil {
for _, e := range prop.Enum() {
enumValues = append(enumValues, e)
}
}
if fieldType := SchemaDeclType(prop, prop.IsXEmbeddedResource()); fieldType != nil {
if propName, ok := apiservercel.Escape(name); ok {
fields[propName] = apiservercel.NewDeclField(propName, fieldType, required[name], enumValues, prop.Default())
}
// the min serialized size for an object is 2 (for {}) plus the min size of all its required
// properties
// only include required properties without a default value; default values are filled in
// server-side
if required[name] && prop.Default() == nil {
minSerializedSize += int64(len(name)) + fieldType.MinSerializedSize + 4
}
}
}
objType := apiservercel.NewObjectType("object", fields)
objType.MinSerializedSize = minSerializedSize
return objType
case "string":
switch s.Format() {
case "byte":
byteWithMaxLength := apiservercel.NewSimpleTypeWithMinSize("bytes", cel.BytesType, types.Bytes([]byte{}), apiservercel.MinStringSize)
if s.MaxLength() != nil {
byteWithMaxLength.MaxElements = zeroIfNegative(*s.MaxLength())
} else {
byteWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s)
}
return byteWithMaxLength
case "duration":
durationWithMaxLength := apiservercel.NewSimpleTypeWithMinSize("duration", cel.DurationType, types.Duration{Duration: time.Duration(0)}, int64(apiservercel.MinDurationSizeJSON))
durationWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s)
return durationWithMaxLength
case "date":
timestampWithMaxLength := apiservercel.NewSimpleTypeWithMinSize("timestamp", cel.TimestampType, types.Timestamp{Time: time.Time{}}, int64(apiservercel.JSONDateSize))
timestampWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s)
return timestampWithMaxLength
case "date-time":
timestampWithMaxLength := apiservercel.NewSimpleTypeWithMinSize("timestamp", cel.TimestampType, types.Timestamp{Time: time.Time{}}, int64(apiservercel.MinDatetimeSizeJSON))
timestampWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s)
return timestampWithMaxLength
}
strWithMaxLength := apiservercel.NewSimpleTypeWithMinSize("string", cel.StringType, types.String(""), apiservercel.MinStringSize)
if s.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,
// but we need to reason about length for things like request size, so we use bytes in this code (and an individual
// unicode code point can be up to 4 bytes long)
strWithMaxLength.MaxElements = zeroIfNegative(*s.MaxLength()) * 4
} else {
if len(s.Enum()) > 0 {
strWithMaxLength.MaxElements = estimateMaxStringEnumLength(s)
} else {
strWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s)
}
}
return strWithMaxLength
case "boolean":
return apiservercel.BoolType
case "number":
return apiservercel.DoubleType
case "integer":
return apiservercel.IntType
}
return nil
}
func zeroIfNegative(v int64) int64 {
if v < 0 {
return 0
}
return v
}
// WithTypeAndObjectMeta ensures the kind, apiVersion and
// metadata.name and metadata.generateName properties are specified, making a shallow copy of the provided schema if needed.
func WithTypeAndObjectMeta(s *spec.Schema) *spec.Schema {
if s.Properties != nil &&
s.Properties["kind"].Type.Contains("string") &&
s.Properties["apiVersion"].Type.Contains("string") &&
s.Properties["metadata"].Type.Contains("object") &&
s.Properties["metadata"].Properties != nil &&
s.Properties["metadata"].Properties["name"].Type.Contains("string") &&
s.Properties["metadata"].Properties["generateName"].Type.Contains("string") {
return s
}
result := *s
props := make(map[string]spec.Schema, len(s.Properties))
for k, prop := range s.Properties {
props[k] = prop
}
stringType := spec.StringProperty()
props["kind"] = *stringType
props["apiVersion"] = *stringType
props["metadata"] = spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"name": *stringType,
"generateName": *stringType,
},
},
}
result.Properties = props
return &result
}
// estimateMaxStringLengthPerRequest estimates the maximum string length (in characters)
// of a string compatible with the format requirements in the provided schema.
// must only be called on schemas of type "string" or x-kubernetes-int-or-string: true
func estimateMaxStringLengthPerRequest(s Schema) int64 {
if s.IsXIntOrString() {
return maxRequestSizeBytes - 2
}
switch s.Format() {
case "duration":
return apiservercel.MaxDurationSizeJSON
case "date":
return apiservercel.JSONDateSize
case "date-time":
return apiservercel.MaxDatetimeSizeJSON
default:
// subtract 2 to account for ""
return maxRequestSizeBytes - 2
}
}
// estimateMaxStringLengthPerRequest estimates the maximum string length (in characters)
// that has a set of enum values.
// The result of the estimation is the length of the longest possible value.
func estimateMaxStringEnumLength(s Schema) int64 {
var maxLength int64
for _, v := range s.Enum() {
if s, ok := v.(string); ok && int64(len(s)) > maxLength {
maxLength = int64(len(s))
}
}
return maxLength
}
// estimateMaxArrayItemsPerRequest estimates the maximum number of array items with
// the provided minimum serialized size that can fit into a single request.
func estimateMaxArrayItemsFromMinSize(minSize int64) int64 {
// subtract 2 to account for [ and ]
return (maxRequestSizeBytes - 2) / (minSize + 1)
}
// estimateMaxAdditionalPropertiesPerRequest estimates the maximum number of additional properties
// with the provided minimum serialized size that can fit into a single request.
func estimateMaxAdditionalPropertiesFromMinSize(minSize int64) int64 {
// 2 bytes for key + "" + colon + comma + smallest possible value, realistically the actual keys
// will all vary in length
keyValuePairSize := minSize + 6
// subtract 2 to account for { and }
return (maxRequestSizeBytes - 2) / keyValuePairSize
}

View File

@ -0,0 +1,127 @@
/*
Copyright 2024 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 common
import (
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
)
// TypeResolver resolves a type by a given name.
type TypeResolver interface {
// Resolve resolves the type by its name.
// This function returns false if the name does not refer to a known object type.
Resolve(name string) (ResolvedType, bool)
}
// ResolvedType refers an object type that can be looked up for its fields.
type ResolvedType interface {
ref.Type
Type() *types.Type
// Field finds the field by the field name, or false if the field is not known.
// This function directly return a FieldType that is known to CEL to be more customizable.
Field(name string) (*types.FieldType, bool)
// FieldNames returns the field names associated with the type, if the type
// is found.
FieldNames() ([]string, bool)
// Val creates an instance for the ResolvedType, given its fields and their values.
Val(fields map[string]ref.Val) ref.Val
}
// ResolverTypeProvider delegates type resolution first to the TypeResolver and then
// to the underlying types.Provider for types not resolved by the TypeResolver.
type ResolverTypeProvider struct {
typeResolver TypeResolver
underlyingTypeProvider types.Provider
}
var _ types.Provider = (*ResolverTypeProvider)(nil)
// FindStructType returns the Type give a qualified type name, by looking it up with
// the DynamicTypeResolver and translating it to CEL Type.
// If the type is not known to the DynamicTypeResolver, the lookup falls back to the underlying
// ResolverTypeProvider instead.
func (p *ResolverTypeProvider) FindStructType(structType string) (*types.Type, bool) {
t, ok := p.typeResolver.Resolve(structType)
if ok {
return types.NewTypeTypeWithParam(t.Type()), true
}
return p.underlyingTypeProvider.FindStructType(structType)
}
// FindStructFieldNames returns the field names associated with the type, if the type
// is found.
func (p *ResolverTypeProvider) FindStructFieldNames(structType string) ([]string, bool) {
t, ok := p.typeResolver.Resolve(structType)
if ok {
return t.FieldNames()
}
return p.underlyingTypeProvider.FindStructFieldNames(structType)
}
// FindStructFieldType returns the field type for a checked type value.
// Returns false if the field could not be found.
func (p *ResolverTypeProvider) FindStructFieldType(structType, fieldName string) (*types.FieldType, bool) {
t, ok := p.typeResolver.Resolve(structType)
if ok {
return t.Field(fieldName)
}
return p.underlyingTypeProvider.FindStructFieldType(structType, fieldName)
}
// NewValue creates a new type value from a qualified name and map of fields.
func (p *ResolverTypeProvider) NewValue(structType string, fields map[string]ref.Val) ref.Val {
t, ok := p.typeResolver.Resolve(structType)
if ok {
return t.Val(fields)
}
return p.underlyingTypeProvider.NewValue(structType, fields)
}
func (p *ResolverTypeProvider) EnumValue(enumName string) ref.Val {
return p.underlyingTypeProvider.EnumValue(enumName)
}
func (p *ResolverTypeProvider) FindIdent(identName string) (ref.Val, bool) {
return p.underlyingTypeProvider.FindIdent(identName)
}
// ResolverEnvOption creates the ResolverTypeProvider with a given DynamicTypeResolver,
// and also returns the CEL ResolverEnvOption to apply it to the env.
func ResolverEnvOption(resolver TypeResolver) cel.EnvOption {
_, envOpt := NewResolverTypeProviderAndEnvOption(resolver)
return envOpt
}
// NewResolverTypeProviderAndEnvOption creates the ResolverTypeProvider with a given DynamicTypeResolver,
// and also returns the CEL ResolverEnvOption to apply it to the env.
func NewResolverTypeProviderAndEnvOption(resolver TypeResolver) (*ResolverTypeProvider, cel.EnvOption) {
tp := &ResolverTypeProvider{typeResolver: resolver}
var envOption cel.EnvOption = func(e *cel.Env) (*cel.Env, error) {
// wrap the existing type provider (acquired from the env)
// and set new type provider for the env.
tp.underlyingTypeProvider = e.CELTypeProvider()
typeProviderOption := cel.CustomTypeProvider(tp)
return typeProviderOption(e)
}
return tp, envOption
}

721
e2e/vendor/k8s.io/apiserver/pkg/cel/common/values.go generated vendored Normal file
View File

@ -0,0 +1,721 @@
/*
Copyright 2021 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 common
import (
"fmt"
"reflect"
"sync"
"time"
"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/kube-openapi/pkg/validation/strfmt"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apiserver/pkg/cel"
)
// UnstructuredToVal converts a Kubernetes unstructured data element to a CEL Val.
// The root schema of custom resource schema is expected contain type meta and object meta schemas.
// If Embedded resources do not contain type meta and object meta schemas, they will be added automatically.
func UnstructuredToVal(unstructured interface{}, schema Schema) ref.Val {
if unstructured == nil {
if schema.Nullable() {
return types.NullValue
}
return types.NewErr("invalid data, got null for schema with nullable=false")
}
if schema.IsXIntOrString() {
switch v := unstructured.(type) {
case string:
return types.String(v)
case int:
return types.Int(v)
case int32:
return types.Int(v)
case int64:
return types.Int(v)
}
return types.NewErr("invalid data, expected XIntOrString value to be either a string or integer")
}
if schema.Type() == "object" {
m, ok := unstructured.(map[string]interface{})
if !ok {
return types.NewErr("invalid data, expected a map for the provided schema with type=object")
}
if schema.IsXEmbeddedResource() || schema.Properties() != nil {
if schema.IsXEmbeddedResource() {
schema = schema.WithTypeAndObjectMeta()
}
return &unstructuredMap{
value: m,
schema: schema,
propSchema: func(key string) (Schema, bool) {
if schema, ok := schema.Properties()[key]; ok {
return schema, true
}
return nil, false
},
}
}
if schema.AdditionalProperties() != nil && schema.AdditionalProperties().Schema() != nil {
return &unstructuredMap{
value: m,
schema: schema,
propSchema: func(key string) (Schema, bool) {
return schema.AdditionalProperties().Schema(), true
},
}
}
// properties and additionalProperties are mutual exclusive, but nothing prevents the situation
// where both are missing.
// An object that (1) has no properties (2) has no additionalProperties or additionalProperties == false
// is treated as an empty object.
// An object that has additionalProperties == true is treated as an unstructured map.
// An object that has x-kubernetes-preserve-unknown-field extension set is treated as an unstructured map.
// Empty object vs unstructured map is differentiated by unstructuredMap implementation with the set schema.
// The resulting result remains the same.
return &unstructuredMap{
value: m,
schema: schema,
propSchema: func(key string) (Schema, bool) {
return nil, false
},
}
}
if schema.Type() == "array" {
l, ok := unstructured.([]interface{})
if !ok {
return types.NewErr("invalid data, expected an array for the provided schema with type=array")
}
if schema.Items() == nil {
return types.NewErr("invalid array type, expected Items with a non-empty Schema")
}
typedList := unstructuredList{elements: l, itemsSchema: schema.Items()}
listType := schema.XListType()
if listType != "" {
switch listType {
case "map":
mapKeys := schema.XListMapKeys()
return &unstructuredMapList{unstructuredList: typedList, escapedKeyProps: escapeKeyProps(mapKeys)}
case "set":
return &unstructuredSetList{unstructuredList: typedList}
case "atomic":
return &typedList
default:
return types.NewErr("invalid x-kubernetes-list-type, expected 'map', 'set' or 'atomic' but got %s", listType)
}
}
return &typedList
}
if schema.Type() == "string" {
str, ok := unstructured.(string)
if !ok {
return types.NewErr("invalid data, expected string, got %T", unstructured)
}
switch schema.Format() {
case "duration":
d, err := strfmt.ParseDuration(str)
if err != nil {
return types.NewErr("Invalid duration %s: %v", str, err)
}
return types.Duration{Duration: d}
case "date":
d, err := time.Parse(strfmt.RFC3339FullDate, str) // strfmt uses this format for OpenAPIv3 value validation
if err != nil {
return types.NewErr("Invalid date formatted string %s: %v", str, err)
}
return types.Timestamp{Time: d}
case "date-time":
d, err := strfmt.ParseDateTime(str)
if err != nil {
return types.NewErr("Invalid date-time formatted string %s: %v", str, err)
}
return types.Timestamp{Time: time.Time(d)}
case "byte":
base64 := strfmt.Base64{}
err := base64.UnmarshalText([]byte(str))
if err != nil {
return types.NewErr("Invalid byte formatted string %s: %v", str, err)
}
return types.Bytes(base64)
}
return types.String(str)
}
if schema.Type() == "number" {
switch v := unstructured.(type) {
// float representations of whole numbers (e.g. 1.0, 0.0) can convert to int representations (e.g. 1, 0) in yaml
// to json translation, and then get parsed as int64s
case int:
return types.Double(v)
case int32:
return types.Double(v)
case int64:
return types.Double(v)
case float32:
return types.Double(v)
case float64:
return types.Double(v)
default:
return types.NewErr("invalid data, expected float, got %T", unstructured)
}
}
if schema.Type() == "integer" {
switch v := unstructured.(type) {
case int:
return types.Int(v)
case int32:
return types.Int(v)
case int64:
return types.Int(v)
default:
return types.NewErr("invalid data, expected int, got %T", unstructured)
}
}
if schema.Type() == "boolean" {
b, ok := unstructured.(bool)
if !ok {
return types.NewErr("invalid data, expected bool, got %T", unstructured)
}
return types.Bool(b)
}
if schema.IsXPreserveUnknownFields() {
return &unknownPreserved{u: unstructured}
}
return types.NewErr("invalid type, expected object, array, number, integer, boolean or string, or no type with x-kubernetes-int-or-string or x-kubernetes-preserve-unknown-fields is true, got %s", schema.Type())
}
// unknownPreserved represents unknown data preserved in custom resources via x-kubernetes-preserve-unknown-fields.
// It preserves the data at runtime without assuming it is of any particular type and supports only equality checking.
// unknownPreserved should be used only for values are not directly accessible in CEL expressions, i.e. for data
// where there is no corresponding CEL type declaration.
type unknownPreserved struct {
u interface{}
}
func (t *unknownPreserved) ConvertToNative(refType reflect.Type) (interface{}, error) {
return nil, fmt.Errorf("type conversion to '%s' not supported for values preserved by x-kubernetes-preserve-unknown-fields", refType)
}
func (t *unknownPreserved) ConvertToType(typeValue ref.Type) ref.Val {
return types.NewErr("type conversion to '%s' not supported for values preserved by x-kubernetes-preserve-unknown-fields", typeValue.TypeName())
}
func (t *unknownPreserved) Equal(other ref.Val) ref.Val {
return types.Bool(equality.Semantic.DeepEqual(t.u, other.Value()))
}
func (t *unknownPreserved) Type() ref.Type {
return types.UnknownType
}
func (t *unknownPreserved) Value() interface{} {
return t.u // used by Equal checks
}
// unstructuredMapList represents an unstructured data instance of an OpenAPI array with x-kubernetes-list-type=map.
type unstructuredMapList struct {
unstructuredList
escapedKeyProps []string
sync.Once // for for lazy load of mapOfList since it is only needed if Equals is called
mapOfList map[interface{}]interface{}
}
func (t *unstructuredMapList) getMap() map[interface{}]interface{} {
t.Do(func() {
t.mapOfList = make(map[interface{}]interface{}, len(t.elements))
for _, e := range t.elements {
t.mapOfList[t.toMapKey(e)] = e
}
})
return t.mapOfList
}
// toMapKey returns a valid golang map key for the given element of the map list.
// element must be a valid map list entry where all map key props are scalar types (which are comparable in go
// and valid for use in a golang map key).
func (t *unstructuredMapList) toMapKey(element interface{}) interface{} {
eObj, ok := element.(map[string]interface{})
if !ok {
return types.NewErr("unexpected data format for element of array with x-kubernetes-list-type=map: %T", element)
}
// Arrays are comparable in go and may be used as map keys, but maps and slices are not.
// So we can special case small numbers of key props as arrays and fall back to serialization
// for larger numbers of key props
if len(t.escapedKeyProps) == 1 {
return eObj[t.escapedKeyProps[0]]
}
if len(t.escapedKeyProps) == 2 {
return [2]interface{}{eObj[t.escapedKeyProps[0]], eObj[t.escapedKeyProps[1]]}
}
if len(t.escapedKeyProps) == 3 {
return [3]interface{}{eObj[t.escapedKeyProps[0]], eObj[t.escapedKeyProps[1]], eObj[t.escapedKeyProps[2]]}
}
key := make([]interface{}, len(t.escapedKeyProps))
for i, kf := range t.escapedKeyProps {
key[i] = eObj[kf]
}
return fmt.Sprintf("%v", key)
}
// Equal on a map list ignores list element order.
func (t *unstructuredMapList) Equal(other ref.Val) ref.Val {
oMapList, ok := other.(traits.Lister)
if !ok {
return types.MaybeNoSuchOverloadErr(other)
}
sz := types.Int(len(t.elements))
if sz != oMapList.Size() {
return types.False
}
tMap := t.getMap()
for it := oMapList.Iterator(); it.HasNext() == types.True; {
v := it.Next()
k := t.toMapKey(v.Value())
tVal, ok := tMap[k]
if !ok {
return types.False
}
eq := UnstructuredToVal(tVal, t.itemsSchema).Equal(v)
if eq != types.True {
return eq // either false or error
}
}
return types.True
}
// Add for a map list `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values
// are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with
// non-intersecting keys are appended, retaining their partial order.
func (t *unstructuredMapList) Add(other ref.Val) ref.Val {
oMapList, ok := other.(traits.Lister)
if !ok {
return types.MaybeNoSuchOverloadErr(other)
}
elements := make([]interface{}, len(t.elements))
keyToIdx := map[interface{}]int{}
for i, e := range t.elements {
k := t.toMapKey(e)
keyToIdx[k] = i
elements[i] = e
}
for it := oMapList.Iterator(); it.HasNext() == types.True; {
v := it.Next().Value()
k := t.toMapKey(v)
if overwritePosition, ok := keyToIdx[k]; ok {
elements[overwritePosition] = v
} else {
elements = append(elements, v)
}
}
return &unstructuredMapList{
unstructuredList: unstructuredList{elements: elements, itemsSchema: t.itemsSchema},
escapedKeyProps: t.escapedKeyProps,
}
}
// escapeKeyProps returns identifiers with Escape applied to each.
// Identifiers that cannot be escaped are left as-is. They are inaccessible to CEL programs but are
// are still needed internally to perform equality checks.
func escapeKeyProps(idents []string) []string {
result := make([]string, len(idents))
for i, prop := range idents {
if escaped, ok := cel.Escape(prop); ok {
result[i] = escaped
} else {
result[i] = prop
}
}
return result
}
// unstructuredSetList represents an unstructured data instance of an OpenAPI array with x-kubernetes-list-type=set.
type unstructuredSetList struct {
unstructuredList
escapedKeyProps []string
sync.Once // for for lazy load of setOfList since it is only needed if Equals is called
set map[interface{}]struct{}
}
func (t *unstructuredSetList) getSet() map[interface{}]struct{} {
// sets are only allowed to contain scalar elements, which are comparable in go, and can safely be used as
// golang map keys
t.Do(func() {
t.set = make(map[interface{}]struct{}, len(t.elements))
for _, e := range t.elements {
t.set[e] = struct{}{}
}
})
return t.set
}
// Equal on a map list ignores list element order.
func (t *unstructuredSetList) Equal(other ref.Val) ref.Val {
oSetList, ok := other.(traits.Lister)
if !ok {
return types.MaybeNoSuchOverloadErr(other)
}
sz := types.Int(len(t.elements))
if sz != oSetList.Size() {
return types.False
}
tSet := t.getSet()
for it := oSetList.Iterator(); it.HasNext() == types.True; {
next := it.Next().Value()
_, ok := tSet[next]
if !ok {
return types.False
}
}
return types.True
}
// Add for a set list `X + Y` performs a union where the array positions of all elements in `X` are preserved and
// non-intersecting elements in `Y` are appended, retaining their partial order.
func (t *unstructuredSetList) Add(other ref.Val) ref.Val {
oSetList, ok := other.(traits.Lister)
if !ok {
return types.MaybeNoSuchOverloadErr(other)
}
elements := t.elements
set := t.getSet()
for it := oSetList.Iterator(); it.HasNext() == types.True; {
next := it.Next().Value()
if _, ok := set[next]; !ok {
set[next] = struct{}{}
elements = append(elements, next)
}
}
return &unstructuredSetList{
unstructuredList: unstructuredList{elements: elements, itemsSchema: t.itemsSchema},
escapedKeyProps: t.escapedKeyProps,
}
}
// unstructuredList represents an unstructured data instance of an OpenAPI array with x-kubernetes-list-type=atomic (the default).
type unstructuredList struct {
elements []interface{}
itemsSchema Schema
}
var _ = traits.Lister(&unstructuredList{})
func (t *unstructuredList) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
switch typeDesc.Kind() {
case reflect.Slice:
switch t.itemsSchema.Type() {
// Workaround for https://github.com/kubernetes/kubernetes/issues/117590 until we
// resolve the desired behavior in cel-go via https://github.com/google/cel-go/issues/688
case "string":
var result []string
for _, e := range t.elements {
s, ok := e.(string)
if !ok {
return nil, fmt.Errorf("expected all elements to be of type string, but got %T", e)
}
result = append(result, s)
}
return result, nil
default:
return t.elements, nil
}
}
return nil, fmt.Errorf("type conversion error from '%s' to '%s'", t.Type(), typeDesc)
}
func (t *unstructuredList) ConvertToType(typeValue ref.Type) ref.Val {
switch typeValue {
case types.ListType:
return t
case types.TypeType:
return types.ListType
}
return types.NewErr("type conversion error from '%s' to '%s'", t.Type(), typeValue.TypeName())
}
func (t *unstructuredList) Equal(other ref.Val) ref.Val {
oList, ok := other.(traits.Lister)
if !ok {
return types.MaybeNoSuchOverloadErr(other)
}
sz := types.Int(len(t.elements))
if sz != oList.Size() {
return types.False
}
for i := types.Int(0); i < sz; i++ {
eq := t.Get(i).Equal(oList.Get(i))
if eq != types.True {
return eq // either false or error
}
}
return types.True
}
func (t *unstructuredList) Type() ref.Type {
return types.ListType
}
func (t *unstructuredList) Value() interface{} {
return t.elements
}
func (t *unstructuredList) Add(other ref.Val) ref.Val {
oList, ok := other.(traits.Lister)
if !ok {
return types.MaybeNoSuchOverloadErr(other)
}
elements := t.elements
for it := oList.Iterator(); it.HasNext() == types.True; {
next := it.Next().Value()
elements = append(elements, next)
}
return &unstructuredList{elements: elements, itemsSchema: t.itemsSchema}
}
func (t *unstructuredList) Contains(val ref.Val) ref.Val {
if types.IsUnknownOrError(val) {
return val
}
var err ref.Val
sz := len(t.elements)
for i := 0; i < sz; i++ {
elem := UnstructuredToVal(t.elements[i], t.itemsSchema)
cmp := elem.Equal(val)
b, ok := cmp.(types.Bool)
if !ok && err == nil {
err = types.MaybeNoSuchOverloadErr(cmp)
}
if b == types.True {
return types.True
}
}
if err != nil {
return err
}
return types.False
}
func (t *unstructuredList) Get(idx ref.Val) ref.Val {
iv, isInt := idx.(types.Int)
if !isInt {
return types.ValOrErr(idx, "unsupported index: %v", idx)
}
i := int(iv)
if i < 0 || i >= len(t.elements) {
return types.NewErr("index out of bounds: %v", idx)
}
return UnstructuredToVal(t.elements[i], t.itemsSchema)
}
func (t *unstructuredList) Iterator() traits.Iterator {
items := make([]ref.Val, len(t.elements))
for i, item := range t.elements {
itemCopy := item
items[i] = UnstructuredToVal(itemCopy, t.itemsSchema)
}
return &listIterator{unstructuredList: t, items: items}
}
type listIterator struct {
*unstructuredList
items []ref.Val
idx int
}
func (it *listIterator) HasNext() ref.Val {
return types.Bool(it.idx < len(it.items))
}
func (it *listIterator) Next() ref.Val {
item := it.items[it.idx]
it.idx++
return item
}
func (t *unstructuredList) Size() ref.Val {
return types.Int(len(t.elements))
}
// unstructuredMap represented an unstructured data instance of an OpenAPI object.
type unstructuredMap struct {
value map[string]interface{}
schema Schema
// propSchema finds the schema to use for a particular map key.
propSchema func(key string) (Schema, bool)
}
var _ = traits.Mapper(&unstructuredMap{})
func (t *unstructuredMap) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
switch typeDesc.Kind() {
case reflect.Map:
return t.value, nil
}
return nil, fmt.Errorf("type conversion error from '%s' to '%s'", t.Type(), typeDesc)
}
func (t *unstructuredMap) ConvertToType(typeValue ref.Type) ref.Val {
switch typeValue {
case types.MapType:
return t
case types.TypeType:
return types.MapType
}
return types.NewErr("type conversion error from '%s' to '%s'", t.Type(), typeValue.TypeName())
}
func (t *unstructuredMap) Equal(other ref.Val) ref.Val {
oMap, isMap := other.(traits.Mapper)
if !isMap {
return types.MaybeNoSuchOverloadErr(other)
}
if t.Size() != oMap.Size() {
return types.False
}
for key, value := range t.value {
if propSchema, ok := t.propSchema(key); ok {
ov, found := oMap.Find(types.String(key))
if !found {
return types.False
}
v := UnstructuredToVal(value, propSchema)
vEq := v.Equal(ov)
if vEq != types.True {
return vEq // either false or error
}
} else {
// Must be an object with properties.
// Since we've encountered an unknown field, fallback to unstructured equality checking.
ouMap, ok := other.(*unstructuredMap)
if !ok {
// The compiler ensures equality is against the same type of object, so this should be unreachable
return types.MaybeNoSuchOverloadErr(other)
}
if oValue, ok := ouMap.value[key]; ok {
if !equality.Semantic.DeepEqual(value, oValue) {
return types.False
}
}
}
}
return types.True
}
func (t *unstructuredMap) Type() ref.Type {
return types.MapType
}
func (t *unstructuredMap) Value() interface{} {
return t.value
}
func (t *unstructuredMap) Contains(key ref.Val) ref.Val {
v, found := t.Find(key)
if v != nil && types.IsUnknownOrError(v) {
return v
}
return types.Bool(found)
}
func (t *unstructuredMap) Get(key ref.Val) ref.Val {
v, found := t.Find(key)
if found {
return v
}
return types.ValOrErr(key, "no such key: %v", key)
}
func (t *unstructuredMap) Iterator() traits.Iterator {
isObject := t.schema.Properties() != nil
keys := make([]ref.Val, len(t.value))
i := 0
for k := range t.value {
if _, ok := t.propSchema(k); ok {
mapKey := k
if isObject {
if escaped, ok := cel.Escape(k); ok {
mapKey = escaped
}
}
keys[i] = types.String(mapKey)
i++
}
}
return &mapIterator{unstructuredMap: t, keys: keys}
}
type mapIterator struct {
*unstructuredMap
keys []ref.Val
idx int
}
func (it *mapIterator) HasNext() ref.Val {
return types.Bool(it.idx < len(it.keys))
}
func (it *mapIterator) Next() ref.Val {
key := it.keys[it.idx]
it.idx++
return key
}
func (t *unstructuredMap) Size() ref.Val {
return types.Int(len(t.value))
}
func (t *unstructuredMap) Find(key ref.Val) (ref.Val, bool) {
isObject := t.schema.Properties() != nil
keyStr, ok := key.(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(key), true
}
k := keyStr.Value().(string)
if isObject {
k, ok = cel.Unescape(k)
if !ok {
return nil, false
}
}
if v, ok := t.value[k]; ok {
// If this is an object with properties, not an object with additionalProperties,
// then null valued nullable fields are treated the same as absent optional fields.
if isObject && v == nil {
return nil, false
}
if propSchema, ok := t.propSchema(k); ok {
return UnstructuredToVal(v, propSchema), true
}
}
return nil, false
}

283
e2e/vendor/k8s.io/apiserver/pkg/cel/environment/base.go generated vendored Normal file
View File

@ -0,0 +1,283 @@
/*
Copyright 2023 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 environment
import (
"fmt"
"strconv"
"sync"
"sync/atomic"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/checker"
"github.com/google/cel-go/ext"
"github.com/google/cel-go/interpreter"
"golang.org/x/sync/singleflight"
"k8s.io/apimachinery/pkg/util/version"
celconfig "k8s.io/apiserver/pkg/apis/cel"
"k8s.io/apiserver/pkg/cel/library"
genericfeatures "k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/component-base/featuregate"
utilversion "k8s.io/component-base/version"
)
// DefaultCompatibilityVersion returns a default compatibility version for use with EnvSet
// that guarantees compatibility with CEL features/libraries/parameters understood by
// the api server min compatibility version
//
// This default will be set to no more than the current Kubernetes major.minor version.
//
// Note that a default version number less than n-1 the current Kubernetes major.minor version
// indicates a wider range of version compatibility than strictly required for rollback.
// A wide range of compatibility is desirable because it means that CEL expressions are portable
// across a wider range of Kubernetes versions.
// A default version number equal to the current Kubernetes major.minor version
// indicates fast forward CEL features that can be used when rollback is no longer needed.
func DefaultCompatibilityVersion() *version.Version {
effectiveVer := featuregate.DefaultComponentGlobalsRegistry.EffectiveVersionFor(featuregate.DefaultKubeComponent)
if effectiveVer == nil {
effectiveVer = utilversion.DefaultKubeEffectiveVersion()
}
return effectiveVer.MinCompatibilityVersion()
}
var baseOpts = append(baseOptsWithoutStrictCost, StrictCostOpt)
var baseOptsWithoutStrictCost = []VersionedOptions{
{
// CEL epoch was actually 1.23, but we artificially set it to 1.0 because these
// options should always be present.
IntroducedVersion: version.MajorMinor(1, 0),
EnvOptions: []cel.EnvOption{
cel.HomogeneousAggregateLiterals(),
// Validate function declarations once during base env initialization,
// so they don't need to be evaluated each time a CEL rule is compiled.
// This is a relatively expensive operation.
cel.EagerlyValidateDeclarations(true),
cel.DefaultUTCTimeZone(true),
UnversionedLib(library.URLs),
UnversionedLib(library.Regex),
UnversionedLib(library.Lists),
// cel-go v0.17.7 change the cost of has() from 0 to 1, but also provided the CostEstimatorOptions option to preserve the old behavior, so we enabled it at the same time we bumped our cel version to v0.17.7.
// Since it is a regression fix, we apply it uniformly to all code use v0.17.7.
cel.CostEstimatorOptions(checker.PresenceTestHasCost(false)),
},
ProgramOptions: []cel.ProgramOption{
cel.EvalOptions(cel.OptOptimize, cel.OptTrackCost),
cel.CostLimit(celconfig.PerCallLimit),
// cel-go v0.17.7 change the cost of has() from 0 to 1, but also provided the CostEstimatorOptions option to preserve the old behavior, so we enabled it at the same time we bumped our cel version to v0.17.7.
// Since it is a regression fix, we apply it uniformly to all code use v0.17.7.
cel.CostTrackerOptions(interpreter.PresenceTestHasCost(false)),
},
},
{
IntroducedVersion: version.MajorMinor(1, 27),
EnvOptions: []cel.EnvOption{
UnversionedLib(library.Authz),
},
},
{
IntroducedVersion: version.MajorMinor(1, 28),
EnvOptions: []cel.EnvOption{
cel.CrossTypeNumericComparisons(true),
cel.OptionalTypes(),
UnversionedLib(library.Quantity),
},
},
// add the new validator in 1.29
{
IntroducedVersion: version.MajorMinor(1, 29),
EnvOptions: []cel.EnvOption{
cel.ASTValidators(
cel.ValidateDurationLiterals(),
cel.ValidateTimestampLiterals(),
cel.ValidateRegexLiterals(),
cel.ValidateHomogeneousAggregateLiterals(),
),
},
},
// String library
{
IntroducedVersion: version.MajorMinor(1, 0),
RemovedVersion: version.MajorMinor(1, 29),
EnvOptions: []cel.EnvOption{
ext.Strings(ext.StringsVersion(0)),
},
},
{
IntroducedVersion: version.MajorMinor(1, 29),
EnvOptions: []cel.EnvOption{
ext.Strings(ext.StringsVersion(2)),
},
},
// Set library
{
IntroducedVersion: version.MajorMinor(1, 29),
EnvOptions: []cel.EnvOption{
ext.Sets(),
},
},
{
IntroducedVersion: version.MajorMinor(1, 30),
EnvOptions: []cel.EnvOption{
UnversionedLib(library.IP),
UnversionedLib(library.CIDR),
},
},
// Format Library
{
IntroducedVersion: version.MajorMinor(1, 31),
EnvOptions: []cel.EnvOption{
UnversionedLib(library.Format),
},
},
// Authz selectors
{
IntroducedVersion: version.MajorMinor(1, 31),
FeatureEnabled: func() bool {
enabled := utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AuthorizeWithSelectors)
authzSelectorsLibraryInit.Do(func() {
// Record the first time feature enablement was checked for this library.
// This is checked from integration tests to ensure no cached cel envs
// are constructed before feature enablement is effectively set.
authzSelectorsLibraryEnabled.Store(enabled)
// Uncomment to debug where the first initialization is coming from if needed.
// debug.PrintStack()
})
return enabled
},
EnvOptions: []cel.EnvOption{
UnversionedLib(library.AuthzSelectors),
},
},
// Two variable comprehensions
{
IntroducedVersion: version.MajorMinor(1, 32),
EnvOptions: []cel.EnvOption{
UnversionedLib(ext.TwoVarComprehensions),
},
},
}
var (
authzSelectorsLibraryInit sync.Once
authzSelectorsLibraryEnabled atomic.Value
)
// AuthzSelectorsLibraryEnabled returns whether the AuthzSelectors library was enabled when it was constructed.
// If it has not been contructed yet, this returns `false, false`.
// This is solely for the benefit of the integration tests making sure feature gates get correctly parsed before AuthzSelector ever has to check for enablement.
func AuthzSelectorsLibraryEnabled() (enabled, constructed bool) {
enabled, constructed = authzSelectorsLibraryEnabled.Load().(bool)
return
}
var StrictCostOpt = VersionedOptions{
// This is to configure the cost calculation for extended libraries
IntroducedVersion: version.MajorMinor(1, 0),
ProgramOptions: []cel.ProgramOption{
cel.CostTracking(&library.CostEstimator{}),
},
}
// cacheBaseEnvs controls whether calls to MustBaseEnvSet are cached.
// Defaults to true, may be disabled by calling DisableBaseEnvSetCachingForTests.
var cacheBaseEnvs = true
// DisableBaseEnvSetCachingForTests clears and disables base env caching.
// This is only intended for unit tests exercising MustBaseEnvSet directly with different enablement options.
// It does not clear other initialization paths that may cache results of calling MustBaseEnvSet.
func DisableBaseEnvSetCachingForTests() {
cacheBaseEnvs = false
baseEnvs.Clear()
baseEnvsWithOption.Clear()
}
// MustBaseEnvSet returns the common CEL base environments for Kubernetes for Version, or panics
// if the version is nil, or does not have major and minor components.
//
// The returned environment contains function libraries, language settings, optimizations and
// runtime cost limits appropriate CEL as it is used in Kubernetes.
//
// The returned environment contains no CEL variable definitions or custom type declarations and
// should be extended to construct environments with the appropriate variable definitions,
// type declarations and any other needed configuration.
// strictCost is used to determine whether to enforce strict cost calculation for CEL expressions.
func MustBaseEnvSet(ver *version.Version, strictCost bool) *EnvSet {
if ver == nil {
panic("version must be non-nil")
}
if len(ver.Components()) < 2 {
panic(fmt.Sprintf("version must contain an major and minor component, but got: %s", ver.String()))
}
key := strconv.FormatUint(uint64(ver.Major()), 10) + "." + strconv.FormatUint(uint64(ver.Minor()), 10)
var entry interface{}
if strictCost {
if entry, ok := baseEnvs.Load(key); ok {
return entry.(*EnvSet)
}
entry, _, _ = baseEnvsSingleflight.Do(key, func() (interface{}, error) {
entry := mustNewEnvSet(ver, baseOpts)
if cacheBaseEnvs {
baseEnvs.Store(key, entry)
}
return entry, nil
})
} else {
if entry, ok := baseEnvsWithOption.Load(key); ok {
return entry.(*EnvSet)
}
entry, _, _ = baseEnvsWithOptionSingleflight.Do(key, func() (interface{}, error) {
entry := mustNewEnvSet(ver, baseOptsWithoutStrictCost)
if cacheBaseEnvs {
baseEnvsWithOption.Store(key, entry)
}
return entry, nil
})
}
return entry.(*EnvSet)
}
var (
baseEnvs = sync.Map{}
baseEnvsWithOption = sync.Map{}
baseEnvsSingleflight = &singleflight.Group{}
baseEnvsWithOptionSingleflight = &singleflight.Group{}
)
// UnversionedLib wraps library initialization calls like ext.Sets() or library.IP()
// to force compilation errors if the call evolves to include a varadic variable option.
//
// This provides automatic detection of a problem that is hard to catch in review--
// If a CEL library used in Kubernetes is unversioned and then become versioned, and we
// fail to set a desired version, the libraries defaults to the latest version, changing
// CEL environment without controlled rollout, bypassing the entire purpose of the base
// environment.
//
// If usages of this function fail to compile: add version=1 argument to all call sites
// that fail compilation while removing the UnversionedLib wrapper. Next, review
// the changes in the library present in higher versions and, if needed, use VersionedOptions to
// the base environment to roll out to a newer version safely.
func UnversionedLib(initializer func() cel.EnvOption) cel.EnvOption {
return initializer()
}

View File

@ -0,0 +1,298 @@
/*
Copyright 2023 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 environment
import (
"fmt"
"math"
"github.com/google/cel-go/cel"
"k8s.io/apimachinery/pkg/util/version"
apiservercel "k8s.io/apiserver/pkg/cel"
)
// Type defines the different types of CEL environments used in Kubernetes.
// CEL environments are used to compile and evaluate CEL expressions.
// Environments include:
// - Function libraries
// - Variables
// - Types (both core CEL types and Kubernetes types)
// - Other CEL environment and program options
type Type string
const (
// NewExpressions is used to validate new or modified expressions in
// requests that write expressions to API resources.
//
// This environment type is compatible with a specific Kubernetes
// major/minor version. To ensure safe rollback, this environment type
// may not include all the function libraries, variables, type declarations, and CEL
// language settings available in the StoredExpressions environment type.
//
// NewExpressions must be used to validate (parse, compile, type check)
// all new or modified CEL expressions before they are written to storage.
NewExpressions Type = "NewExpressions"
// StoredExpressions is used to compile and run CEL expressions that have been
// persisted to storage.
//
// This environment type is compatible with CEL expressions that have been
// persisted to storage by all known versions of Kubernetes. This is the most
// permissive environment available.
//
// StoredExpressions is appropriate for use with CEL expressions in
// configuration files.
StoredExpressions Type = "StoredExpressions"
)
// EnvSet manages the creation and extension of CEL environments. Each EnvSet contains
// both an NewExpressions and StoredExpressions environment. EnvSets are created
// and extended using VersionedOptions so that the EnvSet can prepare environments according
// to what options were introduced at which versions.
//
// Each EnvSet is given a compatibility version when it is created, and prepares the
// NewExpressions environment to be compatible with that version. The EnvSet also
// prepares StoredExpressions to be compatible with all known versions of Kubernetes.
type EnvSet struct {
// compatibilityVersion is the version that all configuration in
// the NewExpressions environment is compatible with.
compatibilityVersion *version.Version
// newExpressions is an environment containing only configuration
// in this EnvSet that is enabled at this compatibilityVersion.
newExpressions *cel.Env
// storedExpressions is an environment containing the latest configuration
// in this EnvSet.
storedExpressions *cel.Env
}
func newEnvSet(compatibilityVersion *version.Version, opts []VersionedOptions) (*EnvSet, error) {
base, err := cel.NewEnv()
if err != nil {
return nil, err
}
baseSet := EnvSet{compatibilityVersion: compatibilityVersion, newExpressions: base, storedExpressions: base}
return baseSet.Extend(opts...)
}
func mustNewEnvSet(ver *version.Version, opts []VersionedOptions) *EnvSet {
envSet, err := newEnvSet(ver, opts)
if err != nil {
panic(fmt.Sprintf("Default environment misconfigured: %v", err))
}
return envSet
}
// NewExpressionsEnv returns the NewExpressions environment Type for this EnvSet.
// See NewExpressions for details.
func (e *EnvSet) NewExpressionsEnv() *cel.Env {
return e.newExpressions
}
// StoredExpressionsEnv returns the StoredExpressions environment Type for this EnvSet.
// See StoredExpressions for details.
func (e *EnvSet) StoredExpressionsEnv() *cel.Env {
return e.storedExpressions
}
// Env returns the CEL environment for the given Type.
func (e *EnvSet) Env(envType Type) (*cel.Env, error) {
switch envType {
case NewExpressions:
return e.newExpressions, nil
case StoredExpressions:
return e.storedExpressions, nil
default:
return nil, fmt.Errorf("unsupported environment type: %v", envType)
}
}
// VersionedOptions provides a set of CEL configuration options as well as the version the
// options were introduced and, optionally, the version the options were removed.
type VersionedOptions struct {
// IntroducedVersion is the version at which these options were introduced.
// The NewExpressions environment will only include options introduced at or before the
// compatibility version of the EnvSet.
//
// For example, to configure a CEL environment with an "object" variable bound to a
// resource kind, first create a DeclType from the groupVersionKind of the resource and then
// populate a VersionedOptions with the variable and the type:
//
// schema := schemaResolver.ResolveSchema(groupVersionKind)
// objectType := apiservercel.SchemaDeclType(schema, true)
// ...
// VersionOptions{
// IntroducedVersion: version.MajorMinor(1, 26),
// DeclTypes: []*apiservercel.DeclType{ objectType },
// EnvOptions: []cel.EnvOption{ cel.Variable("object", objectType.CelType()) },
// },
//
// To create an DeclType from a CRD, use a structural schema. For example:
//
// schema := structuralschema.NewStructural(crdJSONProps)
// objectType := apiservercel.SchemaDeclType(schema, true)
//
// Required.
IntroducedVersion *version.Version
// RemovedVersion is the version at which these options were removed.
// The NewExpressions environment will not include options removed at or before the
// compatibility version of the EnvSet.
//
// All option removals must be backward compatible; the removal must either be paired
// with a compatible replacement introduced at the same version, or the removal must be non-breaking.
// The StoredExpressions environment will not include removed options.
//
// A function library may be upgraded by setting the RemovedVersion of the old library
// to the same value as the IntroducedVersion of the new library. The new library must
// be backward compatible with the old library.
//
// For example:
//
// VersionOptions{
// IntroducedVersion: version.MajorMinor(1, 26), RemovedVersion: version.MajorMinor(1, 27),
// EnvOptions: []cel.EnvOption{ libraries.Example(libraries.ExampleVersion(1)) },
// },
// VersionOptions{
// IntroducedVersion: version.MajorMinor(1, 27),
// EnvOptions: []EnvOptions{ libraries.Example(libraries.ExampleVersion(2)) },
// },
//
// Optional.
RemovedVersion *version.Version
// FeatureEnabled returns true if these options are enabled by feature gates,
// and returns false if these options are not enabled due to feature gates.
//
// This takes priority over IntroducedVersion / RemovedVersion for the NewExpressions environment.
//
// The StoredExpressions environment ignores this function.
//
// Optional.
FeatureEnabled func() bool
// EnvOptions provides CEL EnvOptions. This may be used to add a cel.Variable, a
// cel.Library, or to enable other CEL EnvOptions such as language settings.
//
// If an added cel.Variable has an OpenAPI type, the type must be included in DeclTypes.
EnvOptions []cel.EnvOption
// ProgramOptions provides CEL ProgramOptions. This may be used to set a cel.CostLimit,
// enable optimizations, and set other program level options that should be enabled
// for all programs using this environment.
ProgramOptions []cel.ProgramOption
// DeclTypes provides OpenAPI type declarations to register with the environment.
//
// If cel.Variables added to EnvOptions refer to a OpenAPI type, the type must be included in
// DeclTypes.
DeclTypes []*apiservercel.DeclType
}
// Extend returns an EnvSet based on this EnvSet but extended with given VersionedOptions.
// This EnvSet is not mutated.
// The returned EnvSet has the same compatibility version as the EnvSet that was extended.
//
// Extend is an expensive operation and each call to Extend that adds DeclTypes increases
// the depth of a chain of resolvers. For these reasons, calls to Extend should be kept
// to a minimum.
//
// Some best practices:
//
// - Minimize calls Extend when handling API requests. Where possible, call Extend
// when initializing components.
// - If an EnvSets returned by Extend can be used to compile multiple CEL programs,
// call Extend once and reuse the returned EnvSets.
// - Prefer a single call to Extend with a full list of VersionedOptions over
// making multiple calls to Extend.
func (e *EnvSet) Extend(options ...VersionedOptions) (*EnvSet, error) {
if len(options) > 0 {
newExprOpts, err := e.filterAndBuildOpts(e.newExpressions, e.compatibilityVersion, true, options)
if err != nil {
return nil, err
}
p, err := e.newExpressions.Extend(newExprOpts)
if err != nil {
return nil, err
}
storedExprOpt, err := e.filterAndBuildOpts(e.storedExpressions, version.MajorMinor(math.MaxUint, math.MaxUint), false, options)
if err != nil {
return nil, err
}
s, err := e.storedExpressions.Extend(storedExprOpt)
if err != nil {
return nil, err
}
return &EnvSet{compatibilityVersion: e.compatibilityVersion, newExpressions: p, storedExpressions: s}, nil
}
return e, nil
}
func (e *EnvSet) filterAndBuildOpts(base *cel.Env, compatVer *version.Version, honorFeatureGateEnablement bool, opts []VersionedOptions) (cel.EnvOption, error) {
var envOpts []cel.EnvOption
var progOpts []cel.ProgramOption
var declTypes []*apiservercel.DeclType
for _, opt := range opts {
var allowedByFeatureGate, allowedByVersion bool
if opt.FeatureEnabled != nil && honorFeatureGateEnablement {
// Feature-gate-enabled libraries must follow compatible default feature enablement.
// Enabling alpha features in their first release enables libraries the previous API server is unaware of.
allowedByFeatureGate = opt.FeatureEnabled()
if !allowedByFeatureGate {
continue
}
}
if compatVer.AtLeast(opt.IntroducedVersion) && (opt.RemovedVersion == nil || compatVer.LessThan(opt.RemovedVersion)) {
allowedByVersion = true
}
if allowedByFeatureGate || allowedByVersion {
envOpts = append(envOpts, opt.EnvOptions...)
progOpts = append(progOpts, opt.ProgramOptions...)
declTypes = append(declTypes, opt.DeclTypes...)
}
}
if len(declTypes) > 0 {
provider := apiservercel.NewDeclTypeProvider(declTypes...)
if compatVer.AtLeast(version.MajorMinor(1, 31)) {
provider.SetRecognizeKeywordAsFieldName(true)
}
providerOpts, err := provider.EnvOptions(base.CELTypeProvider())
if err != nil {
return nil, err
}
envOpts = append(envOpts, providerOpts...)
}
combined := cel.Lib(&envLoader{
envOpts: envOpts,
progOpts: progOpts,
})
return combined, nil
}
type envLoader struct {
envOpts []cel.EnvOption
progOpts []cel.ProgramOption
}
func (e *envLoader) CompileOptions() []cel.EnvOption {
return e.envOpts
}
func (e *envLoader) ProgramOptions() []cel.ProgramOption {
return e.progOpts
}

124
e2e/vendor/k8s.io/apiserver/pkg/cel/errors.go generated vendored Normal file
View File

@ -0,0 +1,124 @@
/*
Copyright 2021 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 (
"fmt"
"github.com/google/cel-go/cel"
)
// ErrInternal the basic error that occurs when the expression fails to evaluate
// due to internal reasons. Any Error that has the Type of
// ErrorInternal is considered equal to ErrInternal
var ErrInternal = fmt.Errorf("internal")
// ErrInvalid is the basic error that occurs when the expression fails to
// evaluate but not due to internal reasons. Any Error that has the Type of
// ErrorInvalid is considered equal to ErrInvalid.
var ErrInvalid = fmt.Errorf("invalid")
// ErrRequired is the basic error that occurs when the expression is required
// but absent.
// Any Error that has the Type of ErrorRequired is considered equal
// to ErrRequired.
var ErrRequired = fmt.Errorf("required")
// ErrCompilation is the basic error that occurs when the expression fails to
// compile. Any CompilationError wraps ErrCompilation.
// ErrCompilation wraps ErrInvalid
var ErrCompilation = fmt.Errorf("%w: compilation error", ErrInvalid)
// ErrOutOfBudget is the basic error that occurs when the expression fails due to
// exceeding budget.
var ErrOutOfBudget = fmt.Errorf("out of budget")
// Error is an implementation of the 'error' interface, which represents a
// XValidation error.
type Error struct {
Type ErrorType
Detail string
// Cause is an optional wrapped errors that can be useful to
// programmatically retrieve detailed errors.
Cause error
}
var _ error = &Error{}
// Error implements the error interface.
func (v *Error) Error() string {
return v.Detail
}
func (v *Error) Is(err error) bool {
switch v.Type {
case ErrorTypeRequired:
return err == ErrRequired
case ErrorTypeInvalid:
return err == ErrInvalid
case ErrorTypeInternal:
return err == ErrInternal
}
return false
}
// Unwrap returns the wrapped Cause.
func (v *Error) Unwrap() error {
return v.Cause
}
// ErrorType is a machine-readable value providing more detail about why
// a XValidation is invalid.
type ErrorType string
const (
// ErrorTypeRequired is used to report withNullable values that are not
// provided (e.g. empty strings, null values, or empty arrays). See
// Required().
ErrorTypeRequired ErrorType = "RuleRequired"
// ErrorTypeInvalid is used to report malformed values
ErrorTypeInvalid ErrorType = "RuleInvalid"
// ErrorTypeInternal is used to report other errors that are not related
// to user input. See InternalError().
ErrorTypeInternal ErrorType = "InternalError"
)
// CompilationError indicates an error during expression compilation.
// It wraps ErrCompilation.
type CompilationError struct {
err *Error
Issues *cel.Issues
}
// NewCompilationError wraps a cel.Issues to indicate a compilation failure.
func NewCompilationError(issues *cel.Issues) *CompilationError {
return &CompilationError{
Issues: issues,
err: &Error{
Type: ErrorTypeInvalid,
Detail: fmt.Sprintf("compilation error: %s", issues),
}}
}
func (e *CompilationError) Error() string {
return e.err.Error()
}
func (e *CompilationError) Unwrap() []error {
return []error{e.err, ErrCompilation}
}

170
e2e/vendor/k8s.io/apiserver/pkg/cel/escaping.go generated vendored Normal file
View File

@ -0,0 +1,170 @@
/*
Copyright 2021 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 (
"regexp"
"k8s.io/apimachinery/pkg/util/sets"
)
// celReservedSymbols is a list of RESERVED symbols defined in the CEL lexer.
// No identifiers are allowed to collide with these symbols.
// https://github.com/google/cel-spec/blob/master/doc/langdef.md#syntax
var celReservedSymbols = sets.NewString(
"true", "false", "null", "in",
"as", "break", "const", "continue", "else",
"for", "function", "if", "import", "let",
"loop", "package", "namespace", "return", // !! 'namespace' is used heavily in Kubernetes
"var", "void", "while",
)
// expandMatcher matches the escape sequence, characters that are escaped, and characters that are unsupported
var expandMatcher = regexp.MustCompile(`(__|[-./]|[^a-zA-Z0-9-./_])`)
// newCharacterFilter returns a boolean array to indicate the allowed characters
func newCharacterFilter(characters string) []bool {
maxChar := 0
for _, c := range characters {
if maxChar < int(c) {
maxChar = int(c)
}
}
filter := make([]bool, maxChar+1)
for _, c := range characters {
filter[int(c)] = true
}
return filter
}
type escapeCheck struct {
canSkipRegex bool
invalidCharFound bool
}
// skipRegexCheck checks if escape would be skipped.
// if invalidCharFound is true, it must have invalid character; if invalidCharFound is false, not sure if it has invalid character or not
func skipRegexCheck(ident string) escapeCheck {
escapeCheck := escapeCheck{canSkipRegex: true, invalidCharFound: false}
// skip escape if possible
previous_underscore := false
for _, c := range ident {
if c == '/' || c == '-' || c == '.' {
escapeCheck.canSkipRegex = false
return escapeCheck
}
intc := int(c)
if intc < 0 || intc >= len(validCharacterFilter) || !validCharacterFilter[intc] {
escapeCheck.invalidCharFound = true
return escapeCheck
}
if c == '_' && previous_underscore {
escapeCheck.canSkipRegex = false
return escapeCheck
}
previous_underscore = c == '_'
}
return escapeCheck
}
// validCharacterFilter indicates the allowed characters.
var validCharacterFilter = newCharacterFilter("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_")
// Escape escapes ident and returns a CEL identifier (of the form '[a-zA-Z_][a-zA-Z0-9_]*'), or returns
// false if the ident does not match the supported input format of `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*`.
// Escaping Rules:
// - '__' escapes to '__underscores__'
// - '.' escapes to '__dot__'
// - '-' escapes to '__dash__'
// - '/' escapes to '__slash__'
// - Identifiers that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are: "true", "false",
// "null", "in", "as", "break", "const", "continue", "else", "for", "function", "if", "import", "let", loop", "package",
// "namespace", "return".
func Escape(ident string) (string, bool) {
if len(ident) == 0 || ('0' <= ident[0] && ident[0] <= '9') {
return "", false
}
if celReservedSymbols.Has(ident) {
return "__" + ident + "__", true
}
escapeCheck := skipRegexCheck(ident)
if escapeCheck.invalidCharFound {
return "", false
}
if escapeCheck.canSkipRegex {
return ident, true
}
ok := true
ident = expandMatcher.ReplaceAllStringFunc(ident, func(s string) string {
switch s {
case "__":
return "__underscores__"
case ".":
return "__dot__"
case "-":
return "__dash__"
case "/":
return "__slash__"
default: // matched a unsupported supported
ok = false
return ""
}
})
if !ok {
return "", false
}
return ident, true
}
var unexpandMatcher = regexp.MustCompile(`(_{2}[^_]+_{2})`)
// Unescape unescapes an CEL identifier containing the escape sequences described in Escape, or return false if the
// string contains invalid escape sequences. The escaped input is expected to be a valid CEL identifier, but is
// not checked.
func Unescape(escaped string) (string, bool) {
ok := true
escaped = unexpandMatcher.ReplaceAllStringFunc(escaped, func(s string) string {
contents := s[2 : len(s)-2]
switch contents {
case "underscores":
return "__"
case "dot":
return "."
case "dash":
return "-"
case "slash":
return "/"
}
if celReservedSymbols.Has(contents) {
if len(s) != len(escaped) {
ok = false
}
return contents
}
ok = false
return ""
})
if !ok {
return "", false
}
return escaped, true
}

73
e2e/vendor/k8s.io/apiserver/pkg/cel/format.go generated vendored Normal file
View File

@ -0,0 +1,73 @@
/*
Copyright 2024 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 (
"fmt"
"reflect"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/checker/decls"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
)
var (
FormatObject = decls.NewObjectType("kubernetes.NamedFormat")
FormatType = cel.ObjectType("kubernetes.NamedFormat")
)
// Format provdes a CEL representation of kubernetes format
type Format struct {
Name string
ValidateFunc func(string) []string
// Size of the regex string or estimated equivalent regex string used
// for cost estimation
MaxRegexSize int
}
func (d Format) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
return nil, fmt.Errorf("type conversion error from 'Format' to '%v'", typeDesc)
}
func (d Format) ConvertToType(typeVal ref.Type) ref.Val {
switch typeVal {
case FormatType:
return d
case types.TypeType:
return FormatType
default:
return types.NewErr("type conversion error from '%s' to '%s'", FormatType, typeVal)
}
}
func (d Format) Equal(other ref.Val) ref.Val {
otherDur, ok := other.(Format)
if !ok {
return types.MaybeNoSuchOverloadErr(other)
}
return types.Bool(d.Name == otherDur.Name)
}
func (d Format) Type() ref.Type {
return FormatType
}
func (d Format) Value() interface{} {
return d
}

86
e2e/vendor/k8s.io/apiserver/pkg/cel/ip.go generated vendored Normal file
View File

@ -0,0 +1,86 @@
/*
Copyright 2023 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 (
"fmt"
"math"
"net/netip"
"reflect"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
)
// IP provides a CEL representation of an IP address.
type IP struct {
netip.Addr
}
var (
IPType = cel.OpaqueType("net.IP")
)
// ConvertToNative implements ref.Val.ConvertToNative.
func (d IP) ConvertToNative(typeDesc reflect.Type) (any, error) {
if reflect.TypeOf(d.Addr).AssignableTo(typeDesc) {
return d.Addr, nil
}
if reflect.TypeOf("").AssignableTo(typeDesc) {
return d.Addr.String(), nil
}
return nil, fmt.Errorf("type conversion error from 'IP' to '%v'", typeDesc)
}
// ConvertToType implements ref.Val.ConvertToType.
func (d IP) ConvertToType(typeVal ref.Type) ref.Val {
switch typeVal {
case IPType:
return d
case types.TypeType:
return IPType
case types.StringType:
return types.String(d.Addr.String())
}
return types.NewErr("type conversion error from '%s' to '%s'", IPType, typeVal)
}
// Equal implements ref.Val.Equal.
func (d IP) Equal(other ref.Val) ref.Val {
otherD, ok := other.(IP)
if !ok {
return types.ValOrErr(other, "no such overload")
}
return types.Bool(d.Addr == otherD.Addr)
}
// Type implements ref.Val.Type.
func (d IP) Type() ref.Type {
return IPType
}
// Value implements ref.Val.Value.
func (d IP) Value() any {
return d.Addr
}
// Size returns the size of the IP address in bytes.
// Used in the size estimation of the runtime cost.
func (d IP) Size() ref.Val {
return types.Int(int(math.Ceil(float64(d.Addr.BitLen()) / 8)))
}

191
e2e/vendor/k8s.io/apiserver/pkg/cel/lazy/lazy.go generated vendored Normal file
View File

@ -0,0 +1,191 @@
/*
Copyright 2023 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 lazy
import (
"fmt"
"reflect"
"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/apiserver/pkg/cel"
)
type GetFieldFunc func(*MapValue) ref.Val
var _ ref.Val = (*MapValue)(nil)
var _ traits.Mapper = (*MapValue)(nil)
// MapValue is a map that lazily evaluate its value when a field is first accessed.
// The map value is not designed to be thread-safe.
type MapValue struct {
typeValue *types.Type
// values are previously evaluated values obtained from callbacks
values map[string]ref.Val
// callbacks are a map of field name to the function that returns the field Val
callbacks map[string]GetFieldFunc
// knownValues are registered names, used for iteration
knownValues []string
}
func NewMapValue(objectType ref.Type) *MapValue {
return &MapValue{
typeValue: types.NewTypeValue(objectType.TypeName(), traits.IndexerType|traits.FieldTesterType|traits.IterableType),
values: map[string]ref.Val{},
callbacks: map[string]GetFieldFunc{},
}
}
// Append adds the given field with its name and callback.
func (m *MapValue) Append(name string, callback GetFieldFunc) {
m.knownValues = append(m.knownValues, name)
m.callbacks[name] = callback
}
// Contains checks if the key is known to the map
func (m *MapValue) Contains(key ref.Val) ref.Val {
v, found := m.Find(key)
if v != nil && types.IsUnknownOrError(v) {
return v
}
return types.Bool(found)
}
// Iterator returns an iterator to traverse the map.
func (m *MapValue) Iterator() traits.Iterator {
return &iterator{parent: m, index: 0}
}
// Size returns the number of currently known fields
func (m *MapValue) Size() ref.Val {
return types.Int(len(m.callbacks))
}
// ConvertToNative returns an error because it is disallowed
func (m *MapValue) ConvertToNative(typeDesc reflect.Type) (any, error) {
return nil, fmt.Errorf("disallowed conversion from %q to %q", m.typeValue.TypeName(), typeDesc.Name())
}
// ConvertToType converts the map to the given type.
// Only its own type and "Type" type are allowed.
func (m *MapValue) ConvertToType(typeVal ref.Type) ref.Val {
switch typeVal {
case m.typeValue:
return m
case types.TypeType:
return m.typeValue
}
return types.NewErr("disallowed conversion from %q to %q", m.typeValue.TypeName(), typeVal.TypeName())
}
// Equal returns true if the other object is the same pointer-wise.
func (m *MapValue) Equal(other ref.Val) ref.Val {
otherMap, ok := other.(*MapValue)
if !ok {
return types.MaybeNoSuchOverloadErr(other)
}
return types.Bool(m == otherMap)
}
// Type returns its registered type.
func (m *MapValue) Type() ref.Type {
return m.typeValue
}
// Value is not allowed.
func (m *MapValue) Value() any {
return types.NoSuchOverloadErr()
}
// resolveField resolves the field. Calls the callback if the value is not yet stored.
func (m *MapValue) resolveField(name string) ref.Val {
v, seen := m.values[name]
if seen {
return v
}
f := m.callbacks[name]
v = f(m)
m.values[name] = v
return v
}
func (m *MapValue) Find(key ref.Val) (ref.Val, bool) {
n, ok := key.(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(n), true
}
name, ok := cel.Unescape(n.Value().(string))
if !ok {
return nil, false
}
if _, exists := m.callbacks[name]; !exists {
return nil, false
}
return m.resolveField(name), true
}
func (m *MapValue) Get(key ref.Val) ref.Val {
v, found := m.Find(key)
if found {
return v
}
return types.ValOrErr(key, "no such key: %v", key)
}
type iterator struct {
parent *MapValue
index int
}
func (i *iterator) ConvertToNative(typeDesc reflect.Type) (any, error) {
return nil, fmt.Errorf("disallowed conversion to %q", typeDesc.Name())
}
func (i *iterator) ConvertToType(typeValue ref.Type) ref.Val {
return types.NewErr("disallowed conversion o %q", typeValue.TypeName())
}
func (i *iterator) Equal(other ref.Val) ref.Val {
otherIterator, ok := other.(*iterator)
if !ok {
return types.MaybeNoSuchOverloadErr(other)
}
return types.Bool(otherIterator == i)
}
func (i *iterator) Type() ref.Type {
return types.IteratorType
}
func (i *iterator) Value() any {
return nil
}
func (i *iterator) HasNext() ref.Val {
return types.Bool(i.index < len(i.parent.knownValues))
}
func (i *iterator) Next() ref.Val {
ret := i.parent.Get(types.String(i.parent.knownValues[i.index]))
i.index++
return ret
}
var _ traits.Iterator = (*iterator)(nil)

790
e2e/vendor/k8s.io/apiserver/pkg/cel/library/authz.go generated vendored Normal file
View File

@ -0,0 +1,790 @@
/*
Copyright 2023 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 library
import (
"context"
"fmt"
"reflect"
"strings"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
genericfeatures "k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authentication/serviceaccount"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
)
// Authz provides a CEL function library extension for performing authorization checks.
// Note that authorization checks are only supported for CEL expression fields in the API
// where an 'authorizer' variable is provided to the CEL expression. See the
// documentation of API fields where CEL expressions are used to learn if the 'authorizer'
// variable is provided.
//
// path
//
// Returns a PathCheck configured to check authorization for a non-resource request
// path (e.g. /healthz). If path is an empty string, an error is returned.
// Note that the leading '/' is not required.
//
// <Authorizer>.path(<string>) <PathCheck>
//
// Examples:
//
// authorizer.path('/healthz') // returns a PathCheck for the '/healthz' API path
// authorizer.path('') // results in "path must not be empty" error
// authorizer.path(' ') // results in "path must not be empty" error
//
// group
//
// Returns a GroupCheck configured to check authorization for the API resources for
// a particular API group.
// Note that authorization checks are only supported for CEL expression fields in the API
// where an 'authorizer' variable is provided to the CEL expression. Check the
// documentation of API fields where CEL expressions are used to learn if the 'authorizer'
// variable is provided.
//
// <Authorizer>.group(<string>) <GroupCheck>
//
// Examples:
//
// authorizer.group('apps') // returns a GroupCheck for the 'apps' API group
// authorizer.group('') // returns a GroupCheck for the core API group
// authorizer.group('example.com') // returns a GroupCheck for the custom resources in the 'example.com' API group
//
// serviceAccount
//
// Returns an Authorizer configured to check authorization for the provided service account namespace and name.
// If the name is not a valid DNS subdomain string (as defined by RFC 1123), an error is returned.
// If the namespace is not a valid DNS label (as defined by RFC 1123), an error is returned.
//
// <Authorizer>.serviceAccount(<string>, <string>) <Authorizer>
//
// Examples:
//
// authorizer.serviceAccount('default', 'myserviceaccount') // returns an Authorizer for the service account with namespace 'default' and name 'myserviceaccount'
// authorizer.serviceAccount('not@a#valid!namespace', 'validname') // returns an error
// authorizer.serviceAccount('valid.example.com', 'invalid@*name') // returns an error
//
// resource
//
// Returns a ResourceCheck configured to check authorization for a particular API resource.
// Note that the provided resource string should be a lower case plural name of a Kubernetes API resource.
//
// <GroupCheck>.resource(<string>) <ResourceCheck>
//
// Examples:
//
// authorizer.group('apps').resource('deployments') // returns a ResourceCheck for the 'deployments' resources in the 'apps' group.
// authorizer.group('').resource('pods') // returns a ResourceCheck for the 'pods' resources in the core group.
// authorizer.group('apps').resource('') // results in "resource must not be empty" error
// authorizer.group('apps').resource(' ') // results in "resource must not be empty" error
//
// subresource
//
// Returns a ResourceCheck configured to check authorization for a particular subresource of an API resource.
// If subresource is set to "", the subresource field of this ResourceCheck is considered unset.
//
// <ResourceCheck>.subresource(<string>) <ResourceCheck>
//
// Examples:
//
// authorizer.group('').resource('pods').subresource('status') // returns a ResourceCheck the 'status' subresource of 'pods'
// authorizer.group('apps').resource('deployments').subresource('scale') // returns a ResourceCheck the 'scale' subresource of 'deployments'
// authorizer.group('example.com').resource('widgets').subresource('scale') // returns a ResourceCheck for the 'scale' subresource of the 'widgets' custom resource
// authorizer.group('example.com').resource('widgets').subresource('') // returns a ResourceCheck for the 'widgets' resource.
//
// namespace
//
// Returns a ResourceCheck configured to check authorization for a particular namespace.
// For cluster scoped resources, namespace() does not need to be called; namespace defaults
// to "", which is the correct namespace value to use to check cluster scoped resources.
// If namespace is set to "", the ResourceCheck will check authorization for the cluster scope.
//
// <ResourceCheck>.namespace(<string>) <ResourceCheck>
//
// Examples:
//
// authorizer.group('apps').resource('deployments').namespace('test') // returns a ResourceCheck for 'deployments' in the 'test' namespace
// authorizer.group('').resource('pods').namespace('default') // returns a ResourceCheck for 'pods' in the 'default' namespace
// authorizer.group('').resource('widgets').namespace('') // returns a ResourceCheck for 'widgets' in the cluster scope
//
// name
//
// Returns a ResourceCheck configured to check authorization for a particular resource name.
// If name is set to "", the name field of this ResourceCheck is considered unset.
//
// <ResourceCheck>.name(<name>) <ResourceCheck>
//
// Examples:
//
// authorizer.group('apps').resource('deployments').namespace('test').name('backend') // returns a ResourceCheck for the 'backend' 'deployments' resource in the 'test' namespace
// authorizer.group('apps').resource('deployments').namespace('test').name('') // returns a ResourceCheck for the 'deployments' resource in the 'test' namespace
//
// check
//
// For PathCheck, checks if the principal (user or service account) that sent the request is authorized for the HTTP request verb of the path.
// For ResourceCheck, checks if the principal (user or service account) that sent the request is authorized for the API verb and the configured authorization checks of the ResourceCheck.
// The check operation can be expensive, particularly in clusters using the webhook authorization mode.
//
// <PathCheck>.check(<check>) <Decision>
// <ResourceCheck>.check(<check>) <Decision>
//
// Examples:
//
// authorizer.group('').resource('pods').namespace('default').check('create') // Checks if the principal (user or service account) is authorized create pods in the 'default' namespace.
// authorizer.path('/healthz').check('get') // Checks if the principal (user or service account) is authorized to make HTTP GET requests to the /healthz API path.
//
// allowed
//
// Returns true if the authorizer's decision for the check is "allow". Note that if the authorizer's decision is
// "no opinion", that the 'allowed' function will return false.
//
// <Decision>.allowed() <bool>
//
// Examples:
//
// authorizer.group('').resource('pods').namespace('default').check('create').allowed() // Returns true if the principal (user or service account) is allowed create pods in the 'default' namespace.
// authorizer.path('/healthz').check('get').allowed() // Returns true if the principal (user or service account) is allowed to make HTTP GET requests to the /healthz API path.
//
// reason
//
// Returns a string reason for the authorization decision
//
// <Decision>.reason() <string>
//
// Examples:
//
// authorizer.path('/healthz').check('GET').reason()
//
// errored
//
// Returns true if the authorization check resulted in an error.
//
// <Decision>.errored() <bool>
//
// Examples:
//
// authorizer.group('').resource('pods').namespace('default').check('create').errored() // Returns true if the authorization check resulted in an error
//
// error
//
// If the authorization check resulted in an error, returns the error. Otherwise, returns the empty string.
//
// <Decision>.error() <string>
//
// Examples:
//
// authorizer.group('').resource('pods').namespace('default').check('create').error()
//
// fieldSelector
//
// Takes a string field selector, parses it to field selector requirements, and includes it in the authorization check.
// If the field selector does not parse successfully, no field selector requirements are included in the authorization check.
// Added in Kubernetes 1.31+, Authz library version 1.
//
// <ResourceCheck>.fieldSelector(<string>) <ResourceCheck>
//
// Examples:
//
// authorizer.group('').resource('pods').fieldSelector('spec.nodeName=mynode').check('list').allowed()
//
// labelSelector (added in v1, Kubernetes 1.31+)
//
// Takes a string label selector, parses it to label selector requirements, and includes it in the authorization check.
// If the label selector does not parse successfully, no label selector requirements are included in the authorization check.
// Added in Kubernetes 1.31+, Authz library version 1.
//
// <ResourceCheck>.labelSelector(<string>) <ResourceCheck>
//
// Examples:
//
// authorizer.group('').resource('pods').labelSelector('app=example').check('list').allowed()
func Authz() cel.EnvOption {
return cel.Lib(authzLib)
}
var authzLib = &authz{}
type authz struct{}
func (*authz) LibraryName() string {
return "kubernetes.authz"
}
func (*authz) Types() []*cel.Type {
return []*cel.Type{
AuthorizerType,
PathCheckType,
GroupCheckType,
ResourceCheckType,
DecisionType}
}
func (*authz) declarations() map[string][]cel.FunctionOpt {
return authzLibraryDecls
}
var authzLibraryDecls = map[string][]cel.FunctionOpt{
"path": {
cel.MemberOverload("authorizer_path", []*cel.Type{AuthorizerType, cel.StringType}, PathCheckType,
cel.BinaryBinding(authorizerPath))},
"group": {
cel.MemberOverload("authorizer_group", []*cel.Type{AuthorizerType, cel.StringType}, GroupCheckType,
cel.BinaryBinding(authorizerGroup))},
"serviceAccount": {
cel.MemberOverload("authorizer_serviceaccount", []*cel.Type{AuthorizerType, cel.StringType, cel.StringType}, AuthorizerType,
cel.FunctionBinding(authorizerServiceAccount))},
"resource": {
cel.MemberOverload("groupcheck_resource", []*cel.Type{GroupCheckType, cel.StringType}, ResourceCheckType,
cel.BinaryBinding(groupCheckResource))},
"subresource": {
cel.MemberOverload("resourcecheck_subresource", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
cel.BinaryBinding(resourceCheckSubresource))},
"namespace": {
cel.MemberOverload("resourcecheck_namespace", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
cel.BinaryBinding(resourceCheckNamespace))},
"name": {
cel.MemberOverload("resourcecheck_name", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
cel.BinaryBinding(resourceCheckName))},
"check": {
cel.MemberOverload("pathcheck_check", []*cel.Type{PathCheckType, cel.StringType}, DecisionType,
cel.BinaryBinding(pathCheckCheck)),
cel.MemberOverload("resourcecheck_check", []*cel.Type{ResourceCheckType, cel.StringType}, DecisionType,
cel.BinaryBinding(resourceCheckCheck))},
"errored": {
cel.MemberOverload("decision_errored", []*cel.Type{DecisionType}, cel.BoolType,
cel.UnaryBinding(decisionErrored))},
"error": {
cel.MemberOverload("decision_error", []*cel.Type{DecisionType}, cel.StringType,
cel.UnaryBinding(decisionError))},
"allowed": {
cel.MemberOverload("decision_allowed", []*cel.Type{DecisionType}, cel.BoolType,
cel.UnaryBinding(decisionAllowed))},
"reason": {
cel.MemberOverload("decision_reason", []*cel.Type{DecisionType}, cel.StringType,
cel.UnaryBinding(decisionReason))},
}
func (*authz) CompileOptions() []cel.EnvOption {
options := make([]cel.EnvOption, 0, len(authzLibraryDecls))
for name, overloads := range authzLibraryDecls {
options = append(options, cel.Function(name, overloads...))
}
return options
}
func (*authz) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{}
}
// AuthzSelectors provides a CEL function library extension for adding fieldSelector and
// labelSelector filters to authorization checks. This requires the Authz library.
// See documentation of the Authz library for use and availability of the authorizer variable.
//
// fieldSelector
//
// Takes a string field selector, parses it to field selector requirements, and includes it in the authorization check.
// If the field selector does not parse successfully, no field selector requirements are included in the authorization check.
// Added in Kubernetes 1.31+.
//
// <ResourceCheck>.fieldSelector(<string>) <ResourceCheck>
//
// Examples:
//
// authorizer.group('').resource('pods').fieldSelector('spec.nodeName=mynode').check('list').allowed()
//
// labelSelector
//
// Takes a string label selector, parses it to label selector requirements, and includes it in the authorization check.
// If the label selector does not parse successfully, no label selector requirements are included in the authorization check.
// Added in Kubernetes 1.31+.
//
// <ResourceCheck>.labelSelector(<string>) <ResourceCheck>
//
// Examples:
//
// authorizer.group('').resource('pods').labelSelector('app=example').check('list').allowed()
func AuthzSelectors() cel.EnvOption {
return cel.Lib(authzSelectorsLib)
}
var authzSelectorsLib = &authzSelectors{}
type authzSelectors struct{}
func (*authzSelectors) LibraryName() string {
return "kubernetes.authzSelectors"
}
func (*authzSelectors) Types() []*cel.Type {
return []*cel.Type{ResourceCheckType}
}
func (*authzSelectors) declarations() map[string][]cel.FunctionOpt {
return authzSelectorsLibraryDecls
}
var authzSelectorsLibraryDecls = map[string][]cel.FunctionOpt{
"fieldSelector": {
cel.MemberOverload("authorizer_fieldselector", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
cel.BinaryBinding(resourceCheckFieldSelector))},
"labelSelector": {
cel.MemberOverload("authorizer_labelselector", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
cel.BinaryBinding(resourceCheckLabelSelector))},
}
func (*authzSelectors) CompileOptions() []cel.EnvOption {
options := make([]cel.EnvOption, 0, len(authzSelectorsLibraryDecls))
for name, overloads := range authzSelectorsLibraryDecls {
options = append(options, cel.Function(name, overloads...))
}
return options
}
func (*authzSelectors) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{}
}
func authorizerPath(arg1, arg2 ref.Val) ref.Val {
authz, ok := arg1.(authorizerVal)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
path, ok := arg2.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
if len(strings.TrimSpace(path)) == 0 {
return types.NewErr("path must not be empty")
}
return authz.pathCheck(path)
}
func authorizerGroup(arg1, arg2 ref.Val) ref.Val {
authz, ok := arg1.(authorizerVal)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
group, ok := arg2.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
return authz.groupCheck(group)
}
func authorizerServiceAccount(args ...ref.Val) ref.Val {
argn := len(args)
if argn != 3 {
return types.NoSuchOverloadErr()
}
authz, ok := args[0].(authorizerVal)
if !ok {
return types.MaybeNoSuchOverloadErr(args[0])
}
namespace, ok := args[1].Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(args[1])
}
name, ok := args[2].Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(args[2])
}
if errors := apimachineryvalidation.ValidateServiceAccountName(name, false); len(errors) > 0 {
return types.NewErr("Invalid service account name")
}
if errors := apimachineryvalidation.ValidateNamespaceName(namespace, false); len(errors) > 0 {
return types.NewErr("Invalid service account namespace")
}
return authz.serviceAccount(namespace, name)
}
func groupCheckResource(arg1, arg2 ref.Val) ref.Val {
groupCheck, ok := arg1.(groupCheckVal)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
resource, ok := arg2.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
if len(strings.TrimSpace(resource)) == 0 {
return types.NewErr("resource must not be empty")
}
return groupCheck.resourceCheck(resource)
}
func resourceCheckSubresource(arg1, arg2 ref.Val) ref.Val {
resourceCheck, ok := arg1.(resourceCheckVal)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
subresource, ok := arg2.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
result := resourceCheck
result.subresource = subresource
return result
}
func resourceCheckFieldSelector(arg1, arg2 ref.Val) ref.Val {
resourceCheck, ok := arg1.(resourceCheckVal)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
fieldSelector, ok := arg2.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
result := resourceCheck
result.fieldSelector = fieldSelector
return result
}
func resourceCheckLabelSelector(arg1, arg2 ref.Val) ref.Val {
resourceCheck, ok := arg1.(resourceCheckVal)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
labelSelector, ok := arg2.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
result := resourceCheck
result.labelSelector = labelSelector
return result
}
func resourceCheckNamespace(arg1, arg2 ref.Val) ref.Val {
resourceCheck, ok := arg1.(resourceCheckVal)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
namespace, ok := arg2.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
result := resourceCheck
result.namespace = namespace
return result
}
func resourceCheckName(arg1, arg2 ref.Val) ref.Val {
resourceCheck, ok := arg1.(resourceCheckVal)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
name, ok := arg2.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
result := resourceCheck
result.name = name
return result
}
func pathCheckCheck(arg1, arg2 ref.Val) ref.Val {
pathCheck, ok := arg1.(pathCheckVal)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
httpRequestVerb, ok := arg2.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
return pathCheck.Authorize(context.TODO(), httpRequestVerb)
}
func resourceCheckCheck(arg1, arg2 ref.Val) ref.Val {
resourceCheck, ok := arg1.(resourceCheckVal)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
apiVerb, ok := arg2.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
return resourceCheck.Authorize(context.TODO(), apiVerb)
}
func decisionErrored(arg ref.Val) ref.Val {
decision, ok := arg.(decisionVal)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Bool(decision.err != nil)
}
func decisionError(arg ref.Val) ref.Val {
decision, ok := arg.(decisionVal)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
if decision.err == nil {
return types.String("")
}
return types.String(decision.err.Error())
}
func decisionAllowed(arg ref.Val) ref.Val {
decision, ok := arg.(decisionVal)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Bool(decision.authDecision == authorizer.DecisionAllow)
}
func decisionReason(arg ref.Val) ref.Val {
decision, ok := arg.(decisionVal)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.String(decision.reason)
}
var (
AuthorizerType = cel.ObjectType("kubernetes.authorization.Authorizer")
PathCheckType = cel.ObjectType("kubernetes.authorization.PathCheck")
GroupCheckType = cel.ObjectType("kubernetes.authorization.GroupCheck")
ResourceCheckType = cel.ObjectType("kubernetes.authorization.ResourceCheck")
DecisionType = cel.ObjectType("kubernetes.authorization.Decision")
)
// Resource represents an API resource
type Resource interface {
// GetName returns the name of the object as presented in the request. On a CREATE operation, the client
// may omit name and rely on the server to generate the name. If that is the case, this method will return
// the empty string
GetName() string
// GetNamespace is the namespace associated with the request (if any)
GetNamespace() string
// GetResource is the name of the resource being requested. This is not the kind. For example: pods
GetResource() schema.GroupVersionResource
// GetSubresource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind.
// For instance, /pods has the resource "pods" and the kind "Pod", while /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod"
// (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource "binding", and kind "Binding".
GetSubresource() string
}
func NewAuthorizerVal(userInfo user.Info, authorizer authorizer.Authorizer) ref.Val {
return authorizerVal{receiverOnlyObjectVal: receiverOnlyVal(AuthorizerType), userInfo: userInfo, authAuthorizer: authorizer}
}
func NewResourceAuthorizerVal(userInfo user.Info, authorizer authorizer.Authorizer, requestResource Resource) ref.Val {
a := authorizerVal{receiverOnlyObjectVal: receiverOnlyVal(AuthorizerType), userInfo: userInfo, authAuthorizer: authorizer}
resource := requestResource.GetResource()
g := a.groupCheck(resource.Group)
r := g.resourceCheck(resource.Resource)
r.subresource = requestResource.GetSubresource()
r.namespace = requestResource.GetNamespace()
r.name = requestResource.GetName()
return r
}
type authorizerVal struct {
receiverOnlyObjectVal
userInfo user.Info
authAuthorizer authorizer.Authorizer
}
func (a authorizerVal) pathCheck(path string) pathCheckVal {
return pathCheckVal{receiverOnlyObjectVal: receiverOnlyVal(PathCheckType), authorizer: a, path: path}
}
func (a authorizerVal) groupCheck(group string) groupCheckVal {
return groupCheckVal{receiverOnlyObjectVal: receiverOnlyVal(GroupCheckType), authorizer: a, group: group}
}
func (a authorizerVal) serviceAccount(namespace, name string) authorizerVal {
sa := &serviceaccount.ServiceAccountInfo{Name: name, Namespace: namespace}
return authorizerVal{
receiverOnlyObjectVal: receiverOnlyVal(AuthorizerType),
userInfo: sa.UserInfo(),
authAuthorizer: a.authAuthorizer,
}
}
type pathCheckVal struct {
receiverOnlyObjectVal
authorizer authorizerVal
path string
}
func (a pathCheckVal) Authorize(ctx context.Context, verb string) ref.Val {
attr := &authorizer.AttributesRecord{
Path: a.path,
Verb: verb,
User: a.authorizer.userInfo,
}
decision, reason, err := a.authorizer.authAuthorizer.Authorize(ctx, attr)
return newDecision(decision, err, reason)
}
type groupCheckVal struct {
receiverOnlyObjectVal
authorizer authorizerVal
group string
}
func (g groupCheckVal) resourceCheck(resource string) resourceCheckVal {
return resourceCheckVal{receiverOnlyObjectVal: receiverOnlyVal(ResourceCheckType), groupCheck: g, resource: resource}
}
type resourceCheckVal struct {
receiverOnlyObjectVal
groupCheck groupCheckVal
resource string
subresource string
namespace string
name string
fieldSelector string
labelSelector string
}
func (a resourceCheckVal) Authorize(ctx context.Context, verb string) ref.Val {
attr := &authorizer.AttributesRecord{
ResourceRequest: true,
APIGroup: a.groupCheck.group,
APIVersion: "*",
Resource: a.resource,
Subresource: a.subresource,
Namespace: a.namespace,
Name: a.name,
Verb: verb,
User: a.groupCheck.authorizer.userInfo,
}
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AuthorizeWithSelectors) {
if len(a.fieldSelector) > 0 {
selector, err := fields.ParseSelector(a.fieldSelector)
if err != nil {
attr.FieldSelectorRequirements, attr.FieldSelectorParsingErr = nil, err
} else {
attr.FieldSelectorRequirements, attr.FieldSelectorParsingErr = selector.Requirements(), nil
}
}
if len(a.labelSelector) > 0 {
requirements, err := labels.ParseToRequirements(a.labelSelector)
if err != nil {
attr.LabelSelectorRequirements, attr.LabelSelectorParsingErr = nil, err
} else {
attr.LabelSelectorRequirements, attr.LabelSelectorParsingErr = requirements, nil
}
}
}
decision, reason, err := a.groupCheck.authorizer.authAuthorizer.Authorize(ctx, attr)
return newDecision(decision, err, reason)
}
func newDecision(authDecision authorizer.Decision, err error, reason string) decisionVal {
return decisionVal{receiverOnlyObjectVal: receiverOnlyVal(DecisionType), authDecision: authDecision, err: err, reason: reason}
}
type decisionVal struct {
receiverOnlyObjectVal
err error
authDecision authorizer.Decision
reason string
}
// receiverOnlyObjectVal provides an implementation of ref.Val for
// any object type that has receiver functions but does not expose any fields to
// CEL.
type receiverOnlyObjectVal struct {
typeValue *types.Type
}
// receiverOnlyVal returns a receiverOnlyObjectVal for the given type.
func receiverOnlyVal(objectType *cel.Type) receiverOnlyObjectVal {
return receiverOnlyObjectVal{typeValue: types.NewTypeValue(objectType.String())}
}
// ConvertToNative implements ref.Val.ConvertToNative.
func (a receiverOnlyObjectVal) ConvertToNative(typeDesc reflect.Type) (any, error) {
return nil, fmt.Errorf("type conversion error from '%s' to '%v'", a.typeValue.String(), typeDesc)
}
// ConvertToType implements ref.Val.ConvertToType.
func (a receiverOnlyObjectVal) ConvertToType(typeVal ref.Type) ref.Val {
switch typeVal {
case a.typeValue:
return a
case types.TypeType:
return a.typeValue
}
return types.NewErr("type conversion error from '%s' to '%s'", a.typeValue, typeVal)
}
// Equal implements ref.Val.Equal.
func (a receiverOnlyObjectVal) Equal(other ref.Val) ref.Val {
o, ok := other.(receiverOnlyObjectVal)
if !ok {
return types.MaybeNoSuchOverloadErr(other)
}
return types.Bool(a == o)
}
// Type implements ref.Val.Type.
func (a receiverOnlyObjectVal) Type() ref.Type {
return a.typeValue
}
// Value implements ref.Val.Value.
func (a receiverOnlyObjectVal) Value() any {
return types.NoSuchOverloadErr()
}

295
e2e/vendor/k8s.io/apiserver/pkg/cel/library/cidr.go generated vendored Normal file
View File

@ -0,0 +1,295 @@
/*
Copyright 2023 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 library
import (
"fmt"
"net/netip"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
apiservercel "k8s.io/apiserver/pkg/cel"
)
// CIDR provides a CEL function library extension of CIDR notation parsing functions.
//
// cidr
//
// Converts a string in CIDR notation to a network address representation or results in an error if the string is not a valid CIDR notation.
// The CIDR must be an IPv4 or IPv6 subnet address with a mask.
// Leading zeros in IPv4 address octets are not allowed.
// IPv4-mapped IPv6 addresses (e.g. ::ffff:1.2.3.4/24) are not allowed.
//
// cidr(<string>) <CIDR>
//
// Examples:
//
// cidr('192.168.0.0/16') // returns an IPv4 address with a CIDR mask
// cidr('::1/128') // returns an IPv6 address with a CIDR mask
// cidr('192.168.0.0/33') // error
// cidr('::1/129') // error
// cidr('192.168.0.1/16') // error, because there are non-0 bits after the prefix
//
// isCIDR
//
// Returns true if a string is a valid CIDR notation respresentation of a subnet with mask.
// The CIDR must be an IPv4 or IPv6 subnet address with a mask.
// Leading zeros in IPv4 address octets are not allowed.
// IPv4-mapped IPv6 addresses (e.g. ::ffff:1.2.3.4/24) are not allowed.
//
// isCIDR(<string>) <bool>
//
// Examples:
//
// isCIDR('192.168.0.0/16') // returns true
// isCIDR('::1/128') // returns true
// isCIDR('192.168.0.0/33') // returns false
// isCIDR('::1/129') // returns false
//
// containsIP / containerCIDR / ip / masked / prefixLength
//
// - containsIP: Returns true if a the CIDR contains the given IP address.
// The IP address must be an IPv4 or IPv6 address.
// May take either a string or IP address as an argument.
//
// - containsCIDR: Returns true if a the CIDR contains the given CIDR.
// The CIDR must be an IPv4 or IPv6 subnet address with a mask.
// May take either a string or CIDR as an argument.
//
// - ip: Returns the IP address representation of the CIDR.
//
// - masked: Returns the CIDR representation of the network address with a masked prefix.
// This can be used to return the canonical form of the CIDR network.
//
// - prefixLength: Returns the prefix length of the CIDR in bits.
// This is the number of bits in the mask.
//
// Examples:
//
// cidr('192.168.0.0/24').containsIP(ip('192.168.0.1')) // returns true
// cidr('192.168.0.0/24').containsIP(ip('192.168.1.1')) // returns false
// cidr('192.168.0.0/24').containsIP('192.168.0.1') // returns true
// cidr('192.168.0.0/24').containsIP('192.168.1.1') // returns false
// cidr('192.168.0.0/16').containsCIDR(cidr('192.168.10.0/24')) // returns true
// cidr('192.168.1.0/24').containsCIDR(cidr('192.168.2.0/24')) // returns false
// cidr('192.168.0.0/16').containsCIDR('192.168.10.0/24') // returns true
// cidr('192.168.1.0/24').containsCIDR('192.168.2.0/24') // returns false
// cidr('192.168.0.1/24').ip() // returns ipAddr('192.168.0.1')
// cidr('192.168.0.1/24').ip().family() // returns '4'
// cidr('::1/128').ip() // returns ipAddr('::1')
// cidr('::1/128').ip().family() // returns '6'
// cidr('192.168.0.0/24').masked() // returns cidr('192.168.0.0/24')
// cidr('192.168.0.1/24').masked() // returns cidr('192.168.0.0/24')
// cidr('192.168.0.0/24') == cidr('192.168.0.0/24').masked() // returns true, CIDR was already in canonical format
// cidr('192.168.0.1/24') == cidr('192.168.0.1/24').masked() // returns false, CIDR was not in canonical format
// cidr('192.168.0.0/16').prefixLength() // returns 16
// cidr('::1/128').prefixLength() // returns 128
func CIDR() cel.EnvOption {
return cel.Lib(cidrsLib)
}
var cidrsLib = &cidrs{}
type cidrs struct{}
func (*cidrs) LibraryName() string {
return "kubernetes.net.cidr"
}
func (*cidrs) declarations() map[string][]cel.FunctionOpt {
return cidrLibraryDecls
}
func (*cidrs) Types() []*cel.Type {
return []*cel.Type{apiservercel.CIDRType, apiservercel.IPType}
}
var cidrLibraryDecls = map[string][]cel.FunctionOpt{
"cidr": {
cel.Overload("string_to_cidr", []*cel.Type{cel.StringType}, apiservercel.CIDRType,
cel.UnaryBinding(stringToCIDR)),
},
"containsIP": {
cel.MemberOverload("cidr_contains_ip_string", []*cel.Type{apiservercel.CIDRType, cel.StringType}, cel.BoolType,
cel.BinaryBinding(cidrContainsIPString)),
cel.MemberOverload("cidr_contains_ip_ip", []*cel.Type{apiservercel.CIDRType, apiservercel.IPType}, cel.BoolType,
cel.BinaryBinding(cidrContainsIP)),
},
"containsCIDR": {
cel.MemberOverload("cidr_contains_cidr_string", []*cel.Type{apiservercel.CIDRType, cel.StringType}, cel.BoolType,
cel.BinaryBinding(cidrContainsCIDRString)),
cel.MemberOverload("cidr_contains_cidr", []*cel.Type{apiservercel.CIDRType, apiservercel.CIDRType}, cel.BoolType,
cel.BinaryBinding(cidrContainsCIDR)),
},
"ip": {
cel.MemberOverload("cidr_ip", []*cel.Type{apiservercel.CIDRType}, apiservercel.IPType,
cel.UnaryBinding(cidrToIP)),
},
"prefixLength": {
cel.MemberOverload("cidr_prefix_length", []*cel.Type{apiservercel.CIDRType}, cel.IntType,
cel.UnaryBinding(prefixLength)),
},
"masked": {
cel.MemberOverload("cidr_masked", []*cel.Type{apiservercel.CIDRType}, apiservercel.CIDRType,
cel.UnaryBinding(masked)),
},
"isCIDR": {
cel.Overload("is_cidr", []*cel.Type{cel.StringType}, cel.BoolType,
cel.UnaryBinding(isCIDR)),
},
"string": {
cel.Overload("cidr_to_string", []*cel.Type{apiservercel.CIDRType}, cel.StringType,
cel.UnaryBinding(cidrToString)),
},
}
func (*cidrs) CompileOptions() []cel.EnvOption {
options := []cel.EnvOption{cel.Types(apiservercel.CIDRType),
cel.Variable(apiservercel.CIDRType.TypeName(), types.NewTypeTypeWithParam(apiservercel.CIDRType)),
}
for name, overloads := range cidrLibraryDecls {
options = append(options, cel.Function(name, overloads...))
}
return options
}
func (*cidrs) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{}
}
func stringToCIDR(arg ref.Val) ref.Val {
s, ok := arg.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
net, err := parseCIDR(s)
if err != nil {
return types.NewErr("network address parse error during conversion from string: %v", err)
}
return apiservercel.CIDR{
Prefix: net,
}
}
func cidrToString(arg ref.Val) ref.Val {
cidr, ok := arg.(apiservercel.CIDR)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.String(cidr.Prefix.String())
}
func cidrContainsIPString(arg ref.Val, other ref.Val) ref.Val {
return cidrContainsIP(arg, stringToIP(other))
}
func cidrContainsCIDRString(arg ref.Val, other ref.Val) ref.Val {
return cidrContainsCIDR(arg, stringToCIDR(other))
}
func cidrContainsIP(arg ref.Val, other ref.Val) ref.Val {
cidr, ok := arg.(apiservercel.CIDR)
if !ok {
return types.MaybeNoSuchOverloadErr(other)
}
ip, ok := other.(apiservercel.IP)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Bool(cidr.Contains(ip.Addr))
}
func cidrContainsCIDR(arg ref.Val, other ref.Val) ref.Val {
cidr, ok := arg.(apiservercel.CIDR)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
containsCIDR, ok := other.(apiservercel.CIDR)
if !ok {
return types.MaybeNoSuchOverloadErr(other)
}
equalMasked := cidr.Prefix.Masked() == netip.PrefixFrom(containsCIDR.Prefix.Addr(), cidr.Prefix.Bits())
return types.Bool(equalMasked && cidr.Prefix.Bits() <= containsCIDR.Prefix.Bits())
}
func prefixLength(arg ref.Val) ref.Val {
cidr, ok := arg.(apiservercel.CIDR)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Int(cidr.Prefix.Bits())
}
func isCIDR(arg ref.Val) ref.Val {
s, ok := arg.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
_, err := parseCIDR(s)
return types.Bool(err == nil)
}
func cidrToIP(arg ref.Val) ref.Val {
cidr, ok := arg.(apiservercel.CIDR)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return apiservercel.IP{
Addr: cidr.Prefix.Addr(),
}
}
func masked(arg ref.Val) ref.Val {
cidr, ok := arg.(apiservercel.CIDR)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
maskedCIDR := cidr.Prefix.Masked()
return apiservercel.CIDR{
Prefix: maskedCIDR,
}
}
// parseCIDR parses a string into an CIDR.
// We use this function to parse CIDR notation in the CEL library
// so that we can share the common logic of rejecting strings
// that IPv4-mapped IPv6 addresses or contain non-zero bits after the mask.
func parseCIDR(raw string) (netip.Prefix, error) {
net, err := netip.ParsePrefix(raw)
if err != nil {
return netip.Prefix{}, fmt.Errorf("network address parse error during conversion from string: %v", err)
}
if net.Addr().Is4In6() {
return netip.Prefix{}, fmt.Errorf("IPv4-mapped IPv6 address %q is not allowed", raw)
}
return net, nil
}

643
e2e/vendor/k8s.io/apiserver/pkg/cel/library/cost.go generated vendored Normal file
View File

@ -0,0 +1,643 @@
/*
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 library
import (
"fmt"
"github.com/google/cel-go/checker"
"github.com/google/cel-go/common"
"github.com/google/cel-go/common/ast"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
"math"
"k8s.io/apiserver/pkg/cel"
)
// panicOnUnknown makes cost estimate functions panic on unrecognized functions.
// This is only set to true for unit tests.
var panicOnUnknown = false
// builtInFunctions is a list of functions used in cost tests that are not handled by CostEstimator.
var knownUnhandledFunctions = map[string]bool{
"@not_strictly_false": true,
"uint": true,
"duration": true,
"bytes": true,
"cel.@mapInsert": true,
"timestamp": true,
"strings.quote": true,
"value": true,
"_==_": true,
"_&&_": true,
"_||_": true,
"_>_": true,
"_>=_": true,
"_<_": true,
"_<=_": true,
"!_": true,
"_?_:_": true,
"_+_": true,
"_-_": true,
}
// CostEstimator implements CEL's interpretable.ActualCostEstimator and checker.CostEstimator.
type CostEstimator struct {
// SizeEstimator provides a CostEstimator.EstimateSize that this CostEstimator will delegate size estimation
// calculations to if the size is not well known (i.e. a constant).
SizeEstimator checker.CostEstimator
}
const (
// shortest repeatable selector requirement that allocates a values slice is 2 characters: k,
selectorLengthToRequirementCount = float64(.5)
// the expensive parts to represent each requirement are a struct and a values slice
costPerRequirement = float64(common.ListCreateBaseCost + common.StructCreateBaseCost)
)
// a selector consists of a list of requirements held in a slice
var baseSelectorCost = checker.CostEstimate{Min: common.ListCreateBaseCost, Max: common.ListCreateBaseCost}
func selectorCostEstimate(selectorLength checker.SizeEstimate) checker.CostEstimate {
parseCost := selectorLength.MultiplyByCostFactor(common.StringTraversalCostFactor)
requirementCount := selectorLength.MultiplyByCostFactor(selectorLengthToRequirementCount)
requirementCost := requirementCount.MultiplyByCostFactor(costPerRequirement)
return baseSelectorCost.Add(parseCost).Add(requirementCost)
}
func (l *CostEstimator) CallCost(function, overloadId string, args []ref.Val, result ref.Val) *uint64 {
switch function {
case "check":
// An authorization check has a fixed cost
// This cost is set to allow for only two authorization checks per expression
cost := uint64(350000)
return &cost
case "serviceAccount", "path", "group", "resource", "subresource", "namespace", "name", "allowed", "reason", "error", "errored":
// All authorization builder and accessor functions have a nominal cost
cost := uint64(1)
return &cost
case "fieldSelector", "labelSelector":
// field and label selector parse is a string parse into a structured set of requirements
if len(args) >= 2 {
selectorLength := actualSize(args[1])
cost := selectorCostEstimate(checker.SizeEstimate{Min: selectorLength, Max: selectorLength})
return &cost.Max
}
case "isSorted", "sum", "max", "min", "indexOf", "lastIndexOf":
var cost uint64
if len(args) > 0 {
cost += traversalCost(args[0]) // these O(n) operations all cost roughly the cost of a single traversal
}
return &cost
case "url", "lowerAscii", "upperAscii", "substring", "trim", "jsonpatch.escapeKey":
if len(args) >= 1 {
cost := uint64(math.Ceil(float64(actualSize(args[0])) * common.StringTraversalCostFactor))
return &cost
}
case "replace", "split":
if len(args) >= 1 {
// cost is the traversal plus the construction of the result
cost := uint64(math.Ceil(float64(actualSize(args[0])) * 2 * common.StringTraversalCostFactor))
return &cost
}
case "join":
if len(args) >= 1 {
cost := uint64(math.Ceil(float64(actualSize(result)) * 2 * common.StringTraversalCostFactor))
return &cost
}
case "find", "findAll":
if len(args) >= 2 {
strCost := uint64(math.Ceil((1.0 + float64(actualSize(args[0]))) * common.StringTraversalCostFactor))
// We don't know how many expressions are in the regex, just the string length (a huge
// improvement here would be to somehow get a count the number of expressions in the regex or
// how many states are in the regex state machine and use that to measure regex cost).
// For now, we're making a guess that each expression in a regex is typically at least 4 chars
// in length.
regexCost := uint64(math.Ceil(float64(actualSize(args[1])) * common.RegexStringLengthCostFactor))
cost := strCost * regexCost
return &cost
}
case "cidr", "isIP", "isCIDR":
// IP and CIDR parsing is a string traversal.
if len(args) >= 1 {
cost := uint64(math.Ceil(float64(actualSize(args[0])) * common.StringTraversalCostFactor))
return &cost
}
case "ip":
// IP and CIDR parsing is a string traversal.
if len(args) >= 1 {
if overloadId == "cidr_ip" {
// The IP member of the CIDR object is just accessing a field.
// Nominal cost.
cost := uint64(1)
return &cost
}
cost := uint64(math.Ceil(float64(actualSize(args[0])) * common.StringTraversalCostFactor))
return &cost
}
case "ip.isCanonical":
if len(args) >= 1 {
// We have to parse the string and then compare the parsed string to the original string.
// So we double the cost of parsing the string.
cost := uint64(math.Ceil(float64(actualSize(args[0])) * 2 * common.StringTraversalCostFactor))
return &cost
}
case "masked", "prefixLength", "family", "isUnspecified", "isLoopback", "isLinkLocalMulticast", "isLinkLocalUnicast", "isGlobalUnicast":
// IP and CIDR accessors are nominal cost.
cost := uint64(1)
return &cost
case "containsIP":
if len(args) >= 2 {
cidrSize := actualSize(args[0])
otherSize := actualSize(args[1])
// This is the base cost of comparing two byte lists.
// We will compare only up to the length of the CIDR prefix in bytes, so use the cidrSize twice.
cost := uint64(math.Ceil(float64(cidrSize+cidrSize) * common.StringTraversalCostFactor))
if overloadId == "cidr_contains_ip_string" {
// If we are comparing a string, we must parse the string to into the right type, so add the cost of traversing the string again.
cost += uint64(math.Ceil(float64(otherSize) * common.StringTraversalCostFactor))
}
return &cost
}
case "containsCIDR":
if len(args) >= 2 {
cidrSize := actualSize(args[0])
otherSize := actualSize(args[1])
// This is the base cost of comparing two byte lists.
// We will compare only up to the length of the CIDR prefix in bytes, so use the cidrSize twice.
cost := uint64(math.Ceil(float64(cidrSize+cidrSize) * common.StringTraversalCostFactor))
// As we are comparing if a CIDR is within another CIDR, we first mask the base CIDR and
// also compare the CIDR bits.
// This has an additional cost of the length of the IP being traversed again, plus 1.
cost += uint64(math.Ceil(float64(cidrSize)*common.StringTraversalCostFactor)) + 1
if overloadId == "cidr_contains_cidr_string" {
// If we are comparing a string, we must parse the string to into the right type, so add the cost of traversing the string again.
cost += uint64(math.Ceil(float64(otherSize) * common.StringTraversalCostFactor))
}
return &cost
}
case "quantity", "isQuantity":
if len(args) >= 1 {
cost := uint64(math.Ceil(float64(actualSize(args[0])) * common.StringTraversalCostFactor))
return &cost
}
case "validate":
if len(args) >= 2 {
format, isFormat := args[0].Value().(cel.Format)
if isFormat {
strSize := actualSize(args[1])
// Dont have access to underlying regex, estimate a long regexp
regexSize := format.MaxRegexSize
// Copied from CEL implementation for regex cost
//
// https://swtch.com/~rsc/regexp/regexp1.html applies to RE2 implementation supported by CEL
// Add one to string length for purposes of cost calculation to prevent product of string and regex to be 0
// in case where string is empty but regex is still expensive.
strCost := uint64(math.Ceil((1.0 + float64(strSize)) * common.StringTraversalCostFactor))
// We don't know how many expressions are in the regex, just the string length (a huge
// improvement here would be to somehow get a count the number of expressions in the regex or
// how many states are in the regex state machine and use that to measure regex cost).
// For now, we're making a guess that each expression in a regex is typically at least 4 chars
// in length.
regexCost := uint64(math.Ceil(float64(regexSize) * common.RegexStringLengthCostFactor))
cost := strCost * regexCost
return &cost
}
}
case "format.named":
// Simply dictionary lookup
cost := uint64(1)
return &cost
case "sign", "asInteger", "isInteger", "asApproximateFloat", "isGreaterThan", "isLessThan", "compareTo", "add", "sub":
cost := uint64(1)
return &cost
case "getScheme", "getHostname", "getHost", "getPort", "getEscapedPath", "getQuery":
// url accessors
cost := uint64(1)
return &cost
case "_==_":
if len(args) == 2 {
unitCost := uint64(1)
lhs := args[0]
switch lhs.(type) {
case *cel.Quantity, cel.Quantity,
*cel.IP, cel.IP,
*cel.CIDR, cel.CIDR,
*cel.Format, cel.Format, // Formats have a small max size. Format takes pointer receiver.
*cel.URL, cel.URL, // TODO: Computing the actual cost is expensive, and changing this would be a breaking change
*cel.Semver, cel.Semver,
*authorizerVal, authorizerVal, *pathCheckVal, pathCheckVal, *groupCheckVal, groupCheckVal,
*resourceCheckVal, resourceCheckVal, *decisionVal, decisionVal:
return &unitCost
default:
if panicOnUnknown && lhs.Type() != nil && isRegisteredType(lhs.Type().TypeName()) {
panic(fmt.Errorf("CallCost: unhandled equality for Kubernetes type %T", lhs))
}
}
}
}
if panicOnUnknown && !knownUnhandledFunctions[function] {
panic(fmt.Errorf("CallCost: unhandled function %q or args %v", function, args))
}
return nil
}
func (l *CostEstimator) EstimateCallCost(function, overloadId string, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {
// WARNING: Any changes to this code impact API compatibility! The estimated cost is used to determine which CEL rules may be written to a
// CRD and any change (cost increases and cost decreases) are breaking.
switch function {
case "check":
// An authorization check has a fixed cost
// This cost is set to allow for only two authorization checks per expression
return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 350000, Max: 350000}}
case "serviceAccount", "path", "group", "resource", "subresource", "namespace", "name", "allowed", "reason", "error", "errored":
// All authorization builder and accessor functions have a nominal cost
return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 1, Max: 1}}
case "fieldSelector", "labelSelector":
// field and label selector parse is a string parse into a structured set of requirements
if len(args) == 1 {
return &checker.CallEstimate{CostEstimate: selectorCostEstimate(l.sizeEstimate(args[0]))}
}
case "isSorted", "sum", "max", "min", "indexOf", "lastIndexOf":
if target != nil {
// Charge 1 cost for comparing each element in the list
elCost := checker.CostEstimate{Min: 1, Max: 1}
// If the list contains strings or bytes, add the cost of traversing all the strings/bytes as a way
// of estimating the additional comparison cost.
if elNode := l.listElementNode(*target); elNode != nil {
k := elNode.Type().Kind()
if k == types.StringKind || k == types.BytesKind {
sz := l.sizeEstimate(elNode)
elCost = elCost.Add(sz.MultiplyByCostFactor(common.StringTraversalCostFactor))
}
return &checker.CallEstimate{CostEstimate: l.sizeEstimate(*target).MultiplyByCost(elCost)}
} else { // the target is a string, which is supported by indexOf and lastIndexOf
return &checker.CallEstimate{CostEstimate: l.sizeEstimate(*target).MultiplyByCostFactor(common.StringTraversalCostFactor)}
}
}
case "url", "jsonpatch.escapeKey":
if len(args) == 1 {
sz := l.sizeEstimate(args[0])
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor), ResultSize: &sz}
}
case "lowerAscii", "upperAscii", "substring", "trim":
if target != nil {
sz := l.sizeEstimate(*target)
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor), ResultSize: &sz}
}
case "replace":
if target != nil && len(args) >= 2 {
sz := l.sizeEstimate(*target)
toReplaceSz := l.sizeEstimate(args[0])
replaceWithSz := l.sizeEstimate(args[1])
var replaceCount, retainedSz checker.SizeEstimate
// find the longest replacement:
if toReplaceSz.Min == 0 {
// if the string being replaced is empty, replace surrounds all characters in the input string with the replacement.
if sz.Max < math.MaxUint64 {
replaceCount.Max = sz.Max + 1
} else {
replaceCount.Max = sz.Max
}
// Include the length of the longest possible original string length.
retainedSz.Max = sz.Max
} else if replaceWithSz.Max <= toReplaceSz.Min {
// If the replacement does not make the result longer, use the original string length.
replaceCount.Max = 0
retainedSz.Max = sz.Max
} else {
// Replace the smallest possible substrings with the largest possible replacement
// as many times as possible.
replaceCount.Max = uint64(math.Ceil(float64(sz.Max) / float64(toReplaceSz.Min)))
}
// find the shortest replacement:
if toReplaceSz.Max == 0 {
// if the string being replaced is empty, replace surrounds all characters in the input string with the replacement.
if sz.Min < math.MaxUint64 {
replaceCount.Min = sz.Min + 1
} else {
replaceCount.Min = sz.Min
}
// Include the length of the shortest possible original string length.
retainedSz.Min = sz.Min
} else if toReplaceSz.Max <= replaceWithSz.Min {
// If the replacement does not make the result shorter, use the original string length.
replaceCount.Min = 0
retainedSz.Min = sz.Min
} else {
// Replace the largest possible substrings being with the smallest possible replacement
// as many times as possible.
replaceCount.Min = uint64(math.Ceil(float64(sz.Min) / float64(toReplaceSz.Max)))
}
size := replaceCount.Multiply(replaceWithSz).Add(retainedSz)
// cost is the traversal plus the construction of the result
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(2 * common.StringTraversalCostFactor), ResultSize: &size}
}
case "split":
if target != nil {
sz := l.sizeEstimate(*target)
// Worst case size is where is that a separator of "" is used, and each char is returned as a list element.
max := sz.Max
if len(args) > 1 {
if v := args[1].Expr().AsLiteral(); v != nil {
if i, ok := v.Value().(int64); ok {
max = uint64(i)
}
}
}
// Cost is the traversal plus the construction of the result.
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(2 * common.StringTraversalCostFactor), ResultSize: &checker.SizeEstimate{Min: 0, Max: max}}
}
case "join":
if target != nil {
var sz checker.SizeEstimate
listSize := l.sizeEstimate(*target)
if elNode := l.listElementNode(*target); elNode != nil {
elemSize := l.sizeEstimate(elNode)
sz = listSize.Multiply(elemSize)
}
if len(args) > 0 {
sepSize := l.sizeEstimate(args[0])
minSeparators := uint64(0)
maxSeparators := uint64(0)
if listSize.Min > 0 {
minSeparators = listSize.Min - 1
}
if listSize.Max > 0 {
maxSeparators = listSize.Max - 1
}
sz = sz.Add(sepSize.Multiply(checker.SizeEstimate{Min: minSeparators, Max: maxSeparators}))
}
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor), ResultSize: &sz}
}
case "find", "findAll":
if target != nil && len(args) >= 1 {
sz := l.sizeEstimate(*target)
// Add one to string length for purposes of cost calculation to prevent product of string and regex to be 0
// in case where string is empty but regex is still expensive.
strCost := sz.Add(checker.SizeEstimate{Min: 1, Max: 1}).MultiplyByCostFactor(common.StringTraversalCostFactor)
// We don't know how many expressions are in the regex, just the string length (a huge
// improvement here would be to somehow get a count the number of expressions in the regex or
// how many states are in the regex state machine and use that to measure regex cost).
// For now, we're making a guess that each expression in a regex is typically at least 4 chars
// in length.
regexCost := l.sizeEstimate(args[0]).MultiplyByCostFactor(common.RegexStringLengthCostFactor)
// worst case size of result is that every char is returned as separate find result.
return &checker.CallEstimate{CostEstimate: strCost.Multiply(regexCost), ResultSize: &checker.SizeEstimate{Min: 0, Max: sz.Max}}
}
case "cidr", "isIP", "isCIDR":
if target != nil {
sz := l.sizeEstimate(args[0])
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor)}
}
case "ip":
if target != nil && len(args) >= 1 {
if overloadId == "cidr_ip" {
// The IP member of the CIDR object is just accessing a field.
// Nominal cost.
return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 1, Max: 1}}
}
sz := l.sizeEstimate(args[0])
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor)}
} else if target != nil {
// The IP member of a CIDR is a just accessing a field, nominal cost.
return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 1, Max: 1}}
}
case "ip.isCanonical":
if target != nil && len(args) >= 1 {
sz := l.sizeEstimate(args[0])
// We have to parse the string and then compare the parsed string to the original string.
// So we double the cost of parsing the string.
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(2 * common.StringTraversalCostFactor)}
}
case "masked", "prefixLength", "family", "isUnspecified", "isLoopback", "isLinkLocalMulticast", "isLinkLocalUnicast", "isGlobalUnicast":
// IP and CIDR accessors are nominal cost.
return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 1, Max: 1}}
case "containsIP":
if target != nil && len(args) >= 1 {
// The base cost of the function is the cost of comparing two byte lists.
// The byte lists will be either ipv4 or ipv6 so will have a length of 4, or 16 bytes.
sz := checker.SizeEstimate{Min: 4, Max: 16}
// We have to compare the two strings to determine if the CIDR/IP is in the other CIDR.
ipCompCost := sz.Add(sz).MultiplyByCostFactor(common.StringTraversalCostFactor)
if overloadId == "cidr_contains_ip_string" {
// If we are comparing a string, we must parse the string to into the right type, so add the cost of traversing the string again.
ipCompCost = ipCompCost.Add(checker.CostEstimate(l.sizeEstimate(args[0])).MultiplyByCostFactor(common.StringTraversalCostFactor))
}
return &checker.CallEstimate{CostEstimate: ipCompCost}
}
case "containsCIDR":
if target != nil && len(args) >= 1 {
// The base cost of the function is the cost of comparing two byte lists.
// The byte lists will be either ipv4 or ipv6 so will have a length of 4, or 16 bytes.
sz := checker.SizeEstimate{Min: 4, Max: 16}
// We have to compare the two strings to determine if the CIDR/IP is in the other CIDR.
ipCompCost := sz.Add(sz).MultiplyByCostFactor(common.StringTraversalCostFactor)
// As we are comparing if a CIDR is within another CIDR, we first mask the base CIDR and
// also compare the CIDR bits.
// This has an additional cost of the length of the IP being traversed again, plus 1.
ipCompCost = ipCompCost.Add(sz.MultiplyByCostFactor(common.StringTraversalCostFactor))
ipCompCost = ipCompCost.Add(checker.CostEstimate{Min: 1, Max: 1})
if overloadId == "cidr_contains_cidr_string" {
// If we are comparing a string, we must parse the string to into the right type, so add the cost of traversing the string again.
ipCompCost = ipCompCost.Add(checker.CostEstimate(l.sizeEstimate(args[0])).MultiplyByCostFactor(common.StringTraversalCostFactor))
}
return &checker.CallEstimate{CostEstimate: ipCompCost}
}
case "quantity", "isQuantity":
if target != nil {
sz := l.sizeEstimate(args[0])
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor)}
}
case "validate":
if target != nil {
sz := l.sizeEstimate(args[0])
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor).MultiplyByCostFactor(cel.MaxNameFormatRegexSize * common.RegexStringLengthCostFactor)}
}
case "format.named":
return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 1, Max: 1}}
case "sign", "asInteger", "isInteger", "asApproximateFloat", "isGreaterThan", "isLessThan", "compareTo", "add", "sub":
return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 1, Max: 1}}
case "getScheme", "getHostname", "getHost", "getPort", "getEscapedPath", "getQuery":
// url accessors
return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 1, Max: 1}}
case "_==_":
if len(args) == 2 {
lhs := args[0]
rhs := args[1]
if lhs.Type().Equal(rhs.Type()) == types.True {
t := lhs.Type()
if t.Kind() == types.OpaqueKind {
switch t.TypeName() {
case cel.IPType.TypeName(), cel.CIDRType.TypeName():
return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 1, Max: 1}}
}
}
if t.Kind() == types.StructKind {
switch t {
case cel.QuantityType, AuthorizerType, PathCheckType, // O(1) cost equality checks
GroupCheckType, ResourceCheckType, DecisionType, cel.SemverType:
return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 1, Max: 1}}
case cel.FormatType:
return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 1, Max: cel.MaxFormatSize}.MultiplyByCostFactor(common.StringTraversalCostFactor)}
case cel.URLType:
size := checker.SizeEstimate{Min: 1, Max: 1}
rhSize := rhs.ComputedSize()
lhSize := rhs.ComputedSize()
if rhSize != nil && lhSize != nil {
size = rhSize.Union(*lhSize)
}
return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 1, Max: size.Max}.MultiplyByCostFactor(common.StringTraversalCostFactor)}
}
}
if panicOnUnknown && isRegisteredType(t.TypeName()) {
panic(fmt.Errorf("EstimateCallCost: unhandled equality for Kubernetes type %v", t))
}
}
}
}
if panicOnUnknown && !knownUnhandledFunctions[function] {
panic(fmt.Errorf("EstimateCallCost: unhandled function %q, target %v, args %v", function, target, args))
}
return nil
}
func actualSize(value ref.Val) uint64 {
if sz, ok := value.(traits.Sizer); ok {
return uint64(sz.Size().(types.Int))
}
if panicOnUnknown {
// debug.PrintStack()
panic(fmt.Errorf("actualSize: non-sizer type %T", value))
}
return 1
}
func (l *CostEstimator) sizeEstimate(t checker.AstNode) checker.SizeEstimate {
if sz := t.ComputedSize(); sz != nil {
return *sz
}
if sz := l.EstimateSize(t); sz != nil {
return *sz
}
return checker.SizeEstimate{Min: 0, Max: math.MaxUint64}
}
func (l *CostEstimator) listElementNode(list checker.AstNode) checker.AstNode {
if params := list.Type().Parameters(); len(params) > 0 {
lt := params[0]
nodePath := list.Path()
if nodePath != nil {
// Provide path if we have it so that a OpenAPIv3 maxLength validation can be looked up, if it exists
// for this node.
path := make([]string, len(nodePath)+1)
copy(path, nodePath)
path[len(nodePath)] = "@items"
return &itemsNode{path: path, t: lt, expr: nil}
} else {
// Provide just the type if no path is available so that worst case size can be looked up based on type.
return &itemsNode{t: lt, expr: nil}
}
}
return nil
}
func (l *CostEstimator) EstimateSize(element checker.AstNode) *checker.SizeEstimate {
if l.SizeEstimator != nil {
return l.SizeEstimator.EstimateSize(element)
}
return nil
}
type itemsNode struct {
path []string
t *types.Type
expr ast.Expr
}
func (i *itemsNode) Path() []string {
return i.path
}
func (i *itemsNode) Type() *types.Type {
return i.t
}
func (i *itemsNode) Expr() ast.Expr {
return i.expr
}
func (i *itemsNode) ComputedSize() *checker.SizeEstimate {
return nil
}
var _ checker.AstNode = (*itemsNode)(nil)
// traversalCost computes the cost of traversing a ref.Val as a data tree.
func traversalCost(v ref.Val) uint64 {
// TODO: This could potentially be optimized by sampling maps and lists instead of traversing.
switch vt := v.(type) {
case types.String:
return uint64(float64(len(string(vt))) * common.StringTraversalCostFactor)
case types.Bytes:
return uint64(float64(len([]byte(vt))) * common.StringTraversalCostFactor)
case traits.Lister:
cost := uint64(0)
for it := vt.Iterator(); it.HasNext() == types.True; {
i := it.Next()
cost += traversalCost(i)
}
return cost
case traits.Mapper: // maps and objects
cost := uint64(0)
for it := vt.Iterator(); it.HasNext() == types.True; {
k := it.Next()
cost += traversalCost(k) + traversalCost(vt.Get(k))
}
return cost
default:
return 1
}
}

279
e2e/vendor/k8s.io/apiserver/pkg/cel/library/format.go generated vendored Normal file
View File

@ -0,0 +1,279 @@
/*
Copyright 2024 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 library
import (
"fmt"
"net/url"
"github.com/asaskevich/govalidator"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/decls"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/util/validation"
apiservercel "k8s.io/apiserver/pkg/cel"
"k8s.io/kube-openapi/pkg/validation/strfmt"
)
// Format provides a CEL library exposing common named Kubernetes string
// validations. Can be used in CRD ValidationRules messageExpression.
//
// Example:
//
// rule: format.dns1123label.validate(object.metadata.name).hasValue()
// messageExpression: format.dns1123label.validate(object.metadata.name).value().join("\n")
//
// format.named(name: string) -> ?Format
//
// Returns the Format with the given name, if it exists. Otherwise, optional.none
// Allowed names are:
// - `dns1123Label`
// - `dns1123Subdomain`
// - `dns1035Label`
// - `qualifiedName`
// - `dns1123LabelPrefix`
// - `dns1123SubdomainPrefix`
// - `dns1035LabelPrefix`
// - `labelValue`
// - `uri`
// - `uuid`
// - `byte`
// - `date`
// - `datetime`
//
// format.<formatName>() -> Format
//
// Convenience functions for all the named formats are also available
//
// Examples:
// format.dns1123Label().validate("my-label-name")
// format.dns1123Subdomain().validate("apiextensions.k8s.io")
// format.dns1035Label().validate("my-label-name")
// format.qualifiedName().validate("apiextensions.k8s.io/v1beta1")
// format.dns1123LabelPrefix().validate("my-label-prefix-")
// format.dns1123SubdomainPrefix().validate("mysubdomain.prefix.-")
// format.dns1035LabelPrefix().validate("my-label-prefix-")
// format.uri().validate("http://example.com")
// Uses same pattern as isURL, but returns an error
// format.uuid().validate("123e4567-e89b-12d3-a456-426614174000")
// format.byte().validate("aGVsbG8=")
// format.date().validate("2021-01-01")
// format.datetime().validate("2021-01-01T00:00:00Z")
//
// <Format>.validate(str: string) -> ?list<string>
//
// Validates the given string against the given format. Returns optional.none
// if the string is valid, otherwise a list of validation error strings.
func Format() cel.EnvOption {
return cel.Lib(formatLib)
}
var formatLib = &format{}
type format struct{}
func (*format) LibraryName() string {
return "kubernetes.format"
}
func (*format) Types() []*cel.Type {
return []*cel.Type{apiservercel.FormatType}
}
func (*format) declarations() map[string][]cel.FunctionOpt {
return formatLibraryDecls
}
func ZeroArgumentFunctionBinding(binding func() ref.Val) decls.OverloadOpt {
return func(o *decls.OverloadDecl) (*decls.OverloadDecl, error) {
wrapped, err := decls.FunctionBinding(func(values ...ref.Val) ref.Val { return binding() })(o)
if err != nil {
return nil, err
}
if len(wrapped.ArgTypes()) != 0 {
return nil, fmt.Errorf("function binding must have 0 arguments")
}
return o, nil
}
}
func (*format) CompileOptions() []cel.EnvOption {
options := make([]cel.EnvOption, 0, len(formatLibraryDecls))
for name, overloads := range formatLibraryDecls {
options = append(options, cel.Function(name, overloads...))
}
for name, constantValue := range ConstantFormats {
prefixedName := "format." + name
options = append(options, cel.Function(prefixedName, cel.Overload(prefixedName, []*cel.Type{}, apiservercel.FormatType, ZeroArgumentFunctionBinding(func() ref.Val {
return constantValue
}))))
}
return options
}
func (*format) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{}
}
var ConstantFormats = map[string]apiservercel.Format{
"dns1123Label": {
Name: "DNS1123Label",
ValidateFunc: func(s string) []string { return apimachineryvalidation.NameIsDNSLabel(s, false) },
MaxRegexSize: 30,
},
"dns1123Subdomain": {
Name: "DNS1123Subdomain",
ValidateFunc: func(s string) []string { return apimachineryvalidation.NameIsDNSSubdomain(s, false) },
MaxRegexSize: 60,
},
"dns1035Label": {
Name: "DNS1035Label",
ValidateFunc: func(s string) []string { return apimachineryvalidation.NameIsDNS1035Label(s, false) },
MaxRegexSize: 30,
},
"qualifiedName": {
Name: "QualifiedName",
ValidateFunc: validation.IsQualifiedName,
MaxRegexSize: 60, // uses subdomain regex
},
"dns1123LabelPrefix": {
Name: "DNS1123LabelPrefix",
ValidateFunc: func(s string) []string { return apimachineryvalidation.NameIsDNSLabel(s, true) },
MaxRegexSize: 30,
},
"dns1123SubdomainPrefix": {
Name: "DNS1123SubdomainPrefix",
ValidateFunc: func(s string) []string { return apimachineryvalidation.NameIsDNSSubdomain(s, true) },
MaxRegexSize: 60,
},
"dns1035LabelPrefix": {
Name: "DNS1035LabelPrefix",
ValidateFunc: func(s string) []string { return apimachineryvalidation.NameIsDNS1035Label(s, true) },
MaxRegexSize: 30,
},
"labelValue": {
Name: "LabelValue",
ValidateFunc: validation.IsValidLabelValue,
MaxRegexSize: 40,
},
// CRD formats
// Implementations sourced from strfmt, which kube-openapi uses as its
// format library. There are other CRD formats supported, but they are
// covered by other portions of the CEL library (like IP/CIDR), or their
// use is discouraged (like bsonobjectid, email, etc)
"uri": {
Name: "URI",
ValidateFunc: func(s string) []string {
// Directly call ParseRequestURI since we can get a better error message
_, err := url.ParseRequestURI(s)
if err != nil {
return []string{err.Error()}
}
return nil
},
// Use govalidator url regex to estimate, since ParseRequestURI
// doesnt use regex
MaxRegexSize: len(govalidator.URL),
},
"uuid": {
Name: "uuid",
ValidateFunc: func(s string) []string {
if !strfmt.Default.Validates("uuid", s) {
return []string{"does not match the UUID format"}
}
return nil
},
MaxRegexSize: len(strfmt.UUIDPattern),
},
"byte": {
Name: "byte",
ValidateFunc: func(s string) []string {
if !strfmt.Default.Validates("byte", s) {
return []string{"invalid base64"}
}
return nil
},
MaxRegexSize: len(govalidator.Base64),
},
"date": {
Name: "date",
ValidateFunc: func(s string) []string {
if !strfmt.Default.Validates("date", s) {
return []string{"invalid date"}
}
return nil
},
// Estimated regex size for RFC3339FullDate which is
// a date format. Assume a date-time pattern is longer
// so use that to conservatively estimate this
MaxRegexSize: len(strfmt.DateTimePattern),
},
"datetime": {
Name: "datetime",
ValidateFunc: func(s string) []string {
if !strfmt.Default.Validates("datetime", s) {
return []string{"invalid datetime"}
}
return nil
},
MaxRegexSize: len(strfmt.DateTimePattern),
},
}
var formatLibraryDecls = map[string][]cel.FunctionOpt{
"validate": {
cel.MemberOverload("format-validate", []*cel.Type{apiservercel.FormatType, cel.StringType}, cel.OptionalType(cel.ListType(cel.StringType)), cel.BinaryBinding(formatValidate)),
},
"format.named": {
cel.Overload("format-named", []*cel.Type{cel.StringType}, cel.OptionalType(apiservercel.FormatType), cel.UnaryBinding(func(name ref.Val) ref.Val {
nameString, ok := name.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(name)
}
f, ok := ConstantFormats[nameString]
if !ok {
return types.OptionalNone
}
return types.OptionalOf(f)
})),
},
}
func formatValidate(arg1, arg2 ref.Val) ref.Val {
f, ok := arg1.Value().(apiservercel.Format)
if !ok {
return types.MaybeNoSuchOverloadErr(arg1)
}
str, ok := arg2.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg2)
}
res := f.ValidateFunc(str)
if len(res) == 0 {
return types.OptionalNone
}
return types.OptionalOf(types.NewStringList(types.DefaultTypeAdapter, res))
}

337
e2e/vendor/k8s.io/apiserver/pkg/cel/library/ip.go generated vendored Normal file
View File

@ -0,0 +1,337 @@
/*
Copyright 2023 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 library
import (
"fmt"
"net/netip"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
apiservercel "k8s.io/apiserver/pkg/cel"
)
// IP provides a CEL function library extension of IP address parsing functions.
//
// ip
//
// Converts a string to an IP address or results in an error if the string is not a valid IP address.
// The IP address must be an IPv4 or IPv6 address.
// IPv4-mapped IPv6 addresses (e.g. ::ffff:1.2.3.4) are not allowed.
// IP addresses with zones (e.g. fe80::1%eth0) are not allowed.
// Leading zeros in IPv4 address octets are not allowed.
//
// ip(<string>) <IPAddr>
//
// Examples:
//
// ip('127.0.0.1') // returns an IPv4 address
// ip('::1') // returns an IPv6 address
// ip('127.0.0.256') // error
// ip(':::1') // error
//
// isIP
//
// Returns true if a string is a valid IP address.
// The IP address must be an IPv4 or IPv6 address.
// IPv4-mapped IPv6 addresses (e.g. ::ffff:1.2.3.4) are not allowed.
// IP addresses with zones (e.g. fe80::1%eth0) are not allowed.
// Leading zeros in IPv4 address octets are not allowed.
//
// isIP(<string>) <bool>
//
// Examples:
//
// isIP('127.0.0.1') // returns true
// isIP('::1') // returns true
// isIP('127.0.0.256') // returns false
// isIP(':::1') // returns false
//
// ip.isCanonical
//
// Returns true if the IP address is in its canonical form.
// There is exactly one canonical form for every IP address, so fields containing
// IPs in canonical form can just be treated as strings when checking for equality or uniqueness.
//
// ip.isCanonical(<string>) <bool>
//
// Examples:
//
// ip.isCanonical('127.0.0.1') // returns true; all valid IPv4 addresses are canonical
// ip.isCanonical('2001:db8::abcd') // returns true
// ip.isCanonical('2001:DB8::ABCD') // returns false
// ip.isCanonical('2001:db8::0:0:0:abcd') // returns false
//
// family / isUnspecified / isLoopback / isLinkLocalMulticast / isLinkLocalUnicast / isGlobalUnicast
//
// - family: returns the IP addresses' family (IPv4 or IPv6) as an integer, either '4' or '6'.
//
// - isUnspecified: returns true if the IP address is the unspecified address.
// Either the IPv4 address "0.0.0.0" or the IPv6 address "::".
//
// - isLoopback: returns true if the IP address is the loopback address.
// Either an IPv4 address with a value of 127.x.x.x or an IPv6 address with a value of ::1.
//
// - isLinkLocalMulticast: returns true if the IP address is a link-local multicast address.
// Either an IPv4 address with a value of 224.0.0.x or an IPv6 address in the network ff00::/8.
//
// - isLinkLocalUnicast: returns true if the IP address is a link-local unicast address.
// Either an IPv4 address with a value of 169.254.x.x or an IPv6 address in the network fe80::/10.
//
// - isGlobalUnicast: returns true if the IP address is a global unicast address.
// Either an IPv4 address that is not zero or 255.255.255.255 or an IPv6 address that is not a link-local unicast, loopback or multicast address.
//
// Examples:
//
// ip('127.0.0.1').family() // returns '4”
// ip('::1').family() // returns '6'
// ip('127.0.0.1').family() == 4 // returns true
// ip('::1').family() == 6 // returns true
// ip('0.0.0.0').isUnspecified() // returns true
// ip('127.0.0.1').isUnspecified() // returns false
// ip('::').isUnspecified() // returns true
// ip('::1').isUnspecified() // returns false
// ip('127.0.0.1').isLoopback() // returns true
// ip('192.168.0.1').isLoopback() // returns false
// ip('::1').isLoopback() // returns true
// ip('2001:db8::abcd').isLoopback() // returns false
// ip('224.0.0.1').isLinkLocalMulticast() // returns true
// ip('224.0.1.1').isLinkLocalMulticast() // returns false
// ip('ff02::1').isLinkLocalMulticast() // returns true
// ip('fd00::1').isLinkLocalMulticast() // returns false
// ip('169.254.169.254').isLinkLocalUnicast() // returns true
// ip('192.168.0.1').isLinkLocalUnicast() // returns false
// ip('fe80::1').isLinkLocalUnicast() // returns true
// ip('fd80::1').isLinkLocalUnicast() // returns false
// ip('192.168.0.1').isGlobalUnicast() // returns true
// ip('255.255.255.255').isGlobalUnicast() // returns false
// ip('2001:db8::abcd').isGlobalUnicast() // returns true
// ip('ff00::1').isGlobalUnicast() // returns false
func IP() cel.EnvOption {
return cel.Lib(ipLib)
}
var ipLib = &ip{}
type ip struct{}
func (*ip) LibraryName() string {
return "kubernetes.net.ip"
}
func (*ip) declarations() map[string][]cel.FunctionOpt {
return ipLibraryDecls
}
func (*ip) Types() []*cel.Type {
return []*cel.Type{apiservercel.IPType}
}
var ipLibraryDecls = map[string][]cel.FunctionOpt{
"ip": {
cel.Overload("string_to_ip", []*cel.Type{cel.StringType}, apiservercel.IPType,
cel.UnaryBinding(stringToIP)),
},
"family": {
cel.MemberOverload("ip_family", []*cel.Type{apiservercel.IPType}, cel.IntType,
cel.UnaryBinding(family)),
},
"ip.isCanonical": {
cel.Overload("ip_is_canonical", []*cel.Type{cel.StringType}, cel.BoolType,
cel.UnaryBinding(ipIsCanonical)),
},
"isUnspecified": {
cel.MemberOverload("ip_is_unspecified", []*cel.Type{apiservercel.IPType}, cel.BoolType,
cel.UnaryBinding(isUnspecified)),
},
"isLoopback": {
cel.MemberOverload("ip_is_loopback", []*cel.Type{apiservercel.IPType}, cel.BoolType,
cel.UnaryBinding(isLoopback)),
},
"isLinkLocalMulticast": {
cel.MemberOverload("ip_is_link_local_multicast", []*cel.Type{apiservercel.IPType}, cel.BoolType,
cel.UnaryBinding(isLinkLocalMulticast)),
},
"isLinkLocalUnicast": {
cel.MemberOverload("ip_is_link_local_unicast", []*cel.Type{apiservercel.IPType}, cel.BoolType,
cel.UnaryBinding(isLinkLocalUnicast)),
},
"isGlobalUnicast": {
cel.MemberOverload("ip_is_global_unicast", []*cel.Type{apiservercel.IPType}, cel.BoolType,
cel.UnaryBinding(isGlobalUnicast)),
},
"isIP": {
cel.Overload("is_ip", []*cel.Type{cel.StringType}, cel.BoolType,
cel.UnaryBinding(isIP)),
},
"string": {
cel.Overload("ip_to_string", []*cel.Type{apiservercel.IPType}, cel.StringType,
cel.UnaryBinding(ipToString)),
},
}
func (*ip) CompileOptions() []cel.EnvOption {
options := []cel.EnvOption{cel.Types(apiservercel.IPType),
cel.Variable(apiservercel.IPType.TypeName(), types.NewTypeTypeWithParam(apiservercel.IPType)),
}
for name, overloads := range ipLibraryDecls {
options = append(options, cel.Function(name, overloads...))
}
return options
}
func (*ip) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{}
}
func stringToIP(arg ref.Val) ref.Val {
s, ok := arg.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
addr, err := parseIPAddr(s)
if err != nil {
// Don't add context, we control the error message already.
return types.NewErr("%v", err)
}
return apiservercel.IP{
Addr: addr,
}
}
func ipToString(arg ref.Val) ref.Val {
ip, ok := arg.(apiservercel.IP)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.String(ip.Addr.String())
}
func family(arg ref.Val) ref.Val {
ip, ok := arg.(apiservercel.IP)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
switch {
case ip.Addr.Is4():
return types.Int(4)
case ip.Addr.Is6():
return types.Int(6)
default:
return types.NewErr("IP address %q is not an IPv4 or IPv6 address", ip.Addr.String())
}
}
func ipIsCanonical(arg ref.Val) ref.Val {
s, ok := arg.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
addr, err := parseIPAddr(s)
if err != nil {
// Don't add context, we control the error message already.
return types.NewErr("%v", err)
}
// Addr.String() always returns the canonical form of the IP address.
// Therefore comparing this with the original string representation
// will tell us if the IP address is in its canonical form.
return types.Bool(addr.String() == s)
}
func isIP(arg ref.Val) ref.Val {
s, ok := arg.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
_, err := parseIPAddr(s)
return types.Bool(err == nil)
}
func isUnspecified(arg ref.Val) ref.Val {
ip, ok := arg.(apiservercel.IP)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Bool(ip.Addr.IsUnspecified())
}
func isLoopback(arg ref.Val) ref.Val {
ip, ok := arg.(apiservercel.IP)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Bool(ip.Addr.IsLoopback())
}
func isLinkLocalMulticast(arg ref.Val) ref.Val {
ip, ok := arg.(apiservercel.IP)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Bool(ip.Addr.IsLinkLocalMulticast())
}
func isLinkLocalUnicast(arg ref.Val) ref.Val {
ip, ok := arg.(apiservercel.IP)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Bool(ip.Addr.IsLinkLocalUnicast())
}
func isGlobalUnicast(arg ref.Val) ref.Val {
ip, ok := arg.(apiservercel.IP)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Bool(ip.Addr.IsGlobalUnicast())
}
// parseIPAddr parses a string into an IP address.
// We use this function to parse IP addresses in the CEL library
// so that we can share the common logic of rejecting IP addresses
// that contain zones or are IPv4-mapped IPv6 addresses.
func parseIPAddr(raw string) (netip.Addr, error) {
addr, err := netip.ParseAddr(raw)
if err != nil {
return netip.Addr{}, fmt.Errorf("IP Address %q parse error during conversion from string: %v", raw, err)
}
if addr.Zone() != "" {
return netip.Addr{}, fmt.Errorf("IP address %q with zone value is not allowed", raw)
}
if addr.Is4In6() {
return netip.Addr{}, fmt.Errorf("IPv4-mapped IPv6 address %q is not allowed", raw)
}
return addr, nil
}

View File

@ -0,0 +1,89 @@
/*
Copyright 2024 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 library
import (
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"strings"
)
// JSONPatch provides a CEL function library extension of JSONPatch functions.
//
// jsonpatch.escapeKey
//
// Escapes a string for use as a JSONPatch path key.
//
// jsonpatch.escapeKey(<string>) <string>
//
// Examples:
//
// "/metadata/labels/" + jsonpatch.escapeKey('k8s.io/my~label') // returns "/metadata/labels/k8s.io~1my~0label"
func JSONPatch() cel.EnvOption {
return cel.Lib(jsonPatchLib)
}
var jsonPatchLib = &jsonPatch{}
type jsonPatch struct{}
func (*jsonPatch) LibraryName() string {
return "kubernetes.jsonpatch"
}
func (*jsonPatch) declarations() map[string][]cel.FunctionOpt {
return jsonPatchLibraryDecls
}
func (*jsonPatch) Types() []*cel.Type {
return []*cel.Type{}
}
var jsonPatchLibraryDecls = map[string][]cel.FunctionOpt{
"jsonpatch.escapeKey": {
cel.Overload("string_jsonpatch_escapeKey_string", []*cel.Type{cel.StringType}, cel.StringType,
cel.UnaryBinding(escape)),
},
}
func (*jsonPatch) CompileOptions() []cel.EnvOption {
var options []cel.EnvOption
for name, overloads := range jsonPatchLibraryDecls {
options = append(options, cel.Function(name, overloads...))
}
return options
}
func (*jsonPatch) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{}
}
var jsonPatchReplacer = strings.NewReplacer("/", "~1", "~", "~0")
func escapeKey(k string) string {
return jsonPatchReplacer.Replace(k)
}
func escape(arg ref.Val) ref.Val {
s, ok := arg.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
escaped := escapeKey(s)
return types.String(escaped)
}

View File

@ -0,0 +1,61 @@
/*
Copyright 2024 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 library
import (
"github.com/google/cel-go/cel"
)
// Library represents a CEL library used by kubernetes.
type Library interface {
// SingletonLibrary provides the library name and ensures the library can be safely registered into environments.
cel.SingletonLibrary
// Types provides all custom types introduced by the library.
Types() []*cel.Type
// declarations returns all function declarations provided by the library.
declarations() map[string][]cel.FunctionOpt
}
// KnownLibraries returns all libraries used in Kubernetes.
func KnownLibraries() []Library {
return []Library{
authzLib,
authzSelectorsLib,
listsLib,
regexLib,
urlsLib,
quantityLib,
ipLib,
cidrsLib,
formatLib,
semverLib,
jsonPatchLib,
}
}
func isRegisteredType(typeName string) bool {
for _, lib := range KnownLibraries() {
for _, rt := range lib.Types() {
if rt.TypeName() == typeName {
return true
}
}
}
return false
}

324
e2e/vendor/k8s.io/apiserver/pkg/cel/library/lists.go generated vendored Normal file
View File

@ -0,0 +1,324 @@
/*
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 library
import (
"fmt"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
"github.com/google/cel-go/interpreter/functions"
)
// Lists provides a CEL function library extension of list utility functions.
//
// isSorted
//
// Returns true if the provided list of comparable elements is sorted, else returns false.
//
// <list<T>>.isSorted() <bool>, T must be a comparable type
//
// Examples:
//
// [1, 2, 3].isSorted() // return true
// ['a', 'b', 'b', 'c'].isSorted() // return true
// [2.0, 1.0].isSorted() // return false
// [1].isSorted() // return true
// [].isSorted() // return true
//
// sum
//
// Returns the sum of the elements of the provided list. Supports CEL number (int, uint, double) and duration types.
//
// <list<T>>.sum() <T>, T must be a numeric type or a duration
//
// Examples:
//
// [1, 3].sum() // returns 4
// [1.0, 3.0].sum() // returns 4.0
// ['1m', '1s'].sum() // returns '1m1s'
// emptyIntList.sum() // returns 0
// emptyDoubleList.sum() // returns 0.0
// [].sum() // returns 0
//
// min / max
//
// Returns the minimum/maximum valued element of the provided list. Supports all comparable types.
// If the list is empty, an error is returned.
//
// <list<T>>.min() <T>, T must be a comparable type
// <list<T>>.max() <T>, T must be a comparable type
//
// Examples:
//
// [1, 3].min() // returns 1
// [1, 3].max() // returns 3
// [].min() // error
// [1].min() // returns 1
// ([0] + emptyList).min() // returns 0
//
// indexOf / lastIndexOf
//
// Returns either the first or last positional index of the provided element in the list.
// If the element is not found, -1 is returned. Supports all equatable types.
//
// <list<T>>.indexOf(<T>) <int>, T must be an equatable type
// <list<T>>.lastIndexOf(<T>) <int>, T must be an equatable type
//
// Examples:
//
// [1, 2, 2, 3].indexOf(2) // returns 1
// ['a', 'b', 'b', 'c'].lastIndexOf('b') // returns 2
// [1.0].indexOf(1.1) // returns -1
// [].indexOf('string') // returns -1
func Lists() cel.EnvOption {
return cel.Lib(listsLib)
}
var listsLib = &lists{}
type lists struct{}
func (*lists) LibraryName() string {
return "kubernetes.lists"
}
func (*lists) Types() []*cel.Type {
return []*cel.Type{}
}
func (*lists) declarations() map[string][]cel.FunctionOpt {
return listsLibraryDecls
}
var paramA = cel.TypeParamType("A")
// CEL typeParams can be used to constraint to a specific trait (e.g. traits.ComparableType) if the 1st operand is the type to constrain.
// But the functions we need to constrain are <list<paramType>>, not just <paramType>.
// Make sure the order of overload set is deterministic
type namedCELType struct {
typeName string
celType *cel.Type
}
var summableTypes = []namedCELType{
{typeName: "int", celType: cel.IntType},
{typeName: "uint", celType: cel.UintType},
{typeName: "double", celType: cel.DoubleType},
{typeName: "duration", celType: cel.DurationType},
}
var zeroValuesOfSummableTypes = map[string]ref.Val{
"int": types.Int(0),
"uint": types.Uint(0),
"double": types.Double(0.0),
"duration": types.Duration{Duration: 0},
}
var comparableTypes = []namedCELType{
{typeName: "int", celType: cel.IntType},
{typeName: "uint", celType: cel.UintType},
{typeName: "double", celType: cel.DoubleType},
{typeName: "bool", celType: cel.BoolType},
{typeName: "duration", celType: cel.DurationType},
{typeName: "timestamp", celType: cel.TimestampType},
{typeName: "string", celType: cel.StringType},
{typeName: "bytes", celType: cel.BytesType},
}
// WARNING: All library additions or modifications must follow
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/2876-crd-validation-expression-language#function-library-updates
var listsLibraryDecls = map[string][]cel.FunctionOpt{
"isSorted": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
return cel.MemberOverload(fmt.Sprintf("list_%s_is_sorted_bool", name),
[]*cel.Type{cel.ListType(paramType)}, cel.BoolType, cel.UnaryBinding(isSorted))
}),
"sum": templatedOverloads(summableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
return cel.MemberOverload(fmt.Sprintf("list_%s_sum_%s", name, name),
[]*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(func(list ref.Val) ref.Val {
return sum(
func() ref.Val {
return zeroValuesOfSummableTypes[name]
})(list)
}))
}),
"max": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
return cel.MemberOverload(fmt.Sprintf("list_%s_max_%s", name, name),
[]*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(max()))
}),
"min": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt {
return cel.MemberOverload(fmt.Sprintf("list_%s_min_%s", name, name),
[]*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(min()))
}),
"indexOf": {
cel.MemberOverload("list_a_index_of_int", []*cel.Type{cel.ListType(paramA), paramA}, cel.IntType,
cel.BinaryBinding(indexOf)),
},
"lastIndexOf": {
cel.MemberOverload("list_a_last_index_of_int", []*cel.Type{cel.ListType(paramA), paramA}, cel.IntType,
cel.BinaryBinding(lastIndexOf)),
},
}
func (*lists) CompileOptions() []cel.EnvOption {
options := []cel.EnvOption{}
for name, overloads := range listsLibraryDecls {
options = append(options, cel.Function(name, overloads...))
}
return options
}
func (*lists) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{}
}
func isSorted(val ref.Val) ref.Val {
var prev traits.Comparer
iterable, ok := val.(traits.Iterable)
if !ok {
return types.MaybeNoSuchOverloadErr(val)
}
for it := iterable.Iterator(); it.HasNext() == types.True; {
next := it.Next()
nextCmp, ok := next.(traits.Comparer)
if !ok {
return types.MaybeNoSuchOverloadErr(next)
}
if prev != nil {
cmp := prev.Compare(next)
if cmp == types.IntOne {
return types.False
}
}
prev = nextCmp
}
return types.True
}
func sum(init func() ref.Val) functions.UnaryOp {
return func(val ref.Val) ref.Val {
i := init()
acc, ok := i.(traits.Adder)
if !ok {
// Should never happen since all passed in init values are valid
return types.MaybeNoSuchOverloadErr(i)
}
iterable, ok := val.(traits.Iterable)
if !ok {
return types.MaybeNoSuchOverloadErr(val)
}
for it := iterable.Iterator(); it.HasNext() == types.True; {
next := it.Next()
nextAdder, ok := next.(traits.Adder)
if !ok {
// Should never happen for type checked CEL programs
return types.MaybeNoSuchOverloadErr(next)
}
if acc != nil {
s := acc.Add(next)
sum, ok := s.(traits.Adder)
if !ok {
// Should never happen for type checked CEL programs
return types.MaybeNoSuchOverloadErr(s)
}
acc = sum
} else {
acc = nextAdder
}
}
return acc.(ref.Val)
}
}
func min() functions.UnaryOp {
return cmp("min", types.IntOne)
}
func max() functions.UnaryOp {
return cmp("max", types.IntNegOne)
}
func cmp(opName string, opPreferCmpResult ref.Val) functions.UnaryOp {
return func(val ref.Val) ref.Val {
var result traits.Comparer
iterable, ok := val.(traits.Iterable)
if !ok {
return types.MaybeNoSuchOverloadErr(val)
}
for it := iterable.Iterator(); it.HasNext() == types.True; {
next := it.Next()
nextCmp, ok := next.(traits.Comparer)
if !ok {
// Should never happen for type checked CEL programs
return types.MaybeNoSuchOverloadErr(next)
}
if result == nil {
result = nextCmp
} else {
cmp := result.Compare(next)
if cmp == opPreferCmpResult {
result = nextCmp
}
}
}
if result == nil {
return types.NewErr("%s called on empty list", opName)
}
return result.(ref.Val)
}
}
func indexOf(list ref.Val, item ref.Val) ref.Val {
lister, ok := list.(traits.Lister)
if !ok {
return types.MaybeNoSuchOverloadErr(list)
}
sz := lister.Size().(types.Int)
for i := types.Int(0); i < sz; i++ {
if lister.Get(types.Int(i)).Equal(item) == types.True {
return types.Int(i)
}
}
return types.Int(-1)
}
func lastIndexOf(list ref.Val, item ref.Val) ref.Val {
lister, ok := list.(traits.Lister)
if !ok {
return types.MaybeNoSuchOverloadErr(list)
}
sz := lister.Size().(types.Int)
for i := sz - 1; i >= 0; i-- {
if lister.Get(types.Int(i)).Equal(item) == types.True {
return types.Int(i)
}
}
return types.Int(-1)
}
// templatedOverloads returns overloads for each of the provided types. The template function is called with each type
// name (map key) and type to construct the overloads.
func templatedOverloads(types []namedCELType, template func(name string, t *cel.Type) cel.FunctionOpt) []cel.FunctionOpt {
overloads := make([]cel.FunctionOpt, len(types))
i := 0
for _, t := range types {
overloads[i] = template(t.typeName, t.celType)
i++
}
return overloads
}

388
e2e/vendor/k8s.io/apiserver/pkg/cel/library/quantity.go generated vendored Normal file
View File

@ -0,0 +1,388 @@
/*
Copyright 2023 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 library
import (
"errors"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"k8s.io/apimachinery/pkg/api/resource"
apiservercel "k8s.io/apiserver/pkg/cel"
)
// Quantity provides a CEL function library extension of Kubernetes
// resource.Quantity parsing functions. See `resource.Quantity`
// documentation for more detailed information about the format itself:
// https://pkg.go.dev/k8s.io/apimachinery/pkg/api/resource#Quantity
//
// quantity
//
// Converts a string to a Quantity or results in an error if the string is not a valid Quantity. Refer
// to resource.Quantity documentation for information on accepted patterns.
//
// quantity(<string>) <Quantity>
//
// Examples:
//
// quantity('1.5G') // returns a Quantity
// quantity('200k') // returns a Quantity
// quantity('200K') // error
// quantity('Three') // error
// quantity('Mi') // error
//
// isQuantity
//
// Returns true if a string is a valid Quantity. isQuantity returns true if and
// only if quantity does not result in error.
//
// isQuantity( <string>) <bool>
//
// Examples:
//
// isQuantity('1.3G') // returns true
// isQuantity('1.3Gi') // returns true
// isQuantity('1,3G') // returns false
// isQuantity('10000k') // returns true
// isQuantity('200K') // returns false
// isQuantity('Three') // returns false
// isQuantity('Mi') // returns false
//
// Conversion to Scalars:
//
// - isInteger: returns true if and only if asInteger is safe to call without an error
//
// - asInteger: returns a representation of the current value as an int64 if
// possible or results in an error if conversion would result in overflow
// or loss of precision.
//
// - asApproximateFloat: returns a float64 representation of the quantity which may
// lose precision. If the value of the quantity is outside the range of a float64
// +Inf/-Inf will be returned.
//
// <Quantity>.isInteger() <bool>
// <Quantity>.asInteger() <int>
// <Quantity>.asApproximateFloat() <float>
//
// Examples:
//
// quantity("50000000G").isInteger() // returns true
// quantity("50k").isInteger() // returns true
// quantity("9999999999999999999999999999999999999G").asInteger() // error: cannot convert value to integer
// quantity("9999999999999999999999999999999999999G").isInteger() // returns false
// quantity("50k").asInteger() == 50000 // returns true
// quantity("50k").sub(20000).asApproximateFloat() == 30000 // returns true
//
// Arithmetic
//
// - sign: Returns `1` if the quantity is positive, `-1` if it is negative. `0` if it is zero
//
// - add: Returns sum of two quantities or a quantity and an integer
//
// - sub: Returns difference between two quantities or a quantity and an integer
//
// <Quantity>.sign() <int>
// <Quantity>.add(<quantity>) <quantity>
// <Quantity>.add(<integer>) <quantity>
// <Quantity>.sub(<quantity>) <quantity>
// <Quantity>.sub(<integer>) <quantity>
//
// Examples:
//
// quantity("50k").add("20k") == quantity("70k") // returns true
// quantity("50k").add(20) == quantity("50020") // returns true
// quantity("50k").sub("20k") == quantity("30k") // returns true
// quantity("50k").sub(20000) == quantity("30k") // returns true
// quantity("50k").add(20).sub(quantity("100k")).sub(-50000) == quantity("20") // returns true
//
// Comparisons
//
// - isGreaterThan: Returns true if and only if the receiver is greater than the operand
//
// - isLessThan: Returns true if and only if the receiver is less than the operand
//
// - compareTo: Compares receiver to operand and returns 0 if they are equal, 1 if the receiver is greater, or -1 if the receiver is less than the operand
//
//
// <Quantity>.isLessThan(<quantity>) <bool>
// <Quantity>.isGreaterThan(<quantity>) <bool>
// <Quantity>.compareTo(<quantity>) <int>
//
// Examples:
//
// quantity("200M").compareTo(quantity("0.2G")) // returns 0
// quantity("50M").compareTo(quantity("50Mi")) // returns -1
// quantity("50Mi").compareTo(quantity("50M")) // returns 1
// quantity("150Mi").isGreaterThan(quantity("100Mi")) // returns true
// quantity("50Mi").isGreaterThan(quantity("100Mi")) // returns false
// quantity("50M").isLessThan(quantity("100M")) // returns true
// quantity("100M").isLessThan(quantity("50M")) // returns false
func Quantity() cel.EnvOption {
return cel.Lib(quantityLib)
}
var quantityLib = &quantity{}
type quantity struct{}
func (*quantity) LibraryName() string {
return "kubernetes.quantity"
}
func (*quantity) Types() []*cel.Type {
return []*cel.Type{apiservercel.QuantityType}
}
func (*quantity) declarations() map[string][]cel.FunctionOpt {
return quantityLibraryDecls
}
var quantityLibraryDecls = map[string][]cel.FunctionOpt{
"quantity": {
cel.Overload("string_to_quantity", []*cel.Type{cel.StringType}, apiservercel.QuantityType, cel.UnaryBinding((stringToQuantity))),
},
"isQuantity": {
cel.Overload("is_quantity_string", []*cel.Type{cel.StringType}, cel.BoolType, cel.UnaryBinding(isQuantity)),
},
"sign": {
cel.Overload("quantity_sign", []*cel.Type{apiservercel.QuantityType}, cel.IntType, cel.UnaryBinding(quantityGetSign)),
},
"isGreaterThan": {
cel.MemberOverload("quantity_is_greater_than", []*cel.Type{apiservercel.QuantityType, apiservercel.QuantityType}, cel.BoolType, cel.BinaryBinding(quantityIsGreaterThan)),
},
"isLessThan": {
cel.MemberOverload("quantity_is_less_than", []*cel.Type{apiservercel.QuantityType, apiservercel.QuantityType}, cel.BoolType, cel.BinaryBinding(quantityIsLessThan)),
},
"compareTo": {
cel.MemberOverload("quantity_compare_to", []*cel.Type{apiservercel.QuantityType, apiservercel.QuantityType}, cel.IntType, cel.BinaryBinding(quantityCompareTo)),
},
"asApproximateFloat": {
cel.MemberOverload("quantity_get_float", []*cel.Type{apiservercel.QuantityType}, cel.DoubleType, cel.UnaryBinding(quantityGetApproximateFloat)),
},
"asInteger": {
cel.MemberOverload("quantity_get_int", []*cel.Type{apiservercel.QuantityType}, cel.IntType, cel.UnaryBinding(quantityGetValue)),
},
"isInteger": {
cel.MemberOverload("quantity_is_integer", []*cel.Type{apiservercel.QuantityType}, cel.BoolType, cel.UnaryBinding(quantityCanValue)),
},
"add": {
cel.MemberOverload("quantity_add", []*cel.Type{apiservercel.QuantityType, apiservercel.QuantityType}, apiservercel.QuantityType, cel.BinaryBinding(quantityAdd)),
cel.MemberOverload("quantity_add_int", []*cel.Type{apiservercel.QuantityType, cel.IntType}, apiservercel.QuantityType, cel.BinaryBinding(quantityAddInt)),
},
"sub": {
cel.MemberOverload("quantity_sub", []*cel.Type{apiservercel.QuantityType, apiservercel.QuantityType}, apiservercel.QuantityType, cel.BinaryBinding(quantitySub)),
cel.MemberOverload("quantity_sub_int", []*cel.Type{apiservercel.QuantityType, cel.IntType}, apiservercel.QuantityType, cel.BinaryBinding(quantitySubInt)),
},
}
func (*quantity) CompileOptions() []cel.EnvOption {
options := make([]cel.EnvOption, 0, len(quantityLibraryDecls))
for name, overloads := range quantityLibraryDecls {
options = append(options, cel.Function(name, overloads...))
}
return options
}
func (*quantity) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{}
}
func isQuantity(arg ref.Val) ref.Val {
str, ok := arg.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
_, err := resource.ParseQuantity(str)
if err != nil {
return types.Bool(false)
}
return types.Bool(true)
}
func stringToQuantity(arg ref.Val) ref.Val {
str, ok := arg.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
q, err := resource.ParseQuantity(str)
if err != nil {
return types.WrapErr(err)
}
return apiservercel.Quantity{Quantity: &q}
}
func quantityGetApproximateFloat(arg ref.Val) ref.Val {
q, ok := arg.Value().(*resource.Quantity)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Double(q.AsApproximateFloat64())
}
func quantityCanValue(arg ref.Val) ref.Val {
q, ok := arg.Value().(*resource.Quantity)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
_, success := q.AsInt64()
return types.Bool(success)
}
func quantityGetValue(arg ref.Val) ref.Val {
q, ok := arg.Value().(*resource.Quantity)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
v, success := q.AsInt64()
if !success {
return types.WrapErr(errors.New("cannot convert value to integer"))
}
return types.Int(v)
}
func quantityGetSign(arg ref.Val) ref.Val {
q, ok := arg.Value().(*resource.Quantity)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Int(q.Sign())
}
func quantityIsGreaterThan(arg ref.Val, other ref.Val) ref.Val {
q, ok := arg.Value().(*resource.Quantity)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
q2, ok := other.Value().(*resource.Quantity)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Bool(q.Cmp(*q2) == 1)
}
func quantityIsLessThan(arg ref.Val, other ref.Val) ref.Val {
q, ok := arg.Value().(*resource.Quantity)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
q2, ok := other.Value().(*resource.Quantity)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Bool(q.Cmp(*q2) == -1)
}
func quantityCompareTo(arg ref.Val, other ref.Val) ref.Val {
q, ok := arg.Value().(*resource.Quantity)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
q2, ok := other.Value().(*resource.Quantity)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Int(q.Cmp(*q2))
}
func quantityAdd(arg ref.Val, other ref.Val) ref.Val {
q, ok := arg.Value().(*resource.Quantity)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
q2, ok := other.Value().(*resource.Quantity)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
copy := *q
copy.Add(*q2)
return &apiservercel.Quantity{
Quantity: &copy,
}
}
func quantityAddInt(arg ref.Val, other ref.Val) ref.Val {
q, ok := arg.Value().(*resource.Quantity)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
q2, ok := other.Value().(int64)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
q2Converted := *resource.NewQuantity(q2, resource.DecimalExponent)
copy := *q
copy.Add(q2Converted)
return &apiservercel.Quantity{
Quantity: &copy,
}
}
func quantitySub(arg ref.Val, other ref.Val) ref.Val {
q, ok := arg.Value().(*resource.Quantity)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
q2, ok := other.Value().(*resource.Quantity)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
copy := *q
copy.Sub(*q2)
return &apiservercel.Quantity{
Quantity: &copy,
}
}
func quantitySubInt(arg ref.Val, other ref.Val) ref.Val {
q, ok := arg.Value().(*resource.Quantity)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
q2, ok := other.Value().(int64)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
q2Converted := *resource.NewQuantity(q2, resource.DecimalExponent)
copy := *q
copy.Sub(q2Converted)
return &apiservercel.Quantity{
Quantity: &copy,
}
}

201
e2e/vendor/k8s.io/apiserver/pkg/cel/library/regex.go generated vendored Normal file
View File

@ -0,0 +1,201 @@
/*
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 library
import (
"regexp"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter"
)
// Regex provides a CEL function library extension of regex utility functions.
//
// find / findAll
//
// Returns substrings that match the provided regular expression. find returns the first match. findAll may optionally
// be provided a limit. If the limit is set and >= 0, no more than the limit number of matches are returned.
//
// <string>.find(<string>) <string>
// <string>.findAll(<string>) <list <string>>
// <string>.findAll(<string>, <int>) <list <string>>
//
// Examples:
//
// "abc 123".find('[0-9]*') // returns '123'
// "abc 123".find('xyz') // returns ''
// "123 abc 456".findAll('[0-9]*') // returns ['123', '456']
// "123 abc 456".findAll('[0-9]*', 1) // returns ['123']
// "123 abc 456".findAll('xyz') // returns []
func Regex() cel.EnvOption {
return cel.Lib(regexLib)
}
var regexLib = &regex{}
type regex struct{}
func (*regex) LibraryName() string {
return "kubernetes.regex"
}
func (*regex) Types() []*cel.Type {
return []*cel.Type{}
}
func (*regex) declarations() map[string][]cel.FunctionOpt {
return regexLibraryDecls
}
var regexLibraryDecls = map[string][]cel.FunctionOpt{
"find": {
cel.MemberOverload("string_find_string", []*cel.Type{cel.StringType, cel.StringType}, cel.StringType,
cel.BinaryBinding(find))},
"findAll": {
cel.MemberOverload("string_find_all_string", []*cel.Type{cel.StringType, cel.StringType},
cel.ListType(cel.StringType),
cel.BinaryBinding(func(str, regex ref.Val) ref.Val {
return findAll(str, regex, types.Int(-1))
})),
cel.MemberOverload("string_find_all_string_int",
[]*cel.Type{cel.StringType, cel.StringType, cel.IntType},
cel.ListType(cel.StringType),
cel.FunctionBinding(findAll)),
},
}
func (*regex) CompileOptions() []cel.EnvOption {
options := []cel.EnvOption{}
for name, overloads := range regexLibraryDecls {
options = append(options, cel.Function(name, overloads...))
}
return options
}
func (*regex) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{
cel.OptimizeRegex(FindRegexOptimization, FindAllRegexOptimization),
}
}
func find(strVal ref.Val, regexVal ref.Val) ref.Val {
str, ok := strVal.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(strVal)
}
regex, ok := regexVal.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(regexVal)
}
re, err := regexp.Compile(regex)
if err != nil {
return types.NewErr("Illegal regex: %v", err.Error())
}
result := re.FindString(str)
return types.String(result)
}
func findAll(args ...ref.Val) ref.Val {
argn := len(args)
if argn < 2 || argn > 3 {
return types.NoSuchOverloadErr()
}
str, ok := args[0].Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(args[0])
}
regex, ok := args[1].Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(args[1])
}
n := int64(-1)
if argn == 3 {
n, ok = args[2].Value().(int64)
if !ok {
return types.MaybeNoSuchOverloadErr(args[2])
}
}
re, err := regexp.Compile(regex)
if err != nil {
return types.NewErr("Illegal regex: %v", err.Error())
}
result := re.FindAllString(str, int(n))
return types.NewStringList(types.DefaultTypeAdapter, result)
}
// FindRegexOptimization optimizes the 'find' function by compiling the regex pattern and
// reporting any compilation errors at program creation time, and using the compiled regex pattern for all function
// call invocations.
var FindRegexOptimization = &interpreter.RegexOptimization{
Function: "find",
RegexIndex: 1,
Factory: func(call interpreter.InterpretableCall, regexPattern string) (interpreter.InterpretableCall, error) {
compiledRegex, err := regexp.Compile(regexPattern)
if err != nil {
return nil, err
}
return interpreter.NewCall(call.ID(), call.Function(), call.OverloadID(), call.Args(), func(args ...ref.Val) ref.Val {
if len(args) != 2 {
return types.NoSuchOverloadErr()
}
in, ok := args[0].Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(args[0])
}
return types.String(compiledRegex.FindString(in))
}), nil
},
}
// FindAllRegexOptimization optimizes the 'findAll' function by compiling the regex pattern and
// reporting any compilation errors at program creation time, and using the compiled regex pattern for all function
// call invocations.
var FindAllRegexOptimization = &interpreter.RegexOptimization{
Function: "findAll",
RegexIndex: 1,
Factory: func(call interpreter.InterpretableCall, regexPattern string) (interpreter.InterpretableCall, error) {
compiledRegex, err := regexp.Compile(regexPattern)
if err != nil {
return nil, err
}
return interpreter.NewCall(call.ID(), call.Function(), call.OverloadID(), call.Args(), func(args ...ref.Val) ref.Val {
argn := len(args)
if argn < 2 || argn > 3 {
return types.NoSuchOverloadErr()
}
str, ok := args[0].Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(args[0])
}
n := int64(-1)
if argn == 3 {
n, ok = args[2].Value().(int64)
if !ok {
return types.MaybeNoSuchOverloadErr(args[2])
}
}
result := compiledRegex.FindAllString(str, int(n))
return types.NewStringList(types.DefaultTypeAdapter, result)
}), nil
},
}

View File

@ -0,0 +1,247 @@
/*
Copyright 2024 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 library
import (
"github.com/blang/semver/v4"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
apiservercel "k8s.io/apiserver/pkg/cel"
)
// Semver provides a CEL function library extension for [semver.Version].
//
// semver
//
// Converts a string to a semantic version or results in an error if the string is not a valid semantic version. Refer
// to semver.org documentation for information on accepted patterns.
//
// semver(<string>) <Semver>
//
// Examples:
//
// semver('1.0.0') // returns a Semver
// semver('0.1.0-alpha.1') // returns a Semver
// semver('200K') // error
// semver('Three') // error
// semver('Mi') // error
//
// isSemver
//
// Returns true if a string is a valid Semver. isSemver returns true if and
// only if semver does not result in error.
//
// isSemver( <string>) <bool>
//
// Examples:
//
// isSemver('1.0.0') // returns true
// isSemver('v1.0') // returns true (tolerant parsing)
// isSemver('hello') // returns false
//
// Conversion to Scalars:
//
// - major/minor/patch: return the major version number as int64.
//
// <Semver>.major() <int>
//
// Examples:
//
// semver("1.2.3").major() // returns 1
//
// Comparisons
//
// - isGreaterThan: Returns true if and only if the receiver is greater than the operand
//
// - isLessThan: Returns true if and only if the receiver is less than the operand
//
// - compareTo: Compares receiver to operand and returns 0 if they are equal, 1 if the receiver is greater, or -1 if the receiver is less than the operand
//
//
// <Semver>.isLessThan(<semver>) <bool>
// <Semver>.isGreaterThan(<semver>) <bool>
// <Semver>.compareTo(<semver>) <int>
//
// Examples:
//
// semver("1.2.3").compareTo(semver("1.2.3")) // returns 0
// semver("1.2.3").compareTo(semver("2.0.0")) // returns -1
// semver("1.2.3").compareTo(semver("0.1.2")) // returns 1
func SemverLib() cel.EnvOption {
return cel.Lib(semverLib)
}
var semverLib = &semverLibType{}
type semverLibType struct{}
func (*semverLibType) LibraryName() string {
return "kubernetes.Semver"
}
func (*semverLibType) Types() []*cel.Type {
return []*cel.Type{apiservercel.SemverType}
}
func (*semverLibType) declarations() map[string][]cel.FunctionOpt {
return map[string][]cel.FunctionOpt{
"semver": {
cel.Overload("string_to_semver", []*cel.Type{cel.StringType}, apiservercel.SemverType, cel.UnaryBinding((stringToSemver))),
},
"isSemver": {
cel.Overload("is_semver_string", []*cel.Type{cel.StringType}, cel.BoolType, cel.UnaryBinding(isSemver)),
},
"isGreaterThan": {
cel.MemberOverload("semver_is_greater_than", []*cel.Type{apiservercel.SemverType, apiservercel.SemverType}, cel.BoolType, cel.BinaryBinding(semverIsGreaterThan)),
},
"isLessThan": {
cel.MemberOverload("semver_is_less_than", []*cel.Type{apiservercel.SemverType, apiservercel.SemverType}, cel.BoolType, cel.BinaryBinding(semverIsLessThan)),
},
"compareTo": {
cel.MemberOverload("semver_compare_to", []*cel.Type{apiservercel.SemverType, apiservercel.SemverType}, cel.IntType, cel.BinaryBinding(semverCompareTo)),
},
"major": {
cel.MemberOverload("semver_major", []*cel.Type{apiservercel.SemverType}, cel.IntType, cel.UnaryBinding(semverMajor)),
},
"minor": {
cel.MemberOverload("semver_minor", []*cel.Type{apiservercel.SemverType}, cel.IntType, cel.UnaryBinding(semverMinor)),
},
"patch": {
cel.MemberOverload("semver_patch", []*cel.Type{apiservercel.SemverType}, cel.IntType, cel.UnaryBinding(semverPatch)),
},
}
}
func (s *semverLibType) CompileOptions() []cel.EnvOption {
// Defined in this function to avoid an initialization order problem.
semverLibraryDecls := s.declarations()
options := make([]cel.EnvOption, 0, len(semverLibraryDecls))
for name, overloads := range semverLibraryDecls {
options = append(options, cel.Function(name, overloads...))
}
return options
}
func (*semverLibType) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{}
}
func isSemver(arg ref.Val) ref.Val {
str, ok := arg.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
// Using semver/v4 here is okay because this function isn't
// used to validate the Kubernetes API. In the CEL base library
// we would have to use the regular expression from
// pkg/apis/resource/structured/namedresources/validation/validation.go.
_, err := semver.Parse(str)
if err != nil {
return types.Bool(false)
}
return types.Bool(true)
}
func stringToSemver(arg ref.Val) ref.Val {
str, ok := arg.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
// Using semver/v4 here is okay because this function isn't
// used to validate the Kubernetes API. In the CEL base library
// we would have to use the regular expression from
// pkg/apis/resource/structured/namedresources/validation/validation.go
// first before parsing.
v, err := semver.Parse(str)
if err != nil {
return types.WrapErr(err)
}
return apiservercel.Semver{Version: v}
}
func semverMajor(arg ref.Val) ref.Val {
v, ok := arg.Value().(semver.Version)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Int(v.Major)
}
func semverMinor(arg ref.Val) ref.Val {
v, ok := arg.Value().(semver.Version)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Int(v.Minor)
}
func semverPatch(arg ref.Val) ref.Val {
v, ok := arg.Value().(semver.Version)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Int(v.Patch)
}
func semverIsGreaterThan(arg ref.Val, other ref.Val) ref.Val {
v, ok := arg.Value().(semver.Version)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
v2, ok := other.Value().(semver.Version)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Bool(v.Compare(v2) == 1)
}
func semverIsLessThan(arg ref.Val, other ref.Val) ref.Val {
v, ok := arg.Value().(semver.Version)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
v2, ok := other.Value().(semver.Version)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Bool(v.Compare(v2) == -1)
}
func semverCompareTo(arg ref.Val, other ref.Val) ref.Val {
v, ok := arg.Value().(semver.Version)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
v2, ok := other.Value().(semver.Version)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.Int(v.Compare(v2))
}

83
e2e/vendor/k8s.io/apiserver/pkg/cel/library/test.go generated vendored Normal file
View File

@ -0,0 +1,83 @@
/*
Copyright 2023 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 library
import (
"math"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
)
// Test provides a test() function that returns true.
func Test(options ...TestOption) cel.EnvOption {
t := &testLib{version: math.MaxUint32}
for _, o := range options {
t = o(t)
}
return cel.Lib(t)
}
type testLib struct {
version uint32
}
func (*testLib) LibraryName() string {
return "kubernetes.test"
}
type TestOption func(*testLib) *testLib
func TestVersion(version uint32) func(lib *testLib) *testLib {
return func(sl *testLib) *testLib {
sl.version = version
return sl
}
}
func (t *testLib) CompileOptions() []cel.EnvOption {
var options []cel.EnvOption
if t.version == 0 {
options = append(options, cel.Function("test",
cel.Overload("test", []*cel.Type{}, cel.BoolType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
return types.True
}))))
}
if t.version >= 1 {
options = append(options, cel.Function("test",
cel.Overload("test", []*cel.Type{}, cel.BoolType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
// Return false here so tests can observe which version of the function is registered
// Actual function libraries must not break backward compatibility
return types.False
}))))
options = append(options, cel.Function("testV1",
cel.Overload("testV1", []*cel.Type{}, cel.BoolType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
return types.True
}))))
}
return options
}
func (*testLib) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{}
}

248
e2e/vendor/k8s.io/apiserver/pkg/cel/library/urls.go generated vendored Normal file
View File

@ -0,0 +1,248 @@
/*
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 library
import (
"net/url"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
apiservercel "k8s.io/apiserver/pkg/cel"
)
// URLs provides a CEL function library extension of URL parsing functions.
//
// url
//
// Converts a string to a URL or results in an error if the string is not a valid URL. The URL must be an absolute URI
// or an absolute path.
//
// url(<string>) <URL>
//
// Examples:
//
// url('https://user:pass@example.com:80/path?query=val#fragment') // returns a URL
// url('/absolute-path') // returns a URL
// url('https://a:b:c/') // error
// url('../relative-path') // error
//
// isURL
//
// Returns true if a string is a valid URL. The URL must be an absolute URI or an absolute path.
//
// isURL( <string>) <bool>
//
// Examples:
//
// isURL('https://user:pass@example.com:80/path?query=val#fragment') // returns true
// isURL('/absolute-path') // returns true
// isURL('https://a:b:c/') // returns false
// isURL('../relative-path') // returns false
//
// getScheme / getHost / getHostname / getPort / getEscapedPath / getQuery
//
// Return the parsed components of a URL.
//
// - getScheme: If absent in the URL, returns an empty string.
//
// - getHostname: IPv6 addresses are returned without braces, e.g. "::1". If absent in the URL, returns an empty string.
//
// - getHost: IPv6 addresses are returned with braces, e.g. "[::1]". If absent in the URL, returns an empty string.
//
// - getEscapedPath: The string returned by getEscapedPath is URL escaped, e.g. "with space" becomes "with%20space".
// If absent in the URL, returns an empty string.
//
// - getPort: If absent in the URL, returns an empty string.
//
// - getQuery: Returns the query parameters in "matrix" form where a repeated query key is interpreted to
// mean that there are multiple values for that key. The keys and values are returned unescaped.
// If absent in the URL, returns an empty map.
//
// <URL>.getScheme() <string>
// <URL>.getHost() <string>
// <URL>.getHostname() <string>
// <URL>.getPort() <string>
// <URL>.getEscapedPath() <string>
// <URL>.getQuery() <map <string>, <list <string>>
//
// Examples:
//
// url('/path').getScheme() // returns ''
// url('https://example.com/').getScheme() // returns 'https'
// url('https://example.com:80/').getHost() // returns 'example.com:80'
// url('https://example.com/').getHost() // returns 'example.com'
// url('https://[::1]:80/').getHost() // returns '[::1]:80'
// url('https://[::1]/').getHost() // returns '[::1]'
// url('/path').getHost() // returns ''
// url('https://example.com:80/').getHostname() // returns 'example.com'
// url('https://127.0.0.1:80/').getHostname() // returns '127.0.0.1'
// url('https://[::1]:80/').getHostname() // returns '::1'
// url('/path').getHostname() // returns ''
// url('https://example.com:80/').getPort() // returns '80'
// url('https://example.com/').getPort() // returns ''
// url('/path').getPort() // returns ''
// url('https://example.com/path').getEscapedPath() // returns '/path'
// url('https://example.com/path with spaces/').getEscapedPath() // returns '/path%20with%20spaces/'
// url('https://example.com').getEscapedPath() // returns ''
// url('https://example.com/path?k1=a&k2=b&k2=c').getQuery() // returns { 'k1': ['a'], 'k2': ['b', 'c']}
// url('https://example.com/path?key with spaces=value with spaces').getQuery() // returns { 'key with spaces': ['value with spaces']}
// url('https://example.com/path?').getQuery() // returns {}
// url('https://example.com/path').getQuery() // returns {}
func URLs() cel.EnvOption {
return cel.Lib(urlsLib)
}
var urlsLib = &urls{}
type urls struct{}
func (*urls) LibraryName() string {
return "kubernetes.urls"
}
func (*urls) Types() []*cel.Type {
return []*cel.Type{apiservercel.URLType}
}
func (*urls) declarations() map[string][]cel.FunctionOpt {
return urlLibraryDecls
}
var urlLibraryDecls = map[string][]cel.FunctionOpt{
"url": {
cel.Overload("string_to_url", []*cel.Type{cel.StringType}, apiservercel.URLType,
cel.UnaryBinding(stringToUrl))},
"getScheme": {
cel.MemberOverload("url_get_scheme", []*cel.Type{apiservercel.URLType}, cel.StringType,
cel.UnaryBinding(getScheme))},
"getHost": {
cel.MemberOverload("url_get_host", []*cel.Type{apiservercel.URLType}, cel.StringType,
cel.UnaryBinding(getHost))},
"getHostname": {
cel.MemberOverload("url_get_hostname", []*cel.Type{apiservercel.URLType}, cel.StringType,
cel.UnaryBinding(getHostname))},
"getPort": {
cel.MemberOverload("url_get_port", []*cel.Type{apiservercel.URLType}, cel.StringType,
cel.UnaryBinding(getPort))},
"getEscapedPath": {
cel.MemberOverload("url_get_escaped_path", []*cel.Type{apiservercel.URLType}, cel.StringType,
cel.UnaryBinding(getEscapedPath))},
"getQuery": {
cel.MemberOverload("url_get_query", []*cel.Type{apiservercel.URLType},
cel.MapType(cel.StringType, cel.ListType(cel.StringType)),
cel.UnaryBinding(getQuery))},
"isURL": {
cel.Overload("is_url_string", []*cel.Type{cel.StringType}, cel.BoolType,
cel.UnaryBinding(isURL))},
}
func (*urls) CompileOptions() []cel.EnvOption {
options := []cel.EnvOption{}
for name, overloads := range urlLibraryDecls {
options = append(options, cel.Function(name, overloads...))
}
return options
}
func (*urls) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{}
}
func stringToUrl(arg ref.Val) ref.Val {
s, ok := arg.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
// Use ParseRequestURI to check the URL before conversion.
// ParseRequestURI requires absolute URLs and is used by the OpenAPIv3 'uri' format.
_, err := url.ParseRequestURI(s)
if err != nil {
return types.NewErr("URL parse error during conversion from string: %v", err)
}
// We must parse again with Parse since ParseRequestURI incorrectly parses URLs that contain a fragment
// part and will incorrectly append the fragment to either the path or the query, depending on which it was adjacent to.
u, err := url.Parse(s)
if err != nil {
// 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 apiservercel.URL{URL: u}
}
func getScheme(arg ref.Val) ref.Val {
u, ok := arg.Value().(*url.URL)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.String(u.Scheme)
}
func getHost(arg ref.Val) ref.Val {
u, ok := arg.Value().(*url.URL)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.String(u.Host)
}
func getHostname(arg ref.Val) ref.Val {
u, ok := arg.Value().(*url.URL)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.String(u.Hostname())
}
func getPort(arg ref.Val) ref.Val {
u, ok := arg.Value().(*url.URL)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.String(u.Port())
}
func getEscapedPath(arg ref.Val) ref.Val {
u, ok := arg.Value().(*url.URL)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
return types.String(u.EscapedPath())
}
func getQuery(arg ref.Val) ref.Val {
u, ok := arg.Value().(*url.URL)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
result := map[ref.Val]ref.Val{}
for k, v := range u.Query() {
result[types.String(k)] = types.NewStringList(types.DefaultTypeAdapter, v)
}
return types.NewRefValMap(types.DefaultTypeAdapter, result)
}
func isURL(arg ref.Val) ref.Val {
s, ok := arg.Value().(string)
if !ok {
return types.MaybeNoSuchOverloadErr(arg)
}
_, err := url.ParseRequestURI(s)
return types.Bool(err == nil)
}

54
e2e/vendor/k8s.io/apiserver/pkg/cel/limits.go generated vendored Normal file
View File

@ -0,0 +1,54 @@
/*
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 celconfig "k8s.io/apiserver/pkg/apis/cel"
const (
// DefaultMaxRequestSizeBytes is the size of the largest request that will be accepted
DefaultMaxRequestSizeBytes = celconfig.MaxRequestSizeBytes
// 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
// MaxFormatSize is the maximum size we allow for format strings
MaxFormatSize = 64
MaxNameFormatRegexSize = 128
)

View File

@ -0,0 +1,249 @@
/*
Copyright 2024 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 dynamic
import (
"errors"
"fmt"
"reflect"
"strings"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
"google.golang.org/protobuf/types/known/structpb"
)
// ObjectType is the implementation of the Object type for use when compiling
// CEL expressions without schema information about the object.
// This is to provide CEL expressions with access to Object{} types constructors.
type ObjectType struct {
objectType *types.Type
}
func (o *ObjectType) HasTrait(trait int) bool {
return o.objectType.HasTrait(trait)
}
// TypeName returns the name of this ObjectType.
func (o *ObjectType) TypeName() string {
return o.objectType.TypeName()
}
// Val returns an instance given the fields.
func (o *ObjectType) Val(fields map[string]ref.Val) ref.Val {
return NewObjectVal(o.objectType, fields)
}
func (o *ObjectType) Type() *types.Type {
return o.objectType
}
// Field looks up the field by name.
// This is the unstructured version that allows any name as the field name.
// The returned field is of DynType type.
func (o *ObjectType) Field(name string) (*types.FieldType, bool) {
return &types.FieldType{
// for unstructured, we do not check for its type,
// use DynType for all fields.
Type: types.DynType,
IsSet: func(target any) bool {
if m, ok := target.(map[string]any); ok {
_, isSet := m[name]
return isSet
}
return false
},
GetFrom: func(target any) (any, error) {
if m, ok := target.(map[string]any); ok {
return m[name], nil
}
return nil, fmt.Errorf("cannot get field %q", name)
},
}, true
}
func (o *ObjectType) FieldNames() ([]string, bool) {
return nil, true // Field names are not known for dynamic types. All field names are allowed.
}
// NewObjectType creates a ObjectType by the given field name.
func NewObjectType(name string) *ObjectType {
return &ObjectType{
objectType: types.NewObjectType(name),
}
}
// ObjectVal is the CEL Val for an object that is constructed via the Object{} in
// CEL expressions without schema information about the object.
type ObjectVal struct {
objectType *types.Type
fields map[string]ref.Val
}
// NewObjectVal creates an ObjectVal by its ResolvedType and its fields.
func NewObjectVal(objectType *types.Type, fields map[string]ref.Val) *ObjectVal {
return &ObjectVal{
objectType: objectType,
fields: fields,
}
}
var _ ref.Val = (*ObjectVal)(nil)
var _ traits.Zeroer = (*ObjectVal)(nil)
// ConvertToNative converts the object to map[string]any.
// All nested lists are converted into []any native type.
//
// It returns an error if the target type is not map[string]any,
// or any recursive conversion fails.
func (v *ObjectVal) ConvertToNative(typeDesc reflect.Type) (any, error) {
result := make(map[string]any, len(v.fields))
for k, v := range v.fields {
converted, err := convertField(v)
if err != nil {
return nil, fmt.Errorf("fail to convert field %q: %w", k, err)
}
result[k] = converted
}
if typeDesc == reflect.TypeOf(result) {
return result, nil
}
// CEL's builtin data literal values all support conversion to structpb.Value, which
// can then be serialized to JSON. This is convenient for CEL expressions that return
// an arbitrary JSON value, such as our MutatingAdmissionPolicy JSON Patch valueExpression
// field, so we support the conversion here, for Object data literals, as well.
if typeDesc == reflect.TypeOf(&structpb.Value{}) {
return structpb.NewStruct(result)
}
return nil, fmt.Errorf("unable to convert to %v", typeDesc)
}
// ConvertToType supports type conversions between CEL value types supported by the expression language.
func (v *ObjectVal) ConvertToType(typeValue ref.Type) ref.Val {
if v.objectType.TypeName() == typeValue.TypeName() {
return v
}
if typeValue == types.TypeType {
return types.NewTypeTypeWithParam(v.objectType)
}
return types.NewErr("unsupported conversion into %v", typeValue)
}
// Equal returns true if the `other` value has the same type and content as the implementing struct.
func (v *ObjectVal) Equal(other ref.Val) ref.Val {
if rhs, ok := other.(*ObjectVal); ok {
if v.objectType.Equal(rhs.objectType) != types.True {
return types.False
}
return types.Bool(reflect.DeepEqual(v.fields, rhs.fields))
}
return types.False
}
// Type returns the TypeValue of the value.
func (v *ObjectVal) Type() ref.Type {
return types.NewObjectType(v.objectType.TypeName())
}
// Value returns its value as a map[string]any.
func (v *ObjectVal) Value() any {
var result any
var object map[string]any
result, err := v.ConvertToNative(reflect.TypeOf(object))
if err != nil {
return types.WrapErr(err)
}
return result
}
// CheckTypeNamesMatchFieldPathNames transitively checks the CEL object type names of this ObjectVal. Returns all
// found type name mismatch errors.
// Children ObjectVal types under <field> or this ObjectVal
// must have type names of the form "<ObjectVal.TypeName>.<field>", children of that type must have type names of the
// form "<ObjectVal.TypeName>.<field>.<field>" and so on.
// Intermediate maps and lists are unnamed and ignored.
func (v *ObjectVal) CheckTypeNamesMatchFieldPathNames() error {
return errors.Join(typeCheck(v, []string{v.Type().TypeName()})...)
}
func typeCheck(v ref.Val, typeNamePath []string) []error {
var errs []error
if ov, ok := v.(*ObjectVal); ok {
tn := ov.objectType.TypeName()
if strings.Join(typeNamePath, ".") != tn {
errs = append(errs, fmt.Errorf("unexpected type name %q, expected %q, which matches field name path from root Object type", tn, strings.Join(typeNamePath, ".")))
}
for k, f := range ov.fields {
errs = append(errs, typeCheck(f, append(typeNamePath, k))...)
}
}
value := v.Value()
if listOfVal, ok := value.([]ref.Val); ok {
for _, v := range listOfVal {
errs = append(errs, typeCheck(v, typeNamePath)...)
}
}
if mapOfVal, ok := value.(map[ref.Val]ref.Val); ok {
for _, v := range mapOfVal {
errs = append(errs, typeCheck(v, typeNamePath)...)
}
}
return errs
}
// IsZeroValue indicates whether the object is the zero value for the type.
// For the ObjectVal, it is zero value if and only if the fields map is empty.
func (v *ObjectVal) IsZeroValue() bool {
return len(v.fields) == 0
}
// convertField converts a referred ref.Val to its expected type.
// For objects, the expected type is map[string]any
// For lists, the expected type is []any
// For maps, the expected type is map[string]any
// For anything else, it is converted via value.Value()
//
// It will return an error if the request type is a map but the key
// is not a string.
func convertField(value ref.Val) (any, error) {
// special handling for lists, where the elements are converted with Value() instead of ConvertToNative
// to allow them to become native value of any type.
if listOfVal, ok := value.Value().([]ref.Val); ok {
var result []any
for _, v := range listOfVal {
result = append(result, v.Value())
}
return result, nil
}
// unstructured maps, as seen in annotations
// map keys must be strings
if mapOfVal, ok := value.Value().(map[ref.Val]ref.Val); ok {
result := make(map[string]any, len(mapOfVal))
for k, v := range mapOfVal {
stringKey, ok := k.Value().(string)
if !ok {
return nil, fmt.Errorf("map key %q is of type %T, not string", k, k)
}
result[stringKey] = v.Value()
}
return result, nil
}
return value.Value(), nil
}

View File

@ -0,0 +1,185 @@
/*
Copyright 2024 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 mutation
import (
"fmt"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"reflect"
)
var jsonPatchType = types.NewObjectType(JSONPatchTypeName)
var (
jsonPatchOp = "op"
jsonPatchPath = "path"
jsonPatchFrom = "from"
jsonPatchValue = "value"
)
// JSONPatchType and JSONPatchVal are defined entirely from scratch here because JSONPatchVal
// has a dynamic 'value' field which can not be defined with an OpenAPI schema,
// preventing us from using DeclType and UnstructuredToVal.
// JSONPatchType provides a CEL type for "JSONPatch" operations.
type JSONPatchType struct{}
func (r *JSONPatchType) HasTrait(trait int) bool {
return jsonPatchType.HasTrait(trait)
}
// TypeName returns the name of this ObjectType.
func (r *JSONPatchType) TypeName() string {
return jsonPatchType.TypeName()
}
// Val returns an instance given the fields.
func (r *JSONPatchType) Val(fields map[string]ref.Val) ref.Val {
result := &JSONPatchVal{}
for name, value := range fields {
switch name {
case jsonPatchOp:
if s, ok := value.Value().(string); ok {
result.Op = s
} else {
return types.NewErr("unexpected type %T for JSONPatchType 'op' field", value.Value())
}
case jsonPatchPath:
if s, ok := value.Value().(string); ok {
result.Path = s
} else {
return types.NewErr("unexpected type %T for JSONPatchType 'path' field", value.Value())
}
case jsonPatchFrom:
if s, ok := value.Value().(string); ok {
result.From = s
} else {
return types.NewErr("unexpected type %T for JSONPatchType 'from' field", value.Value())
}
case jsonPatchValue:
result.Val = value
default:
return types.NewErr("unexpected JSONPatchType field: %s", name)
}
}
return result
}
func (r *JSONPatchType) Type() *types.Type {
return jsonPatchType
}
func (r *JSONPatchType) Field(name string) (*types.FieldType, bool) {
var fieldType *types.Type
switch name {
case jsonPatchOp, jsonPatchFrom, jsonPatchPath:
fieldType = cel.StringType
case jsonPatchValue:
fieldType = types.DynType
}
return &types.FieldType{
Type: fieldType,
}, true
}
func (r *JSONPatchType) FieldNames() ([]string, bool) {
return []string{jsonPatchOp, jsonPatchFrom, jsonPatchPath, jsonPatchValue}, true
}
// JSONPatchVal is the ref.Val for a JSONPatch.
type JSONPatchVal struct {
Op, From, Path string
Val ref.Val
}
func (p *JSONPatchVal) ConvertToNative(typeDesc reflect.Type) (any, error) {
if typeDesc == reflect.TypeOf(&JSONPatchVal{}) {
return p, nil
}
return nil, fmt.Errorf("cannot convert to native type: %v", typeDesc)
}
func (p *JSONPatchVal) ConvertToType(typeValue ref.Type) ref.Val {
if typeValue == jsonPatchType {
return p
} else if typeValue == types.TypeType {
return types.NewTypeTypeWithParam(jsonPatchType)
}
return types.NewErr("unsupported type: %s", typeValue.TypeName())
}
func (p *JSONPatchVal) Equal(other ref.Val) ref.Val {
if o, ok := other.(*JSONPatchVal); ok && p != nil && o != nil {
if p.Op != o.Op || p.From != o.From || p.Path != o.Path {
return types.False
}
if (p.Val == nil) != (o.Val == nil) {
return types.False
}
if p.Val == nil {
return types.True
}
return p.Val.Equal(o.Val)
}
return types.False
}
func (p *JSONPatchVal) Get(index ref.Val) ref.Val {
if name, ok := index.Value().(string); ok {
switch name {
case jsonPatchOp:
return types.String(p.Op)
case jsonPatchPath:
return types.String(p.Path)
case jsonPatchFrom:
return types.String(p.From)
case jsonPatchValue:
return p.Val
default:
}
}
return types.NewErr("unsupported indexer: %s", index)
}
func (p *JSONPatchVal) IsSet(field ref.Val) ref.Val {
if name, ok := field.Value().(string); ok {
switch name {
case jsonPatchOp:
return types.Bool(len(p.Op) > 0)
case jsonPatchPath:
return types.Bool(len(p.Path) > 0)
case jsonPatchFrom:
return types.Bool(len(p.From) > 0)
case jsonPatchValue:
return types.Bool(p.Val != nil)
}
}
return types.NewErr("unsupported field: %s", field)
}
func (p *JSONPatchVal) Type() ref.Type {
return jsonPatchType
}
func (p *JSONPatchVal) Value() any {
return p
}
var _ ref.Val = &JSONPatchVal{}

View File

@ -0,0 +1,47 @@
/*
Copyright 2024 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 mutation
import (
"strings"
"k8s.io/apiserver/pkg/cel/common"
"k8s.io/apiserver/pkg/cel/mutation/dynamic"
)
// ObjectTypeName is the name of Object types that are used to declare the types of
// Kubernetes objects in CEL dynamically using the naming scheme "Object.<fieldName>...<fieldName>".
// For example "Object.spec.containers" is the type of the spec.containers field of the object in scope.
const ObjectTypeName = "Object"
// JSONPatchTypeName is the name of the JSONPatch type. This type is typically used to create JSON patches
// in CEL expressions.
const JSONPatchTypeName = "JSONPatch"
// DynamicTypeResolver resolves the Object and JSONPatch types when compiling
// CEL expressions without schema information about the object.
type DynamicTypeResolver struct{}
func (r *DynamicTypeResolver) Resolve(name string) (common.ResolvedType, bool) {
if name == JSONPatchTypeName {
return &JSONPatchType{}, true
}
if name == ObjectTypeName || strings.HasPrefix(name, ObjectTypeName+".") {
return dynamic.NewObjectType(name), true
}
return nil, false
}

229
e2e/vendor/k8s.io/apiserver/pkg/cel/openapi/adaptor.go generated vendored Normal file
View File

@ -0,0 +1,229 @@
/*
Copyright 2023 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 openapi
import (
"github.com/google/cel-go/common/types/ref"
apiservercel "k8s.io/apiserver/pkg/cel"
"k8s.io/apiserver/pkg/cel/common"
"k8s.io/kube-openapi/pkg/validation/spec"
)
var _ common.Schema = (*Schema)(nil)
var _ common.SchemaOrBool = (*SchemaOrBool)(nil)
type Schema struct {
Schema *spec.Schema
}
type SchemaOrBool struct {
SchemaOrBool *spec.SchemaOrBool
}
func (sb *SchemaOrBool) Schema() common.Schema {
return &Schema{Schema: sb.SchemaOrBool.Schema}
}
func (sb *SchemaOrBool) Allows() bool {
return sb.SchemaOrBool.Allows
}
func (s *Schema) Type() string {
if len(s.Schema.Type) == 0 {
return ""
}
return s.Schema.Type[0]
}
func (s *Schema) Format() string {
return s.Schema.Format
}
func (s *Schema) Pattern() string {
return s.Schema.Pattern
}
func (s *Schema) Items() common.Schema {
if s.Schema.Items == nil || s.Schema.Items.Schema == nil {
return nil
}
return &Schema{Schema: s.Schema.Items.Schema}
}
func (s *Schema) Properties() map[string]common.Schema {
if s.Schema.Properties == nil {
return nil
}
res := make(map[string]common.Schema, len(s.Schema.Properties))
for n, prop := range s.Schema.Properties {
// map value is unaddressable, create a shallow copy
// this is a shallow non-recursive copy
s := prop
res[n] = &Schema{Schema: &s}
}
return res
}
func (s *Schema) AdditionalProperties() common.SchemaOrBool {
if s.Schema.AdditionalProperties == nil {
return nil
}
return &SchemaOrBool{SchemaOrBool: s.Schema.AdditionalProperties}
}
func (s *Schema) Default() any {
return s.Schema.Default
}
func (s *Schema) Minimum() *float64 {
return s.Schema.Minimum
}
func (s *Schema) IsExclusiveMinimum() bool {
return s.Schema.ExclusiveMinimum
}
func (s *Schema) Maximum() *float64 {
return s.Schema.Maximum
}
func (s *Schema) IsExclusiveMaximum() bool {
return s.Schema.ExclusiveMaximum
}
func (s *Schema) MultipleOf() *float64 {
return s.Schema.MultipleOf
}
func (s *Schema) UniqueItems() bool {
return s.Schema.UniqueItems
}
func (s *Schema) MinItems() *int64 {
return s.Schema.MinItems
}
func (s *Schema) MaxItems() *int64 {
return s.Schema.MaxItems
}
func (s *Schema) MinLength() *int64 {
return s.Schema.MinLength
}
func (s *Schema) MaxLength() *int64 {
return s.Schema.MaxLength
}
func (s *Schema) MinProperties() *int64 {
return s.Schema.MinProperties
}
func (s *Schema) MaxProperties() *int64 {
return s.Schema.MaxProperties
}
func (s *Schema) Required() []string {
return s.Schema.Required
}
func (s *Schema) Enum() []any {
return s.Schema.Enum
}
func (s *Schema) Nullable() bool {
return s.Schema.Nullable
}
func (s *Schema) AllOf() []common.Schema {
var res []common.Schema
for _, nestedSchema := range s.Schema.AllOf {
nestedSchema := nestedSchema
res = append(res, &Schema{&nestedSchema})
}
return res
}
func (s *Schema) AnyOf() []common.Schema {
var res []common.Schema
for _, nestedSchema := range s.Schema.AnyOf {
nestedSchema := nestedSchema
res = append(res, &Schema{&nestedSchema})
}
return res
}
func (s *Schema) OneOf() []common.Schema {
var res []common.Schema
for _, nestedSchema := range s.Schema.OneOf {
nestedSchema := nestedSchema
res = append(res, &Schema{&nestedSchema})
}
return res
}
func (s *Schema) Not() common.Schema {
if s.Schema.Not == nil {
return nil
}
return &Schema{s.Schema.Not}
}
func (s *Schema) IsXIntOrString() bool {
return isXIntOrString(s.Schema)
}
func (s *Schema) IsXEmbeddedResource() bool {
return isXEmbeddedResource(s.Schema)
}
func (s *Schema) IsXPreserveUnknownFields() bool {
return isXPreserveUnknownFields(s.Schema)
}
func (s *Schema) XListType() string {
return getXListType(s.Schema)
}
func (s *Schema) XMapType() string {
return getXMapType(s.Schema)
}
func (s *Schema) XListMapKeys() []string {
return getXListMapKeys(s.Schema)
}
func (s *Schema) XValidations() []common.ValidationRule {
return getXValidations(s.Schema)
}
func (s *Schema) WithTypeAndObjectMeta() common.Schema {
return &Schema{common.WithTypeAndObjectMeta(s.Schema)}
}
func UnstructuredToVal(unstructured any, schema *spec.Schema) ref.Val {
return common.UnstructuredToVal(unstructured, &Schema{schema})
}
func SchemaDeclType(s *spec.Schema, isResourceRoot bool) *apiservercel.DeclType {
return common.SchemaDeclType(&Schema{Schema: s}, isResourceRoot)
}
func MakeMapList(sts *spec.Schema, items []interface{}) (rv common.MapList) {
return common.MakeMapList(&Schema{Schema: sts}, items)
}

View File

@ -0,0 +1,107 @@
/*
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 openapi
import (
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apiserver/pkg/cel/common"
"k8s.io/kube-openapi/pkg/validation/spec"
)
var intOrStringFormat = intstr.IntOrString{}.OpenAPISchemaFormat()
func isExtension(schema *spec.Schema, key string) bool {
v, ok := schema.Extensions.GetBool(key)
return v && ok
}
func isXIntOrString(schema *spec.Schema) bool {
// built-in types have the Format while CRDs use extension
// both are valid, checking both
return schema.Format == intOrStringFormat || isExtension(schema, extIntOrString)
}
func isXEmbeddedResource(schema *spec.Schema) bool {
return isExtension(schema, extEmbeddedResource)
}
func isXPreserveUnknownFields(schema *spec.Schema) bool {
return isExtension(schema, extPreserveUnknownFields)
}
func getXListType(schema *spec.Schema) string {
s, _ := schema.Extensions.GetString(extListType)
return s
}
func getXMapType(schema *spec.Schema) string {
s, _ := schema.Extensions.GetString(extMapType)
return s
}
func getXListMapKeys(schema *spec.Schema) []string {
mapKeys, ok := schema.Extensions.GetStringSlice(extListMapKeys)
if !ok {
return nil
}
return mapKeys
}
type ValidationRule struct {
RuleField string `json:"rule"`
MessageField string `json:"message"`
MessageExpressionField string `json:"messageExpression"`
PathField string `json:"fieldPath"`
}
func (v ValidationRule) Rule() string {
return v.RuleField
}
func (v ValidationRule) Message() string {
return v.MessageField
}
func (v ValidationRule) FieldPath() string {
return v.PathField
}
func (v ValidationRule) MessageExpression() string {
return v.MessageExpressionField
}
// TODO: simplify
func getXValidations(schema *spec.Schema) []common.ValidationRule {
var rules []ValidationRule
err := schema.Extensions.GetObject(extValidations, &rules)
if err != nil {
return nil
}
results := make([]common.ValidationRule, len(rules))
for i, rule := range rules {
results[i] = rule
}
return results
}
const extIntOrString = "x-kubernetes-int-or-string"
const extEmbeddedResource = "x-kubernetes-embedded-resource"
const extPreserveUnknownFields = "x-kubernetes-preserve-unknown-fields"
const extListType = "x-kubernetes-list-type"
const extMapType = "x-kubernetes-map-type"
const extListMapKeys = "x-kubernetes-list-map-keys"
const extValidations = "x-kubernetes-validations"

View File

@ -0,0 +1,45 @@
/*
Copyright 2023 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 resolver
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// Combine combines the DefinitionsSchemaResolver with a secondary schema resolver.
// The resulting schema resolver uses the DefinitionsSchemaResolver for a GVK that DefinitionsSchemaResolver knows,
// and the secondary otherwise.
func (d *DefinitionsSchemaResolver) Combine(secondary SchemaResolver) SchemaResolver {
return &combinedSchemaResolver{definitions: d, secondary: secondary}
}
type combinedSchemaResolver struct {
definitions *DefinitionsSchemaResolver
secondary SchemaResolver
}
// ResolveSchema takes a GroupVersionKind (GVK) and returns the OpenAPI schema
// identified by the GVK.
// If the DefinitionsSchemaResolver knows the gvk, the DefinitionsSchemaResolver handles the resolution,
// otherwise, the secondary does.
func (r *combinedSchemaResolver) ResolveSchema(gvk schema.GroupVersionKind) (*spec.Schema, error) {
if _, ok := r.definitions.gvkToRef[gvk]; ok {
return r.definitions.ResolveSchema(gvk)
}
return r.secondary.ResolveSchema(gvk)
}

View File

@ -0,0 +1,114 @@
/*
Copyright 2023 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 resolver
import (
"fmt"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/openapi"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// DefinitionsSchemaResolver resolves the schema of a built-in type
// by looking up the OpenAPI definitions.
type DefinitionsSchemaResolver struct {
defs map[string]common.OpenAPIDefinition
gvkToRef map[schema.GroupVersionKind]string
}
// NewDefinitionsSchemaResolver creates a new DefinitionsSchemaResolver.
// An example working setup:
// getDefinitions = "k8s.io/kubernetes/pkg/generated/openapi".GetOpenAPIDefinitions
// scheme = "k8s.io/client-go/kubernetes/scheme".Scheme
func NewDefinitionsSchemaResolver(getDefinitions common.GetOpenAPIDefinitions, schemes ...*runtime.Scheme) *DefinitionsSchemaResolver {
gvkToRef := make(map[schema.GroupVersionKind]string)
namer := openapi.NewDefinitionNamer(schemes...)
defs := getDefinitions(func(path string) spec.Ref {
return spec.MustCreateRef(path)
})
for name := range defs {
_, e := namer.GetDefinitionName(name)
gvks := extensionsToGVKs(e)
for _, gvk := range gvks {
gvkToRef[gvk] = name
}
}
return &DefinitionsSchemaResolver{
gvkToRef: gvkToRef,
defs: defs,
}
}
func (d *DefinitionsSchemaResolver) ResolveSchema(gvk schema.GroupVersionKind) (*spec.Schema, error) {
ref, ok := d.gvkToRef[gvk]
if !ok {
return nil, fmt.Errorf("cannot resolve %v: %w", gvk, ErrSchemaNotFound)
}
s, err := PopulateRefs(func(ref string) (*spec.Schema, bool) {
// find the schema by the ref string, and return a deep copy
def, ok := d.defs[ref]
if !ok {
return nil, false
}
s := def.Schema
return &s, true
}, ref)
if err != nil {
return nil, err
}
return s, nil
}
func extensionsToGVKs(extensions spec.Extensions) []schema.GroupVersionKind {
gvksAny, ok := extensions[extGVK]
if !ok {
return nil
}
gvks, ok := gvksAny.([]any)
if !ok {
return nil
}
result := make([]schema.GroupVersionKind, 0, len(gvks))
for _, gvkAny := range gvks {
// type check the map and all fields
gvkMap, ok := gvkAny.(map[string]any)
if !ok {
return nil
}
g, ok := gvkMap["group"].(string)
if !ok {
return nil
}
v, ok := gvkMap["version"].(string)
if !ok {
return nil
}
k, ok := gvkMap["kind"].(string)
if !ok {
return nil
}
result = append(result, schema.GroupVersionKind{
Group: g,
Version: v,
Kind: k,
})
}
return result
}

View File

@ -0,0 +1,104 @@
/*
Copyright 2023 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 resolver
import (
"encoding/json"
"fmt"
"strings"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// ClientDiscoveryResolver uses client-go discovery to resolve schemas at run time.
type ClientDiscoveryResolver struct {
Discovery discovery.DiscoveryInterface
}
var _ SchemaResolver = (*ClientDiscoveryResolver)(nil)
func (r *ClientDiscoveryResolver) ResolveSchema(gvk schema.GroupVersionKind) (*spec.Schema, error) {
p, err := r.Discovery.OpenAPIV3().Paths()
if err != nil {
return nil, err
}
resourcePath := resourcePathFromGV(gvk.GroupVersion())
c, ok := p[resourcePath]
if !ok {
return nil, fmt.Errorf("cannot resolve group version %q: %w", gvk.GroupVersion(), ErrSchemaNotFound)
}
b, err := c.Schema(runtime.ContentTypeJSON)
if err != nil {
return nil, err
}
resp := new(schemaResponse)
err = json.Unmarshal(b, resp)
if err != nil {
return nil, err
}
ref, err := resolveRef(resp, gvk)
if err != nil {
return nil, err
}
s, err := PopulateRefs(func(ref string) (*spec.Schema, bool) {
s, ok := resp.Components.Schemas[strings.TrimPrefix(ref, refPrefix)]
return s, ok
}, ref)
if err != nil {
return nil, err
}
return s, nil
}
func resolveRef(resp *schemaResponse, gvk schema.GroupVersionKind) (string, error) {
for ref, s := range resp.Components.Schemas {
var gvks []schema.GroupVersionKind
err := s.Extensions.GetObject(extGVK, &gvks)
if err != nil {
return "", err
}
for _, g := range gvks {
if g == gvk {
return ref, nil
}
}
}
return "", fmt.Errorf("cannot resolve group version kind %q: %w", gvk, ErrSchemaNotFound)
}
func resourcePathFromGV(gv schema.GroupVersion) string {
var resourcePath string
if len(gv.Group) == 0 {
resourcePath = fmt.Sprintf("api/%s", gv.Version)
} else {
resourcePath = fmt.Sprintf("apis/%s/%s", gv.Group, gv.Version)
}
return resourcePath
}
type schemaResponse struct {
Components struct {
Schemas map[string]*spec.Schema `json:"schemas"`
} `json:"components"`
}
const refPrefix = "#/components/schemas/"
const extGVK = "x-kubernetes-group-version-kind"

View File

@ -0,0 +1,122 @@
/*
Copyright 2023 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 resolver
import (
"fmt"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// PopulateRefs recursively replaces Refs in the schema with the referred one.
// schemaOf is the callback to find the corresponding schema by the ref.
// This function will not mutate the original schema. If the schema needs to be
// mutated, a copy will be returned, otherwise it returns the original schema.
func PopulateRefs(schemaOf func(ref string) (*spec.Schema, bool), rootRef string) (*spec.Schema, error) {
visitedRefs := sets.New[string]()
rootSchema, ok := schemaOf(rootRef)
visitedRefs.Insert(rootRef)
if !ok {
return nil, fmt.Errorf("internal error: cannot resolve Ref for root schema %q: %w", rootRef, ErrSchemaNotFound)
}
return populateRefs(schemaOf, visitedRefs, rootSchema)
}
func populateRefs(schemaOf func(ref string) (*spec.Schema, bool), visited sets.Set[string], schema *spec.Schema) (*spec.Schema, error) {
result := *schema
changed := false
ref, isRef := refOf(schema)
if isRef {
if visited.Has(ref) {
return &spec.Schema{
// for circular ref, return an empty object as placeholder
SchemaProps: spec.SchemaProps{Type: []string{"object"}},
}, nil
}
visited.Insert(ref)
// restore visited state at the end of the recursion.
defer func() {
visited.Delete(ref)
}()
// replace the whole schema with the referred one.
resolved, ok := schemaOf(ref)
if !ok {
return nil, fmt.Errorf("internal error: cannot resolve Ref %q: %w", ref, ErrSchemaNotFound)
}
result = *resolved
changed = true
}
// schema is an object, populate its properties and additionalProperties
props := make(map[string]spec.Schema, len(schema.Properties))
propsChanged := false
for name, prop := range result.Properties {
populated, err := populateRefs(schemaOf, visited, &prop)
if err != nil {
return nil, err
}
if populated != &prop {
propsChanged = true
}
props[name] = *populated
}
if propsChanged {
changed = true
result.Properties = props
}
if result.AdditionalProperties != nil && result.AdditionalProperties.Schema != nil {
populated, err := populateRefs(schemaOf, visited, result.AdditionalProperties.Schema)
if err != nil {
return nil, err
}
if populated != result.AdditionalProperties.Schema {
changed = true
result.AdditionalProperties.Schema = populated
}
}
// schema is a list, populate its items
if result.Items != nil && result.Items.Schema != nil {
populated, err := populateRefs(schemaOf, visited, result.Items.Schema)
if err != nil {
return nil, err
}
if populated != result.Items.Schema {
changed = true
result.Items.Schema = populated
}
}
if changed {
return &result, nil
}
return schema, nil
}
func refOf(schema *spec.Schema) (string, bool) {
if schema.Ref.GetURL() != nil {
return schema.Ref.String(), true
}
// A Ref may be wrapped in allOf to preserve its description
// see https://github.com/kubernetes/kubernetes/issues/106387
// For kube-openapi, allOf is only used for wrapping a Ref.
for _, allOf := range schema.AllOf {
if ref, isRef := refOf(&allOf); isRef {
return ref, isRef
}
}
return "", false
}

View File

@ -0,0 +1,39 @@
/*
Copyright 2023 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 resolver
import (
"fmt"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// SchemaResolver finds the OpenAPI schema for the given GroupVersionKind.
// This interface uses the type defined by k8s.io/kube-openapi
type SchemaResolver interface {
// ResolveSchema takes a GroupVersionKind (GVK) and returns the OpenAPI schema
// identified by the GVK.
// The function returns a non-nil error if the schema cannot be found or fail
// to resolve. The returned error wraps ErrSchemaNotFound if the resolution is
// attempted but the corresponding schema cannot be found.
ResolveSchema(gvk schema.GroupVersionKind) (*spec.Schema, error)
}
// ErrSchemaNotFound is wrapped and returned if the schema cannot be located
// by the resolver.
var ErrSchemaNotFound = fmt.Errorf("schema not found")

76
e2e/vendor/k8s.io/apiserver/pkg/cel/quantity.go generated vendored Normal file
View File

@ -0,0 +1,76 @@
/*
Copyright 2023 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 (
"fmt"
"reflect"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/checker/decls"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"k8s.io/apimachinery/pkg/api/resource"
)
var (
QuantityObject = decls.NewObjectType("kubernetes.Quantity")
quantityTypeValue = types.NewTypeValue("kubernetes.Quantity")
QuantityType = cel.ObjectType("kubernetes.Quantity")
)
// Quantity provdes a CEL representation of a resource.Quantity
type Quantity struct {
*resource.Quantity
}
func (d Quantity) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
if reflect.TypeOf(d.Quantity).AssignableTo(typeDesc) {
return d.Quantity, nil
}
if reflect.TypeOf("").AssignableTo(typeDesc) {
return d.Quantity.String(), nil
}
return nil, fmt.Errorf("type conversion error from 'Quantity' to '%v'", typeDesc)
}
func (d Quantity) ConvertToType(typeVal ref.Type) ref.Val {
switch typeVal {
case quantityTypeValue:
return d
case types.TypeType:
return quantityTypeValue
default:
return types.NewErr("type conversion error from '%s' to '%s'", quantityTypeValue, typeVal)
}
}
func (d Quantity) Equal(other ref.Val) ref.Val {
otherDur, ok := other.(Quantity)
if !ok {
return types.MaybeNoSuchOverloadErr(other)
}
return types.Bool(d.Quantity.Equal(*otherDur.Quantity))
}
func (d Quantity) Type() ref.Type {
return quantityTypeValue
}
func (d Quantity) Value() interface{} {
return d.Quantity
}

73
e2e/vendor/k8s.io/apiserver/pkg/cel/semver.go generated vendored Normal file
View File

@ -0,0 +1,73 @@
/*
Copyright 2024 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 (
"fmt"
"reflect"
"github.com/blang/semver/v4"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
)
var (
SemverType = cel.ObjectType("kubernetes.Semver")
)
// Semver provdes a CEL representation of a [semver.Version].
type Semver struct {
semver.Version
}
func (v Semver) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
if reflect.TypeOf(v.Version).AssignableTo(typeDesc) {
return v.Version, nil
}
if reflect.TypeOf("").AssignableTo(typeDesc) {
return v.Version.String(), nil
}
return nil, fmt.Errorf("type conversion error from 'Semver' to '%v'", typeDesc)
}
func (v Semver) ConvertToType(typeVal ref.Type) ref.Val {
switch typeVal {
case SemverType:
return v
case types.TypeType:
return SemverType
default:
return types.NewErr("type conversion error from '%s' to '%s'", SemverType, typeVal)
}
}
func (v Semver) Equal(other ref.Val) ref.Val {
otherDur, ok := other.(Semver)
if !ok {
return types.MaybeNoSuchOverloadErr(other)
}
return types.Bool(v.Version.EQ(otherDur.Version))
}
func (v Semver) Type() ref.Type {
return SemverType
}
func (v Semver) Value() interface{} {
return v.Version
}

598
e2e/vendor/k8s.io/apiserver/pkg/cel/types.go generated vendored Normal file
View File

@ -0,0 +1,598 @@
/*
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 (
"fmt"
"math"
"time"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
"k8s.io/apimachinery/pkg/api/resource"
)
const (
noMaxLength = math.MaxInt
)
// NewListType returns a parameterized list type with a specified element type.
func NewListType(elem *DeclType, maxItems int64) *DeclType {
return &DeclType{
name: "list",
ElemType: elem,
MaxElements: maxItems,
celType: cel.ListType(elem.CelType()),
defaultValue: NewListValue(),
// a list can always be represented as [] in JSON, so hardcode the min size
// to 2
MinSerializedSize: 2,
}
}
// NewMapType returns a parameterized map type with the given key and element types.
func NewMapType(key, elem *DeclType, maxProperties int64) *DeclType {
return &DeclType{
name: "map",
KeyType: key,
ElemType: elem,
MaxElements: maxProperties,
celType: cel.MapType(key.CelType(), elem.CelType()),
defaultValue: NewMapValue(),
// a map can always be represented as {} in JSON, so hardcode the min size
// to 2
MinSerializedSize: 2,
}
}
// NewObjectType creates an object type with a qualified name and a set of field declarations.
func NewObjectType(name string, fields map[string]*DeclField) *DeclType {
t := &DeclType{
name: name,
Fields: fields,
celType: cel.ObjectType(name),
traitMask: traits.FieldTesterType | traits.IndexerType,
// an object could potentially be larger than the min size we default to here ({}),
// but we rely upon the caller to change MinSerializedSize accordingly if they add
// properties to the object
MinSerializedSize: 2,
}
t.defaultValue = NewObjectValue(t)
return t
}
func NewSimpleTypeWithMinSize(name string, celType *cel.Type, zeroVal ref.Val, minSize int64) *DeclType {
return &DeclType{
name: name,
celType: celType,
defaultValue: zeroVal,
MinSerializedSize: minSize,
}
}
// DeclType represents the universal type descriptor for OpenAPIv3 types.
type DeclType struct {
fmt.Stringer
name string
// Fields contains a map of escaped CEL identifier field names to field declarations.
Fields map[string]*DeclField
KeyType *DeclType
ElemType *DeclType
TypeParam bool
Metadata map[string]string
MaxElements int64
// MinSerializedSize represents the smallest possible size in bytes that
// the DeclType could be serialized to in JSON.
MinSerializedSize int64
celType *cel.Type
traitMask int
defaultValue ref.Val
}
// MaybeAssignTypeName attempts to set the DeclType name to a fully qualified name, if the type
// is of `object` type.
//
// The DeclType must return true for `IsObject` or this assignment will error.
func (t *DeclType) MaybeAssignTypeName(name string) *DeclType {
if t.IsObject() {
objUpdated := false
if t.name != "object" {
name = t.name
} else {
objUpdated = true
}
fieldMap := make(map[string]*DeclField, len(t.Fields))
for fieldName, field := range t.Fields {
fieldType := field.Type
fieldTypeName := fmt.Sprintf("%s.%s", name, fieldName)
updated := fieldType.MaybeAssignTypeName(fieldTypeName)
if updated == fieldType {
fieldMap[fieldName] = field
continue
}
objUpdated = true
fieldMap[fieldName] = &DeclField{
Name: fieldName,
Type: updated,
Required: field.Required,
enumValues: field.enumValues,
defaultValue: field.defaultValue,
}
}
if !objUpdated {
return t
}
return &DeclType{
name: name,
Fields: fieldMap,
KeyType: t.KeyType,
ElemType: t.ElemType,
TypeParam: t.TypeParam,
Metadata: t.Metadata,
celType: cel.ObjectType(name),
traitMask: t.traitMask,
defaultValue: t.defaultValue,
MinSerializedSize: t.MinSerializedSize,
}
}
if t.IsMap() {
elemTypeName := fmt.Sprintf("%s.@elem", name)
updated := t.ElemType.MaybeAssignTypeName(elemTypeName)
if updated == t.ElemType {
return t
}
return NewMapType(t.KeyType, updated, t.MaxElements)
}
if t.IsList() {
elemTypeName := fmt.Sprintf("%s.@idx", name)
updated := t.ElemType.MaybeAssignTypeName(elemTypeName)
if updated == t.ElemType {
return t
}
return NewListType(updated, t.MaxElements)
}
return t
}
// ExprType returns the CEL expression type of this declaration.
func (t *DeclType) ExprType() (*exprpb.Type, error) {
return cel.TypeToExprType(t.celType)
}
// CelType returns the CEL type of this declaration.
func (t *DeclType) CelType() *cel.Type {
return t.celType
}
// FindField returns the DeclField with the given name if present.
func (t *DeclType) FindField(name string) (*DeclField, bool) {
f, found := t.Fields[name]
return f, found
}
// HasTrait implements the CEL ref.Type interface making this type declaration suitable for use
// within the CEL evaluator.
func (t *DeclType) HasTrait(trait int) bool {
if t.traitMask&trait == trait {
return true
}
if t.defaultValue == nil {
return false
}
_, isDecl := t.defaultValue.Type().(*DeclType)
if isDecl {
return false
}
return t.defaultValue.Type().HasTrait(trait)
}
// IsList returns whether the declaration is a `list` type which defines a parameterized element
// type, but not a parameterized key type or fields.
func (t *DeclType) IsList() bool {
return t.KeyType == nil && t.ElemType != nil && t.Fields == nil
}
// IsMap returns whether the declaration is a 'map' type which defines parameterized key and
// element types, but not fields.
func (t *DeclType) IsMap() bool {
return t.KeyType != nil && t.ElemType != nil && t.Fields == nil
}
// IsObject returns whether the declartion is an 'object' type which defined a set of typed fields.
func (t *DeclType) IsObject() bool {
return t.KeyType == nil && t.ElemType == nil && t.Fields != nil
}
// String implements the fmt.Stringer interface method.
func (t *DeclType) String() string {
return t.name
}
// TypeName returns the fully qualified type name for the DeclType.
func (t *DeclType) TypeName() string {
return t.name
}
// DefaultValue returns the CEL ref.Val representing the default value for this object type,
// if one exists.
func (t *DeclType) DefaultValue() ref.Val {
return t.defaultValue
}
// FieldTypeMap constructs a map of the field and object types nested within a given type.
func FieldTypeMap(path string, t *DeclType) map[string]*DeclType {
if t.IsObject() && t.TypeName() != "object" {
path = t.TypeName()
}
types := make(map[string]*DeclType)
buildDeclTypes(path, t, types)
return types
}
func buildDeclTypes(path string, t *DeclType, types map[string]*DeclType) {
// Ensure object types are properly named according to where they appear in the schema.
if t.IsObject() {
// Hack to ensure that names are uniquely qualified and work well with the type
// resolution steps which require fully qualified type names for field resolution
// to function properly.
types[t.TypeName()] = t
for name, field := range t.Fields {
fieldPath := fmt.Sprintf("%s.%s", path, name)
buildDeclTypes(fieldPath, field.Type, types)
}
}
// Map element properties to type names if needed.
if t.IsMap() {
mapElemPath := fmt.Sprintf("%s.@elem", path)
buildDeclTypes(mapElemPath, t.ElemType, types)
types[path] = t
}
// List element properties.
if t.IsList() {
listIdxPath := fmt.Sprintf("%s.@idx", path)
buildDeclTypes(listIdxPath, t.ElemType, types)
types[path] = t
}
}
// DeclField describes the name, ordinal, and optionality of a field declaration within a type.
type DeclField struct {
Name string
Type *DeclType
Required bool
enumValues []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.
func (f *DeclField) TypeName() string {
return f.Type.TypeName()
}
// DefaultValue returns the zero value associated with the field.
func (f *DeclField) DefaultValue() ref.Val {
if f.defaultValue != nil {
return types.DefaultTypeAdapter.NativeToValue(f.defaultValue)
}
return f.Type.DefaultValue()
}
// EnumValues returns the set of values that this field may take.
func (f *DeclField) EnumValues() []ref.Val {
if f.enumValues == nil || len(f.enumValues) == 0 {
return []ref.Val{}
}
ev := make([]ref.Val, len(f.enumValues))
for i, e := range f.enumValues {
ev[i] = types.DefaultTypeAdapter.NativeToValue(e)
}
return ev
}
func allTypesForDecl(declTypes []*DeclType) map[string]*DeclType {
if declTypes == nil {
return nil
}
allTypes := map[string]*DeclType{}
for _, declType := range declTypes {
for k, t := range FieldTypeMap(declType.TypeName(), declType) {
allTypes[k] = t
}
}
return allTypes
}
// NewDeclTypeProvider returns an Open API Schema-based type-system which is CEL compatible.
func NewDeclTypeProvider(rootTypes ...*DeclType) *DeclTypeProvider {
// Note, if the schema indicates that it's actually based on another proto
// then prefer the proto definition. For expressions in the proto, a new field
// annotation will be needed to indicate the expected environment and type of
// the expression.
allTypes := allTypesForDecl(rootTypes)
return &DeclTypeProvider{
registeredTypes: allTypes,
}
}
// DeclTypeProvider extends the CEL ref.TypeProvider interface and provides an Open API Schema-based
// type-system.
type DeclTypeProvider struct {
registeredTypes map[string]*DeclType
typeProvider types.Provider
typeAdapter types.Adapter
recognizeKeywordAsFieldName bool
}
func (rt *DeclTypeProvider) SetRecognizeKeywordAsFieldName(recognize bool) {
rt.recognizeKeywordAsFieldName = recognize
}
func (rt *DeclTypeProvider) EnumValue(enumName string) ref.Val {
return rt.typeProvider.EnumValue(enumName)
}
func (rt *DeclTypeProvider) FindIdent(identName string) (ref.Val, bool) {
return rt.typeProvider.FindIdent(identName)
}
// EnvOptions returns a set of cel.EnvOption values which includes the declaration set
// as well as a custom ref.TypeProvider.
//
// If the DeclTypeProvider value is nil, an empty []cel.EnvOption set is returned.
func (rt *DeclTypeProvider) EnvOptions(tp types.Provider) ([]cel.EnvOption, error) {
if rt == nil {
return []cel.EnvOption{}, nil
}
rtWithTypes, err := rt.WithTypeProvider(tp)
if err != nil {
return nil, err
}
return []cel.EnvOption{
cel.CustomTypeProvider(rtWithTypes),
cel.CustomTypeAdapter(rtWithTypes),
}, nil
}
// WithTypeProvider returns a new DeclTypeProvider that sets the given TypeProvider
// If the original DeclTypeProvider is nil, the returned DeclTypeProvider is still nil.
func (rt *DeclTypeProvider) WithTypeProvider(tp types.Provider) (*DeclTypeProvider, error) {
if rt == nil {
return nil, nil
}
var ta types.Adapter = types.DefaultTypeAdapter
tpa, ok := tp.(types.Adapter)
if ok {
ta = tpa
}
rtWithTypes := &DeclTypeProvider{
typeProvider: tp,
typeAdapter: ta,
registeredTypes: rt.registeredTypes,
recognizeKeywordAsFieldName: rt.recognizeKeywordAsFieldName,
}
for name, declType := range rt.registeredTypes {
tpType, found := tp.FindStructType(name)
// cast celType to types.type
expT := declType.CelType()
if found && !expT.IsExactType(tpType) {
return nil, fmt.Errorf(
"type %s definition differs between CEL environment and type provider", name)
}
}
return rtWithTypes, nil
}
// FindStructType attempts to resolve the typeName provided from the rule's rule-schema, or if not
// from the embedded ref.TypeProvider.
//
// FindStructType overrides the default type-finding behavior of the embedded TypeProvider.
//
// Note, when the type name is based on the Open API Schema, the name will reflect the object path
// where the type definition appears.
func (rt *DeclTypeProvider) FindStructType(typeName string) (*types.Type, bool) {
if rt == nil {
return nil, false
}
declType, found := rt.findDeclType(typeName)
if found {
expT := declType.CelType()
return types.NewTypeTypeWithParam(expT), found
}
return rt.typeProvider.FindStructType(typeName)
}
// FindDeclType returns the CPT type description which can be mapped to a CEL type.
func (rt *DeclTypeProvider) FindDeclType(typeName string) (*DeclType, bool) {
if rt == nil {
return nil, false
}
return rt.findDeclType(typeName)
}
// FindStructFieldNames returns the field names associated with the type, if the type
// is found.
func (rt *DeclTypeProvider) FindStructFieldNames(typeName string) ([]string, bool) {
return []string{}, false
}
// FindStructFieldType returns a field type given a type name and field name, if found.
//
// Note, the type name for an Open API Schema type is likely to be its qualified object path.
// If, in the future an object instance rather than a type name were provided, the field
// resolution might more accurately reflect the expected type model. However, in this case
// concessions were made to align with the existing CEL interfaces.
func (rt *DeclTypeProvider) FindStructFieldType(typeName, fieldName string) (*types.FieldType, bool) {
st, found := rt.findDeclType(typeName)
if !found {
return rt.typeProvider.FindStructFieldType(typeName, fieldName)
}
f, found := st.Fields[fieldName]
if rt.recognizeKeywordAsFieldName && !found && celReservedSymbols.Has(fieldName) {
f, found = st.Fields["__"+fieldName+"__"]
}
if found {
ft := f.Type
expT := ft.CelType()
return &types.FieldType{
Type: expT,
}, true
}
// This could be a dynamic map.
if st.IsMap() {
et := st.ElemType
expT := et.CelType()
return &types.FieldType{
Type: expT,
}, true
}
return nil, false
}
// NativeToValue is an implementation of the ref.TypeAdapater interface which supports conversion
// of rule values to CEL ref.Val instances.
func (rt *DeclTypeProvider) NativeToValue(val interface{}) ref.Val {
return rt.typeAdapter.NativeToValue(val)
}
func (rt *DeclTypeProvider) NewValue(typeName string, fields map[string]ref.Val) ref.Val {
// TODO: implement for OpenAPI types to enable CEL object instantiation, which is needed
// for mutating admission.
return rt.typeProvider.NewValue(typeName, fields)
}
// TypeNames returns the list of type names declared within the DeclTypeProvider object.
func (rt *DeclTypeProvider) TypeNames() []string {
typeNames := make([]string, len(rt.registeredTypes))
i := 0
for name := range rt.registeredTypes {
typeNames[i] = name
i++
}
return typeNames
}
func (rt *DeclTypeProvider) findDeclType(typeName string) (*DeclType, bool) {
declType, found := rt.registeredTypes[typeName]
if found {
return declType, true
}
declType = findScalar(typeName)
return declType, declType != nil
}
func findScalar(typename string) *DeclType {
switch typename {
case BoolType.TypeName():
return BoolType
case BytesType.TypeName():
return BytesType
case DoubleType.TypeName():
return DoubleType
case DurationType.TypeName():
return DurationType
case IntType.TypeName():
return IntType
case NullType.TypeName():
return NullType
case StringType.TypeName():
return StringType
case TimestampType.TypeName():
return TimestampType
case UintType.TypeName():
return UintType
case ListType.TypeName():
return ListType
case MapType.TypeName():
return MapType
default:
return nil
}
}
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)
// BoolType is equivalent to the CEL 'bool' type.
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)
// 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)
// DurationType is equivalent to the CEL 'duration' type.
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{}}, 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)
// IntType is equivalent to the CEL 'int' type which is a 64-bit signed int.
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)
// 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)
// 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{}}, JSONDateSize)
// QuantityDeclType wraps a [QuantityType] and makes it usable with functions that expect
// a [DeclType].
QuantityDeclType = NewSimpleTypeWithMinSize("quantity", QuantityType, Quantity{Quantity: resource.NewQuantity(0, resource.DecimalSI)}, 8)
// UintType is equivalent to the CEL 'uint' type.
UintType = NewSimpleTypeWithMinSize("uint", cel.UintType, types.Uint(0), 1)
// ListType is equivalent to the CEL 'list' type.
ListType = NewListType(AnyType, noMaxLength)
// MapType is equivalent to the CEL 'map' type.
MapType = NewMapType(AnyType, AnyType, noMaxLength)
)

80
e2e/vendor/k8s.io/apiserver/pkg/cel/url.go generated vendored Normal file
View File

@ -0,0 +1,80 @@
/*
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 (
"fmt"
"net/url"
"reflect"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/checker/decls"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
)
// URL provides a CEL representation of a URL.
type URL struct {
*url.URL
}
var (
URLObject = decls.NewObjectType("kubernetes.URL")
typeValue = types.NewTypeValue("kubernetes.URL")
URLType = cel.ObjectType("kubernetes.URL")
)
// ConvertToNative implements ref.Val.ConvertToNative.
func (d URL) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
if reflect.TypeOf(d.URL).AssignableTo(typeDesc) {
return d.URL, nil
}
if reflect.TypeOf("").AssignableTo(typeDesc) {
return d.URL.String(), nil
}
return nil, fmt.Errorf("type conversion error from 'URL' to '%v'", typeDesc)
}
// ConvertToType implements ref.Val.ConvertToType.
func (d URL) ConvertToType(typeVal ref.Type) ref.Val {
switch typeVal {
case typeValue:
return d
case types.TypeType:
return typeValue
}
return types.NewErr("type conversion error from '%s' to '%s'", typeValue, typeVal)
}
// Equal implements ref.Val.Equal.
func (d URL) Equal(other ref.Val) ref.Val {
otherDur, ok := other.(URL)
if !ok {
return types.MaybeNoSuchOverloadErr(other)
}
return types.Bool(d.URL.String() == otherDur.URL.String())
}
// Type implements ref.Val.Type.
func (d URL) Type() ref.Type {
return typeValue
}
// Value implements ref.Val.Value.
func (d URL) Value() interface{} {
return d.URL
}

769
e2e/vendor/k8s.io/apiserver/pkg/cel/value.go generated vendored Normal file
View File

@ -0,0 +1,769 @@
/*
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 (
"fmt"
"reflect"
"sync"
"time"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
)
// EncodeStyle is a hint for string encoding of parsed values.
type EncodeStyle int
const (
// BlockValueStyle is the default string encoding which preserves whitespace and newlines.
BlockValueStyle EncodeStyle = iota
// FlowValueStyle indicates that the string is an inline representation of complex types.
FlowValueStyle
// FoldedValueStyle is a multiline string with whitespace and newlines trimmed to a single
// a whitespace. Repeated newlines are replaced with a single newline rather than a single
// whitespace.
FoldedValueStyle
// LiteralStyle is a multiline string that preserves newlines, but trims all other whitespace
// to a single character.
LiteralStyle
)
// NewEmptyDynValue returns the zero-valued DynValue.
func NewEmptyDynValue() *DynValue {
// note: 0 is not a valid parse node identifier.
dv, _ := NewDynValue(0, nil)
return dv
}
// NewDynValue returns a DynValue that corresponds to a parse node id and value.
func NewDynValue(id int64, val interface{}) (*DynValue, error) {
dv := &DynValue{ID: id}
err := dv.SetValue(val)
return dv, err
}
// DynValue is a dynamically typed value used to describe unstructured content.
// Whether the value has the desired type is determined by where it is used within the Instance or
// Template, and whether there are schemas which might enforce a more rigid type definition.
type DynValue struct {
ID int64
EncodeStyle EncodeStyle
value interface{}
exprValue ref.Val
declType *DeclType
}
// DeclType returns the policy model type of the dyn value.
func (dv *DynValue) DeclType() *DeclType {
return dv.declType
}
// ConvertToNative is an implementation of the CEL ref.Val method used to adapt between CEL types
// and Go-native types.
//
// The default behavior of this method is to first convert to a CEL type which has a well-defined
// set of conversion behaviors and proxy to the CEL ConvertToNative method for the type.
func (dv *DynValue) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
ev := dv.ExprValue()
if types.IsError(ev) {
return nil, ev.(*types.Err)
}
return ev.ConvertToNative(typeDesc)
}
// Equal returns whether the dyn value is equal to a given CEL value.
func (dv *DynValue) Equal(other ref.Val) ref.Val {
dvType := dv.Type()
otherType := other.Type()
// Preserve CEL's homogeneous equality constraint.
if dvType.TypeName() != otherType.TypeName() {
return types.MaybeNoSuchOverloadErr(other)
}
switch v := dv.value.(type) {
case ref.Val:
return v.Equal(other)
case PlainTextValue:
return celBool(string(v) == other.Value().(string))
case *MultilineStringValue:
return celBool(v.Value == other.Value().(string))
case time.Duration:
otherDuration := other.Value().(time.Duration)
return celBool(v == otherDuration)
case time.Time:
otherTimestamp := other.Value().(time.Time)
return celBool(v.Equal(otherTimestamp))
default:
return celBool(reflect.DeepEqual(v, other.Value()))
}
}
// ExprValue converts the DynValue into a CEL value.
func (dv *DynValue) ExprValue() ref.Val {
return dv.exprValue
}
// Value returns the underlying value held by this reference.
func (dv *DynValue) Value() interface{} {
return dv.value
}
// SetValue updates the underlying value held by this reference.
func (dv *DynValue) SetValue(value interface{}) error {
dv.value = value
var err error
dv.exprValue, dv.declType, err = exprValue(value)
return err
}
// Type returns the CEL type for the given value.
func (dv *DynValue) Type() ref.Type {
return dv.ExprValue().Type()
}
func exprValue(value interface{}) (ref.Val, *DeclType, error) {
switch v := value.(type) {
case bool:
return types.Bool(v), BoolType, nil
case []byte:
return types.Bytes(v), BytesType, nil
case float64:
return types.Double(v), DoubleType, nil
case int64:
return types.Int(v), IntType, nil
case string:
return types.String(v), StringType, nil
case uint64:
return types.Uint(v), UintType, nil
case time.Duration:
return types.Duration{Duration: v}, DurationType, nil
case time.Time:
return types.Timestamp{Time: v}, TimestampType, nil
case types.Null:
return v, NullType, nil
case *ListValue:
return v, ListType, nil
case *MapValue:
return v, MapType, nil
case *ObjectValue:
return v, v.objectType, nil
default:
return nil, unknownType, fmt.Errorf("unsupported type: (%T)%v", v, v)
}
}
// PlainTextValue is a text string literal which must not be treated as an expression.
type PlainTextValue string
// MultilineStringValue is a multiline string value which has been parsed in a way which omits
// whitespace as well as a raw form which preserves whitespace.
type MultilineStringValue struct {
Value string
Raw string
}
func newStructValue() *structValue {
return &structValue{
Fields: []*Field{},
fieldMap: map[string]*Field{},
}
}
type structValue struct {
Fields []*Field
fieldMap map[string]*Field
}
// AddField appends a MapField to the MapValue and indexes the field by name.
func (sv *structValue) AddField(field *Field) {
sv.Fields = append(sv.Fields, field)
sv.fieldMap[field.Name] = field
}
// ConvertToNative converts the MapValue type to a native go types.
func (sv *structValue) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
if typeDesc.Kind() != reflect.Map &&
typeDesc.Kind() != reflect.Struct &&
typeDesc.Kind() != reflect.Pointer &&
typeDesc.Kind() != reflect.Interface {
return nil, fmt.Errorf("type conversion error from object to '%v'", typeDesc)
}
// Unwrap pointers, but track their use.
isPtr := false
if typeDesc.Kind() == reflect.Pointer {
tk := typeDesc
typeDesc = typeDesc.Elem()
if typeDesc.Kind() == reflect.Pointer {
return nil, fmt.Errorf("unsupported type conversion to '%v'", tk)
}
isPtr = true
}
if typeDesc.Kind() == reflect.Map {
keyType := typeDesc.Key()
if keyType.Kind() != reflect.String && keyType.Kind() != reflect.Interface {
return nil, fmt.Errorf("object fields cannot be converted to type '%v'", keyType)
}
elemType := typeDesc.Elem()
sz := len(sv.fieldMap)
ntvMap := reflect.MakeMapWithSize(typeDesc, sz)
for name, val := range sv.fieldMap {
refVal, err := val.Ref.ConvertToNative(elemType)
if err != nil {
return nil, err
}
ntvMap.SetMapIndex(reflect.ValueOf(name), reflect.ValueOf(refVal))
}
return ntvMap.Interface(), nil
}
if typeDesc.Kind() == reflect.Struct {
ntvObjPtr := reflect.New(typeDesc)
ntvObj := ntvObjPtr.Elem()
for name, val := range sv.fieldMap {
f := ntvObj.FieldByName(name)
if !f.IsValid() {
return nil, fmt.Errorf("type conversion error, no such field %s in type %v",
name, typeDesc)
}
fv, err := val.Ref.ConvertToNative(f.Type())
if err != nil {
return nil, err
}
f.Set(reflect.ValueOf(fv))
}
if isPtr {
return ntvObjPtr.Interface(), nil
}
return ntvObj.Interface(), nil
}
return nil, fmt.Errorf("type conversion error from object to '%v'", typeDesc)
}
// GetField returns a MapField by name if one exists.
func (sv *structValue) GetField(name string) (*Field, bool) {
field, found := sv.fieldMap[name]
return field, found
}
// IsSet returns whether the given field, which is defined, has also been set.
func (sv *structValue) IsSet(key ref.Val) ref.Val {
k, ok := key.(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(key)
}
name := string(k)
_, found := sv.fieldMap[name]
return celBool(found)
}
// NewObjectValue creates a struct value with a schema type and returns the empty ObjectValue.
func NewObjectValue(sType *DeclType) *ObjectValue {
return &ObjectValue{
structValue: newStructValue(),
objectType: sType,
}
}
// ObjectValue is a struct with a custom schema type which indicates the fields and types
// associated with the structure.
type ObjectValue struct {
*structValue
objectType *DeclType
}
// ConvertToType is an implementation of the CEL ref.Val interface method.
func (o *ObjectValue) ConvertToType(t ref.Type) ref.Val {
if t == types.TypeType {
return types.NewObjectTypeValue(o.objectType.TypeName())
}
if t.TypeName() == o.objectType.TypeName() {
return o
}
return types.NewErr("type conversion error from '%s' to '%s'", o.Type(), t)
}
// Equal returns true if the two object types are equal and their field values are equal.
func (o *ObjectValue) Equal(other ref.Val) ref.Val {
// Preserve CEL's homogeneous equality semantics.
if o.objectType.TypeName() != other.Type().TypeName() {
return types.MaybeNoSuchOverloadErr(other)
}
o2 := other.(traits.Indexer)
for name := range o.objectType.Fields {
k := types.String(name)
v := o.Get(k)
ov := o2.Get(k)
vEq := v.Equal(ov)
if vEq != types.True {
return vEq
}
}
return types.True
}
// Get returns the value of the specified field.
//
// If the field is set, its value is returned. If the field is not set, the default value for the
// field is returned thus allowing for safe-traversal and preserving proto-like field traversal
// semantics for Open API Schema backed types.
func (o *ObjectValue) Get(name ref.Val) ref.Val {
n, ok := name.(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(n)
}
nameStr := string(n)
field, found := o.fieldMap[nameStr]
if found {
return field.Ref.ExprValue()
}
fieldDef, found := o.objectType.Fields[nameStr]
if !found {
return types.NewErr("no such field: %s", nameStr)
}
defValue := fieldDef.DefaultValue()
if defValue != nil {
return defValue
}
return types.NewErr("no default for type: %s", fieldDef.TypeName())
}
// Type returns the CEL type value of the object.
func (o *ObjectValue) Type() ref.Type {
return o.objectType
}
// Value returns the Go-native representation of the object.
func (o *ObjectValue) Value() interface{} {
return o
}
// NewMapValue returns an empty MapValue.
func NewMapValue() *MapValue {
return &MapValue{
structValue: newStructValue(),
}
}
// MapValue declares an object with a set of named fields whose values are dynamically typed.
type MapValue struct {
*structValue
}
// ConvertToObject produces an ObjectValue from the MapValue with the associated schema type.
//
// The conversion is shallow and the memory shared between the Object and Map as all references
// to the map are expected to be replaced with the Object reference.
func (m *MapValue) ConvertToObject(declType *DeclType) *ObjectValue {
return &ObjectValue{
structValue: m.structValue,
objectType: declType,
}
}
// Contains returns whether the given key is contained in the MapValue.
func (m *MapValue) Contains(key ref.Val) ref.Val {
v, found := m.Find(key)
if v != nil && types.IsUnknownOrError(v) {
return v
}
return celBool(found)
}
// ConvertToType converts the MapValue to another CEL type, if possible.
func (m *MapValue) ConvertToType(t ref.Type) ref.Val {
switch t {
case types.MapType:
return m
case types.TypeType:
return types.MapType
}
return types.NewErr("type conversion error from '%s' to '%s'", m.Type(), t)
}
// Equal returns true if the maps are of the same size, have the same keys, and the key-values
// from each map are equal.
func (m *MapValue) Equal(other ref.Val) ref.Val {
oMap, isMap := other.(traits.Mapper)
if !isMap {
return types.MaybeNoSuchOverloadErr(other)
}
if m.Size() != oMap.Size() {
return types.False
}
for name, field := range m.fieldMap {
k := types.String(name)
ov, found := oMap.Find(k)
if !found {
return types.False
}
v := field.Ref.ExprValue()
vEq := v.Equal(ov)
if vEq != types.True {
return vEq
}
}
return types.True
}
// Find returns the value for the key in the map, if found.
func (m *MapValue) Find(name ref.Val) (ref.Val, bool) {
// Currently only maps with string keys are supported as this is best aligned with JSON,
// and also much simpler to support.
n, ok := name.(types.String)
if !ok {
return types.MaybeNoSuchOverloadErr(n), true
}
nameStr := string(n)
field, found := m.fieldMap[nameStr]
if found {
return field.Ref.ExprValue(), true
}
return nil, false
}
// Get returns the value for the key in the map, or error if not found.
func (m *MapValue) Get(key ref.Val) ref.Val {
v, found := m.Find(key)
if found {
return v
}
return types.ValOrErr(key, "no such key: %v", key)
}
// Iterator produces a traits.Iterator which walks over the map keys.
//
// The Iterator is frequently used within comprehensions.
func (m *MapValue) Iterator() traits.Iterator {
keys := make([]ref.Val, len(m.fieldMap))
i := 0
for k := range m.fieldMap {
keys[i] = types.String(k)
i++
}
return &baseMapIterator{
baseVal: &baseVal{},
keys: keys,
}
}
// Size returns the number of keys in the map.
func (m *MapValue) Size() ref.Val {
return types.Int(len(m.Fields))
}
// Type returns the CEL ref.Type for the map.
func (m *MapValue) Type() ref.Type {
return types.MapType
}
// Value returns the Go-native representation of the MapValue.
func (m *MapValue) Value() interface{} {
return m
}
type baseMapIterator struct {
*baseVal
keys []ref.Val
idx int
}
// HasNext implements the traits.Iterator interface method.
func (it *baseMapIterator) HasNext() ref.Val {
if it.idx < len(it.keys) {
return types.True
}
return types.False
}
// Next implements the traits.Iterator interface method.
func (it *baseMapIterator) Next() ref.Val {
key := it.keys[it.idx]
it.idx++
return key
}
// Type implements the CEL ref.Val interface metohd.
func (it *baseMapIterator) Type() ref.Type {
return types.IteratorType
}
// NewField returns a MapField instance with an empty DynValue that refers to the
// specified parse node id and field name.
func NewField(id int64, name string) *Field {
return &Field{
ID: id,
Name: name,
Ref: NewEmptyDynValue(),
}
}
// Field specifies a field name and a reference to a dynamic value.
type Field struct {
ID int64
Name string
Ref *DynValue
}
// NewListValue returns an empty ListValue instance.
func NewListValue() *ListValue {
return &ListValue{
Entries: []*DynValue{},
}
}
// ListValue contains a list of dynamically typed entries.
type ListValue struct {
Entries []*DynValue
initValueSet sync.Once
valueSet map[ref.Val]struct{}
}
// Add concatenates two lists together to produce a new CEL list value.
func (lv *ListValue) Add(other ref.Val) ref.Val {
oArr, isArr := other.(traits.Lister)
if !isArr {
return types.MaybeNoSuchOverloadErr(other)
}
szRight := len(lv.Entries)
szLeft := int(oArr.Size().(types.Int))
sz := szRight + szLeft
combo := make([]ref.Val, sz)
for i := 0; i < szRight; i++ {
combo[i] = lv.Entries[i].ExprValue()
}
for i := 0; i < szLeft; i++ {
combo[i+szRight] = oArr.Get(types.Int(i))
}
return types.DefaultTypeAdapter.NativeToValue(combo)
}
// Append adds another entry into the ListValue.
func (lv *ListValue) Append(entry *DynValue) {
lv.Entries = append(lv.Entries, entry)
// The append resets all previously built indices.
lv.initValueSet = sync.Once{}
}
// Contains returns whether the input `val` is equal to an element in the list.
//
// If any pair-wise comparison between the input value and the list element is an error, the
// operation will return an error.
func (lv *ListValue) Contains(val ref.Val) ref.Val {
if types.IsUnknownOrError(val) {
return val
}
lv.initValueSet.Do(lv.finalizeValueSet)
if lv.valueSet != nil {
_, found := lv.valueSet[val]
if found {
return types.True
}
// Instead of returning false, ensure that CEL's heterogeneous equality constraint
// is satisfied by allowing pair-wise equality behavior to determine the outcome.
}
var err ref.Val
sz := len(lv.Entries)
for i := 0; i < sz; i++ {
elem := lv.Entries[i]
cmp := elem.Equal(val)
b, ok := cmp.(types.Bool)
if !ok && err == nil {
err = types.MaybeNoSuchOverloadErr(cmp)
}
if b == types.True {
return types.True
}
}
if err != nil {
return err
}
return types.False
}
// ConvertToNative is an implementation of the CEL ref.Val method used to adapt between CEL types
// and Go-native array-like types.
func (lv *ListValue) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
// Non-list conversion.
if typeDesc.Kind() != reflect.Slice &&
typeDesc.Kind() != reflect.Array &&
typeDesc.Kind() != reflect.Interface {
return nil, fmt.Errorf("type conversion error from list to '%v'", typeDesc)
}
// If the list is already assignable to the desired type return it.
if reflect.TypeOf(lv).AssignableTo(typeDesc) {
return lv, nil
}
// List conversion.
otherElem := typeDesc.Elem()
// Allow the element ConvertToNative() function to determine whether conversion is possible.
sz := len(lv.Entries)
nativeList := reflect.MakeSlice(typeDesc, int(sz), int(sz))
for i := 0; i < sz; i++ {
elem := lv.Entries[i]
nativeElemVal, err := elem.ConvertToNative(otherElem)
if err != nil {
return nil, err
}
nativeList.Index(int(i)).Set(reflect.ValueOf(nativeElemVal))
}
return nativeList.Interface(), nil
}
// ConvertToType converts the ListValue to another CEL type.
func (lv *ListValue) ConvertToType(t ref.Type) ref.Val {
switch t {
case types.ListType:
return lv
case types.TypeType:
return types.ListType
}
return types.NewErr("type conversion error from '%s' to '%s'", ListType, t)
}
// Equal returns true if two lists are of the same size, and the values at each index are also
// equal.
func (lv *ListValue) Equal(other ref.Val) ref.Val {
oArr, isArr := other.(traits.Lister)
if !isArr {
return types.MaybeNoSuchOverloadErr(other)
}
sz := types.Int(len(lv.Entries))
if sz != oArr.Size() {
return types.False
}
for i := types.Int(0); i < sz; i++ {
cmp := lv.Get(i).Equal(oArr.Get(i))
if cmp != types.True {
return cmp
}
}
return types.True
}
// Get returns the value at the given index.
//
// If the index is negative or greater than the size of the list, an error is returned.
func (lv *ListValue) Get(idx ref.Val) ref.Val {
iv, isInt := idx.(types.Int)
if !isInt {
return types.ValOrErr(idx, "unsupported index: %v", idx)
}
i := int(iv)
if i < 0 || i >= len(lv.Entries) {
return types.NewErr("index out of bounds: %v", idx)
}
return lv.Entries[i].ExprValue()
}
// Iterator produces a traits.Iterator suitable for use in CEL comprehension macros.
func (lv *ListValue) Iterator() traits.Iterator {
return &baseListIterator{
getter: lv.Get,
sz: len(lv.Entries),
}
}
// Size returns the number of elements in the list.
func (lv *ListValue) Size() ref.Val {
return types.Int(len(lv.Entries))
}
// Type returns the CEL ref.Type for the list.
func (lv *ListValue) Type() ref.Type {
return types.ListType
}
// Value returns the Go-native value.
func (lv *ListValue) Value() interface{} {
return lv
}
// finalizeValueSet inspects the ListValue entries in order to make internal optimizations once all list
// entries are known.
func (lv *ListValue) finalizeValueSet() {
valueSet := make(map[ref.Val]struct{})
for _, e := range lv.Entries {
switch e.value.(type) {
case bool, float64, int64, string, uint64, types.Null, PlainTextValue:
valueSet[e.ExprValue()] = struct{}{}
default:
lv.valueSet = nil
return
}
}
lv.valueSet = valueSet
}
type baseVal struct{}
func (*baseVal) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
return nil, fmt.Errorf("unsupported native conversion to: %v", typeDesc)
}
func (*baseVal) ConvertToType(t ref.Type) ref.Val {
return types.NewErr("unsupported type conversion to: %v", t)
}
func (*baseVal) Equal(other ref.Val) ref.Val {
return types.NewErr("unsupported equality test between instances")
}
func (v *baseVal) Value() interface{} {
return nil
}
type baseListIterator struct {
*baseVal
getter func(idx ref.Val) ref.Val
sz int
idx int
}
func (it *baseListIterator) HasNext() ref.Val {
if it.idx < it.sz {
return types.True
}
return types.False
}
func (it *baseListIterator) Next() ref.Val {
v := it.getter(types.Int(it.idx))
it.idx++
return v
}
func (it *baseListIterator) Type() ref.Type {
return types.IteratorType
}
func celBool(pred bool) ref.Val {
if pred {
return types.True
}
return types.False
}
var unknownType = &DeclType{name: "unknown", MinSerializedSize: 1}