mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
build: move e2e dependencies into e2e/go.mod
Several packages are only used while running the e2e suite. These packages are less important to update, as the they can not influence the final executable that is part of the Ceph-CSI container-image. By moving these dependencies out of the main Ceph-CSI go.mod, it is easier to identify if a reported CVE affects Ceph-CSI, or only the testing (like most of the Kubernetes CVEs). Signed-off-by: Niels de Vos <ndevos@ibm.com>
This commit is contained in:
committed by
mergify[bot]
parent
15da101b1b
commit
bec6090996
106
e2e/vendor/k8s.io/apiserver/pkg/cel/common/adaptor.go
generated
vendored
Normal file
106
e2e/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
e2e/vendor/k8s.io/apiserver/pkg/cel/common/equality.go
generated
vendored
Normal file
334
e2e/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
e2e/vendor/k8s.io/apiserver/pkg/cel/common/maplist.go
generated
vendored
Normal file
177
e2e/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
e2e/vendor/k8s.io/apiserver/pkg/cel/common/schemas.go
generated
vendored
Normal file
274
e2e/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
|
||||
}
|
127
e2e/vendor/k8s.io/apiserver/pkg/cel/common/typeprovider.go
generated
vendored
Normal file
127
e2e/vendor/k8s.io/apiserver/pkg/cel/common/typeprovider.go
generated
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
)
|
||||
|
||||
// TypeResolver resolves a type by a given name.
|
||||
type TypeResolver interface {
|
||||
// Resolve resolves the type by its name.
|
||||
// This function returns false if the name does not refer to a known object type.
|
||||
Resolve(name string) (ResolvedType, bool)
|
||||
}
|
||||
|
||||
// ResolvedType refers an object type that can be looked up for its fields.
|
||||
type ResolvedType interface {
|
||||
ref.Type
|
||||
|
||||
Type() *types.Type
|
||||
|
||||
// Field finds the field by the field name, or false if the field is not known.
|
||||
// This function directly return a FieldType that is known to CEL to be more customizable.
|
||||
Field(name string) (*types.FieldType, bool)
|
||||
|
||||
// FieldNames returns the field names associated with the type, if the type
|
||||
// is found.
|
||||
FieldNames() ([]string, bool)
|
||||
|
||||
// Val creates an instance for the ResolvedType, given its fields and their values.
|
||||
Val(fields map[string]ref.Val) ref.Val
|
||||
}
|
||||
|
||||
// ResolverTypeProvider delegates type resolution first to the TypeResolver and then
|
||||
// to the underlying types.Provider for types not resolved by the TypeResolver.
|
||||
type ResolverTypeProvider struct {
|
||||
typeResolver TypeResolver
|
||||
underlyingTypeProvider types.Provider
|
||||
}
|
||||
|
||||
var _ types.Provider = (*ResolverTypeProvider)(nil)
|
||||
|
||||
// FindStructType returns the Type give a qualified type name, by looking it up with
|
||||
// the DynamicTypeResolver and translating it to CEL Type.
|
||||
// If the type is not known to the DynamicTypeResolver, the lookup falls back to the underlying
|
||||
// ResolverTypeProvider instead.
|
||||
func (p *ResolverTypeProvider) FindStructType(structType string) (*types.Type, bool) {
|
||||
t, ok := p.typeResolver.Resolve(structType)
|
||||
if ok {
|
||||
return types.NewTypeTypeWithParam(t.Type()), true
|
||||
}
|
||||
return p.underlyingTypeProvider.FindStructType(structType)
|
||||
}
|
||||
|
||||
// FindStructFieldNames returns the field names associated with the type, if the type
|
||||
// is found.
|
||||
func (p *ResolverTypeProvider) FindStructFieldNames(structType string) ([]string, bool) {
|
||||
t, ok := p.typeResolver.Resolve(structType)
|
||||
if ok {
|
||||
return t.FieldNames()
|
||||
}
|
||||
return p.underlyingTypeProvider.FindStructFieldNames(structType)
|
||||
}
|
||||
|
||||
// FindStructFieldType returns the field type for a checked type value.
|
||||
// Returns false if the field could not be found.
|
||||
func (p *ResolverTypeProvider) FindStructFieldType(structType, fieldName string) (*types.FieldType, bool) {
|
||||
t, ok := p.typeResolver.Resolve(structType)
|
||||
if ok {
|
||||
return t.Field(fieldName)
|
||||
}
|
||||
return p.underlyingTypeProvider.FindStructFieldType(structType, fieldName)
|
||||
}
|
||||
|
||||
// NewValue creates a new type value from a qualified name and map of fields.
|
||||
func (p *ResolverTypeProvider) NewValue(structType string, fields map[string]ref.Val) ref.Val {
|
||||
t, ok := p.typeResolver.Resolve(structType)
|
||||
if ok {
|
||||
return t.Val(fields)
|
||||
}
|
||||
return p.underlyingTypeProvider.NewValue(structType, fields)
|
||||
}
|
||||
|
||||
func (p *ResolverTypeProvider) EnumValue(enumName string) ref.Val {
|
||||
return p.underlyingTypeProvider.EnumValue(enumName)
|
||||
}
|
||||
|
||||
func (p *ResolverTypeProvider) FindIdent(identName string) (ref.Val, bool) {
|
||||
return p.underlyingTypeProvider.FindIdent(identName)
|
||||
}
|
||||
|
||||
// ResolverEnvOption creates the ResolverTypeProvider with a given DynamicTypeResolver,
|
||||
// and also returns the CEL ResolverEnvOption to apply it to the env.
|
||||
func ResolverEnvOption(resolver TypeResolver) cel.EnvOption {
|
||||
_, envOpt := NewResolverTypeProviderAndEnvOption(resolver)
|
||||
return envOpt
|
||||
}
|
||||
|
||||
// NewResolverTypeProviderAndEnvOption creates the ResolverTypeProvider with a given DynamicTypeResolver,
|
||||
// and also returns the CEL ResolverEnvOption to apply it to the env.
|
||||
func NewResolverTypeProviderAndEnvOption(resolver TypeResolver) (*ResolverTypeProvider, cel.EnvOption) {
|
||||
tp := &ResolverTypeProvider{typeResolver: resolver}
|
||||
var envOption cel.EnvOption = func(e *cel.Env) (*cel.Env, error) {
|
||||
// wrap the existing type provider (acquired from the env)
|
||||
// and set new type provider for the env.
|
||||
tp.underlyingTypeProvider = e.CELTypeProvider()
|
||||
typeProviderOption := cel.CustomTypeProvider(tp)
|
||||
return typeProviderOption(e)
|
||||
}
|
||||
return tp, envOption
|
||||
}
|
721
e2e/vendor/k8s.io/apiserver/pkg/cel/common/values.go
generated
vendored
Normal file
721
e2e/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
|
||||
}
|
Reference in New Issue
Block a user