feat: kubesphere 4.0 (#6115)
* feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> * feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> --------- Signed-off-by: ci-bot <ci-bot@kubesphere.io> Co-authored-by: ks-ci-bot <ks-ci-bot@example.com> Co-authored-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
committed by
GitHub
parent
b5015ec7b9
commit
447a51f08b
11
vendor/k8s.io/apiserver/pkg/cel/OWNERS
generated
vendored
Normal file
11
vendor/k8s.io/apiserver/pkg/cel/OWNERS
generated
vendored
Normal 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
|
||||
106
vendor/k8s.io/apiserver/pkg/cel/common/adaptor.go
generated
vendored
Normal file
106
vendor/k8s.io/apiserver/pkg/cel/common/adaptor.go
generated
vendored
Normal 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
vendor/k8s.io/apiserver/pkg/cel/common/equality.go
generated
vendored
Normal file
334
vendor/k8s.io/apiserver/pkg/cel/common/equality.go
generated
vendored
Normal 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
vendor/k8s.io/apiserver/pkg/cel/common/maplist.go
generated
vendored
Normal file
177
vendor/k8s.io/apiserver/pkg/cel/common/maplist.go
generated
vendored
Normal 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
vendor/k8s.io/apiserver/pkg/cel/common/schemas.go
generated
vendored
Normal file
274
vendor/k8s.io/apiserver/pkg/cel/common/schemas.go
generated
vendored
Normal 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
|
||||
}
|
||||
721
vendor/k8s.io/apiserver/pkg/cel/common/values.go
generated
vendored
Normal file
721
vendor/k8s.io/apiserver/pkg/cel/common/values.go
generated
vendored
Normal 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
|
||||
}
|
||||
160
vendor/k8s.io/apiserver/pkg/cel/environment/base.go
generated
vendored
Normal file
160
vendor/k8s.io/apiserver/pkg/cel/environment/base.go
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// DefaultCompatibilityVersion returns a default compatibility version for use with EnvSet
|
||||
// that guarantees compatibility with CEL features/libraries/parameters understood by
|
||||
// an n-1 version
|
||||
//
|
||||
// This default will be set to no more than n-1 the current Kubernetes major.minor version.
|
||||
//
|
||||
// Note that a default version number less than n-1 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.
|
||||
func DefaultCompatibilityVersion() *version.Version {
|
||||
return version.MajorMinor(1, 28)
|
||||
}
|
||||
|
||||
var baseOpts = []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),
|
||||
|
||||
library.URLs(),
|
||||
library.Regex(),
|
||||
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{
|
||||
library.Authz(),
|
||||
},
|
||||
},
|
||||
{
|
||||
IntroducedVersion: version.MajorMinor(1, 28),
|
||||
EnvOptions: []cel.EnvOption{
|
||||
cel.CrossTypeNumericComparisons(true),
|
||||
cel.OptionalTypes(),
|
||||
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(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 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.
|
||||
func MustBaseEnvSet(ver *version.Version) *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)
|
||||
if entry, ok := baseEnvs.Load(key); ok {
|
||||
return entry.(*EnvSet)
|
||||
}
|
||||
|
||||
entry, _, _ := baseEnvsSingleflight.Do(key, func() (interface{}, error) {
|
||||
entry := mustNewEnvSet(ver, baseOpts)
|
||||
baseEnvs.Store(key, entry)
|
||||
return entry, nil
|
||||
})
|
||||
return entry.(*EnvSet)
|
||||
}
|
||||
|
||||
var (
|
||||
baseEnvs = sync.Map{}
|
||||
baseEnvsSingleflight = &singleflight.Group{}
|
||||
)
|
||||
274
vendor/k8s.io/apiserver/pkg/cel/environment/environment.go
generated
vendored
Normal file
274
vendor/k8s.io/apiserver/pkg/cel/environment/environment.go
generated
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
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
|
||||
|
||||
// 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, 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), 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, opts []VersionedOptions) (cel.EnvOption, error) {
|
||||
var envOpts []cel.EnvOption
|
||||
var progOpts []cel.ProgramOption
|
||||
var declTypes []*apiservercel.DeclType
|
||||
|
||||
for _, opt := range opts {
|
||||
if compatVer.AtLeast(opt.IntroducedVersion) && (opt.RemovedVersion == nil || compatVer.LessThan(opt.RemovedVersion)) {
|
||||
envOpts = append(envOpts, opt.EnvOptions...)
|
||||
progOpts = append(progOpts, opt.ProgramOptions...)
|
||||
declTypes = append(declTypes, opt.DeclTypes...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(declTypes) > 0 {
|
||||
provider := apiservercel.NewDeclTypeProvider(declTypes...)
|
||||
providerOpts, err := provider.EnvOptions(base.TypeProvider())
|
||||
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
|
||||
}
|
||||
191
vendor/k8s.io/apiserver/pkg/cel/lazy/lazy.go
generated
vendored
Normal file
191
vendor/k8s.io/apiserver/pkg/cel/lazy/lazy.go
generated
vendored
Normal 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)
|
||||
626
vendor/k8s.io/apiserver/pkg/cel/library/authz.go
generated
vendored
Normal file
626
vendor/k8s.io/apiserver/pkg/cel/library/authz.go
generated
vendored
Normal file
@@ -0,0 +1,626 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"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()
|
||||
func Authz() cel.EnvOption {
|
||||
return cel.Lib(authzLib)
|
||||
}
|
||||
|
||||
var authzLib = &authz{}
|
||||
|
||||
type authz struct{}
|
||||
|
||||
func (*authz) LibraryName() string {
|
||||
return "k8s.authz"
|
||||
}
|
||||
|
||||
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{}
|
||||
}
|
||||
|
||||
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 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
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
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()
|
||||
}
|
||||
80
vendor/k8s.io/apiserver/pkg/cel/library/cost.go
generated
vendored
80
vendor/k8s.io/apiserver/pkg/cel/library/cost.go
generated
vendored
@@ -36,6 +36,15 @@ type CostEstimator struct {
|
||||
|
||||
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 "isSorted", "sum", "max", "min", "indexOf", "lastIndexOf":
|
||||
var cost uint64
|
||||
if len(args) > 0 {
|
||||
@@ -78,6 +87,13 @@ func (l *CostEstimator) EstimateCallCost(function, overloadId string, target *ch
|
||||
// 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 "isSorted", "sum", "max", "min", "indexOf", "lastIndexOf":
|
||||
if target != nil {
|
||||
// Charge 1 cost for comparing each element in the list
|
||||
@@ -85,8 +101,8 @@ func (l *CostEstimator) EstimateCallCost(function, overloadId string, target *ch
|
||||
// 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 {
|
||||
t := elNode.Type().GetPrimitive()
|
||||
if t == exprpb.Type_STRING || t == exprpb.Type_BYTES {
|
||||
k := elNode.Type().Kind()
|
||||
if k == types.StringKind || k == types.BytesKind {
|
||||
sz := l.sizeEstimate(elNode)
|
||||
elCost = elCost.Add(sz.MultiplyByCostFactor(common.StringTraversalCostFactor))
|
||||
}
|
||||
@@ -94,7 +110,6 @@ func (l *CostEstimator) EstimateCallCost(function, overloadId string, target *ch
|
||||
} 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":
|
||||
if len(args) == 1 {
|
||||
@@ -111,13 +126,51 @@ func (l *CostEstimator) EstimateCallCost(function, overloadId string, target *ch
|
||||
sz := l.sizeEstimate(*target)
|
||||
toReplaceSz := l.sizeEstimate(args[0])
|
||||
replaceWithSz := l.sizeEstimate(args[1])
|
||||
// smallest possible result: smallest input size composed of the largest possible substrings being replaced by smallest possible replacement
|
||||
minSz := uint64(math.Ceil(float64(sz.Min)/float64(toReplaceSz.Max))) * replaceWithSz.Min
|
||||
// largest possible result: largest input size composed of the smallest possible substrings being replaced by largest possible replacement
|
||||
maxSz := uint64(math.Ceil(float64(sz.Max)/float64(toReplaceSz.Min))) * replaceWithSz.Max
|
||||
|
||||
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: &checker.SizeEstimate{Min: minSz, Max: maxSz}}
|
||||
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(2 * common.StringTraversalCostFactor), ResultSize: &size}
|
||||
}
|
||||
case "split":
|
||||
if target != nil {
|
||||
@@ -194,7 +247,8 @@ func (l *CostEstimator) sizeEstimate(t checker.AstNode) checker.SizeEstimate {
|
||||
}
|
||||
|
||||
func (l *CostEstimator) listElementNode(list checker.AstNode) checker.AstNode {
|
||||
if lt := list.Type().GetListType(); lt != nil {
|
||||
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
|
||||
@@ -202,10 +256,10 @@ func (l *CostEstimator) listElementNode(list checker.AstNode) checker.AstNode {
|
||||
path := make([]string, len(nodePath)+1)
|
||||
copy(path, nodePath)
|
||||
path[len(nodePath)] = "@items"
|
||||
return &itemsNode{path: path, t: lt.GetElemType(), expr: nil}
|
||||
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.GetElemType(), expr: nil}
|
||||
return &itemsNode{t: lt, expr: nil}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -220,7 +274,7 @@ func (l *CostEstimator) EstimateSize(element checker.AstNode) *checker.SizeEstim
|
||||
|
||||
type itemsNode struct {
|
||||
path []string
|
||||
t *exprpb.Type
|
||||
t *types.Type
|
||||
expr *exprpb.Expr
|
||||
}
|
||||
|
||||
@@ -228,7 +282,7 @@ func (i *itemsNode) Path() []string {
|
||||
return i.path
|
||||
}
|
||||
|
||||
func (i *itemsNode) Type() *exprpb.Type {
|
||||
func (i *itemsNode) Type() *types.Type {
|
||||
return i.t
|
||||
}
|
||||
|
||||
|
||||
34
vendor/k8s.io/apiserver/pkg/cel/library/libraries.go
generated
vendored
34
vendor/k8s.io/apiserver/pkg/cel/library/libraries.go
generated
vendored
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
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 (
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/ext"
|
||||
"github.com/google/cel-go/interpreter"
|
||||
)
|
||||
|
||||
// ExtensionLibs declares the set of CEL extension libraries available everywhere CEL is used in Kubernetes.
|
||||
var ExtensionLibs = append(k8sExtensionLibs, ext.Strings())
|
||||
|
||||
var k8sExtensionLibs = []cel.EnvOption{
|
||||
URLs(),
|
||||
Regex(),
|
||||
Lists(),
|
||||
}
|
||||
|
||||
var ExtensionLibRegexOptimizations = []*interpreter.RegexOptimization{FindRegexOptimization, FindAllRegexOptimization}
|
||||
4
vendor/k8s.io/apiserver/pkg/cel/library/lists.go
generated
vendored
4
vendor/k8s.io/apiserver/pkg/cel/library/lists.go
generated
vendored
@@ -95,6 +95,10 @@ var listsLib = &lists{}
|
||||
|
||||
type lists struct{}
|
||||
|
||||
func (*lists) LibraryName() string {
|
||||
return "k8s.lists"
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
380
vendor/k8s.io/apiserver/pkg/cel/library/quantity.go
generated
vendored
Normal file
380
vendor/k8s.io/apiserver/pkg/cel/library/quantity.go
generated
vendored
Normal file
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
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 "k8s.quantity"
|
||||
}
|
||||
|
||||
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: ©,
|
||||
}
|
||||
}
|
||||
|
||||
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: ©,
|
||||
}
|
||||
}
|
||||
|
||||
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: ©,
|
||||
}
|
||||
}
|
||||
|
||||
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: ©,
|
||||
}
|
||||
}
|
||||
8
vendor/k8s.io/apiserver/pkg/cel/library/regex.go
generated
vendored
8
vendor/k8s.io/apiserver/pkg/cel/library/regex.go
generated
vendored
@@ -51,6 +51,10 @@ var regexLib = ®ex{}
|
||||
|
||||
type regex struct{}
|
||||
|
||||
func (*regex) LibraryName() string {
|
||||
return "k8s.regex"
|
||||
}
|
||||
|
||||
var regexLibraryDecls = map[string][]cel.FunctionOpt{
|
||||
"find": {
|
||||
cel.MemberOverload("string_find_string", []*cel.Type{cel.StringType, cel.StringType}, cel.StringType,
|
||||
@@ -77,7 +81,9 @@ func (*regex) CompileOptions() []cel.EnvOption {
|
||||
}
|
||||
|
||||
func (*regex) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{}
|
||||
return []cel.ProgramOption{
|
||||
cel.OptimizeRegex(FindRegexOptimization, FindAllRegexOptimization),
|
||||
}
|
||||
}
|
||||
|
||||
func find(strVal ref.Val, regexVal ref.Val) ref.Val {
|
||||
|
||||
83
vendor/k8s.io/apiserver/pkg/cel/library/test.go
generated
vendored
Normal file
83
vendor/k8s.io/apiserver/pkg/cel/library/test.go
generated
vendored
Normal 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 "k8s.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{}
|
||||
}
|
||||
8
vendor/k8s.io/apiserver/pkg/cel/library/urls.go
generated
vendored
8
vendor/k8s.io/apiserver/pkg/cel/library/urls.go
generated
vendored
@@ -61,9 +61,9 @@ import (
|
||||
//
|
||||
// - getScheme: If absent in the URL, returns an empty string.
|
||||
//
|
||||
// - getHostname: IPv6 addresses are returned with braces, e.g. "[::1]". 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 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.
|
||||
@@ -112,6 +112,10 @@ var urlsLib = &urls{}
|
||||
|
||||
type urls struct{}
|
||||
|
||||
func (*urls) LibraryName() string {
|
||||
return "k8s.urls"
|
||||
}
|
||||
|
||||
var urlLibraryDecls = map[string][]cel.FunctionOpt{
|
||||
"url": {
|
||||
cel.Overload("string_to_url", []*cel.Type{cel.StringType}, apiservercel.URLType,
|
||||
|
||||
4
vendor/k8s.io/apiserver/pkg/cel/limits.go
generated
vendored
4
vendor/k8s.io/apiserver/pkg/cel/limits.go
generated
vendored
@@ -16,9 +16,11 @@ 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 = int64(3 * 1024 * 1024)
|
||||
DefaultMaxRequestSizeBytes = celconfig.MaxRequestSizeBytes
|
||||
|
||||
// MaxDurationSizeJSON
|
||||
// OpenAPI duration strings follow RFC 3339, section 5.6 - see the comment on maxDatetimeSizeJSON
|
||||
|
||||
74
vendor/k8s.io/apiserver/pkg/cel/metrics/metrics.go
generated
vendored
Normal file
74
vendor/k8s.io/apiserver/pkg/cel/metrics/metrics.go
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
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 metrics
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
)
|
||||
|
||||
// TODO(jiahuif) CEL is to be used in multiple components, revise naming when that happens.
|
||||
const (
|
||||
namespace = "apiserver"
|
||||
subsystem = "cel"
|
||||
)
|
||||
|
||||
// Metrics provides access to CEL metrics.
|
||||
var Metrics = newCelMetrics()
|
||||
|
||||
type CelMetrics struct {
|
||||
compilationTime *metrics.Histogram
|
||||
evaluationTime *metrics.Histogram
|
||||
}
|
||||
|
||||
func newCelMetrics() *CelMetrics {
|
||||
m := &CelMetrics{
|
||||
compilationTime: metrics.NewHistogram(&metrics.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "compilation_duration_seconds",
|
||||
Help: "CEL compilation time in seconds.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
}),
|
||||
evaluationTime: metrics.NewHistogram(&metrics.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "evaluation_duration_seconds",
|
||||
Help: "CEL evaluation time in seconds.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
}),
|
||||
}
|
||||
|
||||
legacyregistry.MustRegister(m.compilationTime)
|
||||
legacyregistry.MustRegister(m.evaluationTime)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// ObserveCompilation records a CEL compilation with the time the compilation took.
|
||||
func (m *CelMetrics) ObserveCompilation(elapsed time.Duration) {
|
||||
seconds := elapsed.Seconds()
|
||||
m.compilationTime.Observe(seconds)
|
||||
}
|
||||
|
||||
// ObserveEvaluation records a CEL evaluation with the time the evaluation took.
|
||||
func (m *CelMetrics) ObserveEvaluation(elapsed time.Duration) {
|
||||
seconds := elapsed.Seconds()
|
||||
m.evaluationTime.Observe(seconds)
|
||||
}
|
||||
229
vendor/k8s.io/apiserver/pkg/cel/openapi/adaptor.go
generated
vendored
Normal file
229
vendor/k8s.io/apiserver/pkg/cel/openapi/adaptor.go
generated
vendored
Normal 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)
|
||||
}
|
||||
107
vendor/k8s.io/apiserver/pkg/cel/openapi/extensions.go
generated
vendored
Normal file
107
vendor/k8s.io/apiserver/pkg/cel/openapi/extensions.go
generated
vendored
Normal 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"
|
||||
45
vendor/k8s.io/apiserver/pkg/cel/openapi/resolver/combined.go
generated
vendored
Normal file
45
vendor/k8s.io/apiserver/pkg/cel/openapi/resolver/combined.go
generated
vendored
Normal 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)
|
||||
}
|
||||
114
vendor/k8s.io/apiserver/pkg/cel/openapi/resolver/definitions.go
generated
vendored
Normal file
114
vendor/k8s.io/apiserver/pkg/cel/openapi/resolver/definitions.go
generated
vendored
Normal 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
|
||||
}
|
||||
104
vendor/k8s.io/apiserver/pkg/cel/openapi/resolver/discovery.go
generated
vendored
Normal file
104
vendor/k8s.io/apiserver/pkg/cel/openapi/resolver/discovery.go
generated
vendored
Normal 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"
|
||||
122
vendor/k8s.io/apiserver/pkg/cel/openapi/resolver/refs.go
generated
vendored
Normal file
122
vendor/k8s.io/apiserver/pkg/cel/openapi/resolver/refs.go
generated
vendored
Normal 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
|
||||
}
|
||||
39
vendor/k8s.io/apiserver/pkg/cel/openapi/resolver/resolver.go
generated
vendored
Normal file
39
vendor/k8s.io/apiserver/pkg/cel/openapi/resolver/resolver.go
generated
vendored
Normal 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
vendor/k8s.io/apiserver/pkg/cel/quantity.go
generated
vendored
Normal file
76
vendor/k8s.io/apiserver/pkg/cel/quantity.go
generated
vendored
Normal 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 typeValue:
|
||||
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
|
||||
}
|
||||
79
vendor/k8s.io/apiserver/pkg/cel/registry.go
generated
vendored
79
vendor/k8s.io/apiserver/pkg/cel/registry.go
generated
vendored
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
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 (
|
||||
"sync"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
)
|
||||
|
||||
// Resolver declares methods to find policy templates and related configuration objects.
|
||||
type Resolver interface {
|
||||
// FindType returns a DeclType instance corresponding to the given fully-qualified name, if
|
||||
// present.
|
||||
FindType(name string) (*DeclType, bool)
|
||||
}
|
||||
|
||||
// NewRegistry create a registry for keeping track of environments and types
|
||||
// from a base cel.Env expression environment.
|
||||
func NewRegistry(stdExprEnv *cel.Env) *Registry {
|
||||
return &Registry{
|
||||
exprEnvs: map[string]*cel.Env{"": stdExprEnv},
|
||||
types: map[string]*DeclType{
|
||||
BoolType.TypeName(): BoolType,
|
||||
BytesType.TypeName(): BytesType,
|
||||
DoubleType.TypeName(): DoubleType,
|
||||
DurationType.TypeName(): DurationType,
|
||||
IntType.TypeName(): IntType,
|
||||
NullType.TypeName(): NullType,
|
||||
StringType.TypeName(): StringType,
|
||||
TimestampType.TypeName(): TimestampType,
|
||||
UintType.TypeName(): UintType,
|
||||
ListType.TypeName(): ListType,
|
||||
MapType.TypeName(): MapType,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Registry defines a repository of environment, schema, template, and type definitions.
|
||||
//
|
||||
// Registry instances are concurrency-safe.
|
||||
type Registry struct {
|
||||
rwMux sync.RWMutex
|
||||
exprEnvs map[string]*cel.Env
|
||||
types map[string]*DeclType
|
||||
}
|
||||
|
||||
// FindType implements the Resolver interface method.
|
||||
func (r *Registry) FindType(name string) (*DeclType, bool) {
|
||||
r.rwMux.RLock()
|
||||
defer r.rwMux.RUnlock()
|
||||
typ, found := r.types[name]
|
||||
if found {
|
||||
return typ, true
|
||||
}
|
||||
return typ, found
|
||||
}
|
||||
|
||||
// SetType registers a DeclType descriptor by its fully qualified name.
|
||||
func (r *Registry) SetType(name string, declType *DeclType) error {
|
||||
r.rwMux.Lock()
|
||||
defer r.rwMux.Unlock()
|
||||
r.types[name] = declType
|
||||
return nil
|
||||
}
|
||||
171
vendor/k8s.io/apiserver/pkg/cel/types.go
generated
vendored
171
vendor/k8s.io/apiserver/pkg/cel/types.go
generated
vendored
@@ -319,59 +319,83 @@ func (f *DeclField) EnumValues() []ref.Val {
|
||||
return ev
|
||||
}
|
||||
|
||||
// NewRuleTypes returns an Open API Schema-based type-system which is CEL compatible.
|
||||
func NewRuleTypes(kind string,
|
||||
declType *DeclType,
|
||||
res Resolver) (*RuleTypes, error) {
|
||||
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.
|
||||
schemaTypes, err := newSchemaTypeProvider(kind, declType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
allTypes := allTypesForDecl(rootTypes)
|
||||
return &DeclTypeProvider{
|
||||
registeredTypes: allTypes,
|
||||
}
|
||||
if schemaTypes == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &RuleTypes{
|
||||
ruleSchemaDeclTypes: schemaTypes,
|
||||
resolver: res,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RuleTypes extends the CEL ref.TypeProvider interface and provides an Open API Schema-based
|
||||
// DeclTypeProvider extends the CEL ref.TypeProvider interface and provides an Open API Schema-based
|
||||
// type-system.
|
||||
type RuleTypes struct {
|
||||
ref.TypeProvider
|
||||
ruleSchemaDeclTypes *schemaTypeProvider
|
||||
typeAdapter ref.TypeAdapter
|
||||
resolver Resolver
|
||||
type DeclTypeProvider struct {
|
||||
registeredTypes map[string]*DeclType
|
||||
typeProvider ref.TypeProvider
|
||||
typeAdapter ref.TypeAdapter
|
||||
}
|
||||
|
||||
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.
|
||||
//
|
||||
// Note, the standard declaration set includes 'rule' which is defined as the top-level rule-schema
|
||||
// type if one is configured.
|
||||
//
|
||||
// If the RuleTypes value is nil, an empty []cel.EnvOption set is returned.
|
||||
func (rt *RuleTypes) EnvOptions(tp ref.TypeProvider) ([]cel.EnvOption, error) {
|
||||
// If the DeclTypeProvider value is nil, an empty []cel.EnvOption set is returned.
|
||||
func (rt *DeclTypeProvider) EnvOptions(tp ref.TypeProvider) ([]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 ref.TypeProvider) (*DeclTypeProvider, error) {
|
||||
if rt == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var ta ref.TypeAdapter = types.DefaultTypeAdapter
|
||||
tpa, ok := tp.(ref.TypeAdapter)
|
||||
if ok {
|
||||
ta = tpa
|
||||
}
|
||||
rtWithTypes := &RuleTypes{
|
||||
TypeProvider: tp,
|
||||
typeAdapter: ta,
|
||||
ruleSchemaDeclTypes: rt.ruleSchemaDeclTypes,
|
||||
resolver: rt.resolver,
|
||||
rtWithTypes := &DeclTypeProvider{
|
||||
typeProvider: tp,
|
||||
typeAdapter: ta,
|
||||
registeredTypes: rt.registeredTypes,
|
||||
}
|
||||
for name, declType := range rt.ruleSchemaDeclTypes.types {
|
||||
for name, declType := range rt.registeredTypes {
|
||||
tpType, found := tp.FindType(name)
|
||||
expT, err := declType.ExprType()
|
||||
if err != nil {
|
||||
@@ -379,14 +403,10 @@ func (rt *RuleTypes) EnvOptions(tp ref.TypeProvider) ([]cel.EnvOption, error) {
|
||||
}
|
||||
if found && !proto.Equal(tpType, expT) {
|
||||
return nil, fmt.Errorf(
|
||||
"type %s definition differs between CEL environment and rule", name)
|
||||
"type %s definition differs between CEL environment and type provider", name)
|
||||
}
|
||||
}
|
||||
return []cel.EnvOption{
|
||||
cel.CustomTypeProvider(rtWithTypes),
|
||||
cel.CustomTypeAdapter(rtWithTypes),
|
||||
cel.Variable("rule", rt.ruleSchemaDeclTypes.root.CelType()),
|
||||
}, nil
|
||||
return rtWithTypes, nil
|
||||
}
|
||||
|
||||
// FindType attempts to resolve the typeName provided from the rule's rule-schema, or if not
|
||||
@@ -396,7 +416,7 @@ func (rt *RuleTypes) EnvOptions(tp ref.TypeProvider) ([]cel.EnvOption, error) {
|
||||
//
|
||||
// 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 *RuleTypes) FindType(typeName string) (*exprpb.Type, bool) {
|
||||
func (rt *DeclTypeProvider) FindType(typeName string) (*exprpb.Type, bool) {
|
||||
if rt == nil {
|
||||
return nil, false
|
||||
}
|
||||
@@ -408,11 +428,11 @@ func (rt *RuleTypes) FindType(typeName string) (*exprpb.Type, bool) {
|
||||
}
|
||||
return expT, found
|
||||
}
|
||||
return rt.TypeProvider.FindType(typeName)
|
||||
return rt.typeProvider.FindType(typeName)
|
||||
}
|
||||
|
||||
// FindDeclType returns the CPT type description which can be mapped to a CEL type.
|
||||
func (rt *RuleTypes) FindDeclType(typeName string) (*DeclType, bool) {
|
||||
func (rt *DeclTypeProvider) FindDeclType(typeName string) (*DeclType, bool) {
|
||||
if rt == nil {
|
||||
return nil, false
|
||||
}
|
||||
@@ -425,10 +445,10 @@ func (rt *RuleTypes) FindDeclType(typeName string) (*DeclType, bool) {
|
||||
// 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 *RuleTypes) FindFieldType(typeName, fieldName string) (*ref.FieldType, bool) {
|
||||
func (rt *DeclTypeProvider) FindFieldType(typeName, fieldName string) (*ref.FieldType, bool) {
|
||||
st, found := rt.findDeclType(typeName)
|
||||
if !found {
|
||||
return rt.TypeProvider.FindFieldType(typeName, fieldName)
|
||||
return rt.typeProvider.FindFieldType(typeName, fieldName)
|
||||
}
|
||||
|
||||
f, found := st.Fields[fieldName]
|
||||
@@ -458,48 +478,63 @@ func (rt *RuleTypes) FindFieldType(typeName, fieldName string) (*ref.FieldType,
|
||||
|
||||
// NativeToValue is an implementation of the ref.TypeAdapater interface which supports conversion
|
||||
// of rule values to CEL ref.Val instances.
|
||||
func (rt *RuleTypes) NativeToValue(val interface{}) ref.Val {
|
||||
func (rt *DeclTypeProvider) NativeToValue(val interface{}) ref.Val {
|
||||
return rt.typeAdapter.NativeToValue(val)
|
||||
}
|
||||
|
||||
// TypeNames returns the list of type names declared within the RuleTypes object.
|
||||
func (rt *RuleTypes) TypeNames() []string {
|
||||
typeNames := make([]string, len(rt.ruleSchemaDeclTypes.types))
|
||||
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.ruleSchemaDeclTypes.types {
|
||||
for name := range rt.registeredTypes {
|
||||
typeNames[i] = name
|
||||
i++
|
||||
}
|
||||
return typeNames
|
||||
}
|
||||
|
||||
func (rt *RuleTypes) findDeclType(typeName string) (*DeclType, bool) {
|
||||
declType, found := rt.ruleSchemaDeclTypes.types[typeName]
|
||||
func (rt *DeclTypeProvider) findDeclType(typeName string) (*DeclType, bool) {
|
||||
declType, found := rt.registeredTypes[typeName]
|
||||
if found {
|
||||
return declType, true
|
||||
}
|
||||
declType, found = rt.resolver.FindType(typeName)
|
||||
if found {
|
||||
return declType, true
|
||||
}
|
||||
return nil, false
|
||||
declType = findScalar(typeName)
|
||||
return declType, declType != nil
|
||||
}
|
||||
|
||||
func newSchemaTypeProvider(kind string, declType *DeclType) (*schemaTypeProvider, error) {
|
||||
if declType == nil {
|
||||
return nil, 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
|
||||
}
|
||||
root := declType.MaybeAssignTypeName(kind)
|
||||
types := FieldTypeMap(kind, root)
|
||||
return &schemaTypeProvider{
|
||||
root: root,
|
||||
types: types,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type schemaTypeProvider struct {
|
||||
root *DeclType
|
||||
types map[string]*DeclType
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
Reference in New Issue
Block a user