k8s.io/apiserver@v0.31.1/pkg/cel/common/equality.go (about)

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package common
    18  
    19  import (
    20  	"reflect"
    21  	"time"
    22  )
    23  
    24  // CorrelatedObject represents a node in a tree of objects that are being
    25  // validated. It is used to keep track of the old value of an object during
    26  // traversal of the new value. It is also used to cache the results of
    27  // DeepEqual comparisons between the old and new values of objects.
    28  //
    29  // All receiver functions support being called on `nil` to support ergonomic
    30  // recursive descent. The nil `CorrelatedObject` represents an uncorrelatable
    31  // node in the tree.
    32  //
    33  // CorrelatedObject is not thread-safe. It is the responsibility of the caller
    34  // to handle concurrency, if any.
    35  type CorrelatedObject struct {
    36  	// Currently correlated old value during traversal of the schema/object
    37  	OldValue interface{}
    38  
    39  	// Value being validated
    40  	Value interface{}
    41  
    42  	// Schema used for validation of this value. The schema is also used
    43  	// to determine how to correlate the old object.
    44  	Schema Schema
    45  
    46  	// Duration spent on ratcheting validation for this object and all of its
    47  	// children.
    48  	Duration *time.Duration
    49  
    50  	// Scratch space below, may change during validation
    51  
    52  	// Cached comparison result of DeepEqual of `value` and `thunk.oldValue`
    53  	comparisonResult *bool
    54  
    55  	// Cached map representation of a map-type list, or nil if not map-type list
    56  	mapList MapList
    57  
    58  	// Children spawned by a call to `Validate` on this object
    59  	// key is either a string or an index, depending upon whether `value` is
    60  	// a map or a list, respectively.
    61  	//
    62  	// The list of children may be incomplete depending upon if the internal
    63  	// logic of kube-openapi's SchemaValidator short-circuited before
    64  	// reaching all of the children.
    65  	//
    66  	// It should be expected to have an entry for either all of the children, or
    67  	// none of them.
    68  	children map[interface{}]*CorrelatedObject
    69  }
    70  
    71  func NewCorrelatedObject(new, old interface{}, schema Schema) *CorrelatedObject {
    72  	d := time.Duration(0)
    73  	return &CorrelatedObject{
    74  		OldValue: old,
    75  		Value:    new,
    76  		Schema:   schema,
    77  		Duration: &d,
    78  	}
    79  }
    80  
    81  // If OldValue or Value is not a list, or the index is out of bounds of the
    82  // Value list, returns nil
    83  // If oldValue is a list, this considers the x-list-type to decide how to
    84  // correlate old values:
    85  //
    86  // If listType is map, creates a map representation of the list using the designated
    87  // map-keys, caches it for future calls, and returns the map value, or nil if
    88  // the correlated key is not in the old map
    89  //
    90  // Otherwise, if the list type is not correlatable this funcion returns nil.
    91  func (r *CorrelatedObject) correlateOldValueForChildAtNewIndex(index int) interface{} {
    92  	oldAsList, ok := r.OldValue.([]interface{})
    93  	if !ok {
    94  		return nil
    95  	}
    96  
    97  	asList, ok := r.Value.([]interface{})
    98  	if !ok {
    99  		return nil
   100  	} else if len(asList) <= index {
   101  		// Cannot correlate out of bounds index
   102  		return nil
   103  	}
   104  
   105  	listType := r.Schema.XListType()
   106  	switch listType {
   107  	case "map":
   108  		// Look up keys for this index in current object
   109  		currentElement := asList[index]
   110  
   111  		oldList := r.mapList
   112  		if oldList == nil {
   113  			oldList = MakeMapList(r.Schema, oldAsList)
   114  			r.mapList = oldList
   115  		}
   116  		return oldList.Get(currentElement)
   117  
   118  	case "set":
   119  		// Are sets correlatable? Only if the old value equals the current value.
   120  		// We might be able to support this, but do not currently see a lot
   121  		// of value
   122  		// (would allow you to add/remove items from sets with ratcheting but not change them)
   123  		return nil
   124  	case "":
   125  		fallthrough
   126  	case "atomic":
   127  		// Atomic lists are the default are not correlatable by item
   128  		// Ratcheting is not available on a per-index basis
   129  		return nil
   130  	default:
   131  		// Unrecognized list type. Assume non-correlatable.
   132  		return nil
   133  	}
   134  }
   135  
   136  // CachedDeepEqual is equivalent to reflect.DeepEqual, but caches the
   137  // results in the tree of ratchetInvocationScratch objects on the way:
   138  //
   139  // For objects and arrays, this function will make a best effort to make
   140  // use of past DeepEqual checks performed by this Node's children, if available.
   141  //
   142  // If a lazy computation could not be found for all children possibly due
   143  // to validation logic short circuiting and skipping the children, then
   144  // this function simply defers to reflect.DeepEqual.
   145  func (r *CorrelatedObject) CachedDeepEqual() (res bool) {
   146  	start := time.Now()
   147  	defer func() {
   148  		if r != nil && r.Duration != nil {
   149  			*r.Duration += time.Since(start)
   150  		}
   151  	}()
   152  
   153  	if r == nil {
   154  		// Uncorrelatable node is not considered equal to its old value
   155  		return false
   156  	} else if r.comparisonResult != nil {
   157  		return *r.comparisonResult
   158  	}
   159  
   160  	defer func() {
   161  		r.comparisonResult = &res
   162  	}()
   163  
   164  	if r.Value == nil && r.OldValue == nil {
   165  		return true
   166  	} else if r.Value == nil || r.OldValue == nil {
   167  		return false
   168  	}
   169  
   170  	oldAsArray, oldIsArray := r.OldValue.([]interface{})
   171  	newAsArray, newIsArray := r.Value.([]interface{})
   172  
   173  	oldAsMap, oldIsMap := r.OldValue.(map[string]interface{})
   174  	newAsMap, newIsMap := r.Value.(map[string]interface{})
   175  
   176  	// If old and new are not the same type, they are not equal
   177  	if (oldIsArray != newIsArray) || oldIsMap != newIsMap {
   178  		return false
   179  	}
   180  
   181  	// Objects are known to be same type of (map, slice, or primitive)
   182  	switch {
   183  	case oldIsArray:
   184  		// Both arrays case. oldIsArray == newIsArray
   185  		if len(oldAsArray) != len(newAsArray) {
   186  			return false
   187  		}
   188  
   189  		for i := range newAsArray {
   190  			child := r.Index(i)
   191  			if child == nil {
   192  				if r.mapList == nil {
   193  					// Treat non-correlatable array as a unit with reflect.DeepEqual
   194  					return reflect.DeepEqual(oldAsArray, newAsArray)
   195  				}
   196  
   197  				// If array is correlatable, but old not found. Just short circuit
   198  				// comparison
   199  				return false
   200  
   201  			} else if !child.CachedDeepEqual() {
   202  				// If one child is not equal the entire object is not equal
   203  				return false
   204  			}
   205  		}
   206  
   207  		return true
   208  	case oldIsMap:
   209  		// Both maps case. oldIsMap == newIsMap
   210  		if len(oldAsMap) != len(newAsMap) {
   211  			return false
   212  		}
   213  
   214  		for k := range newAsMap {
   215  			child := r.Key(k)
   216  			if child == nil {
   217  				// Un-correlatable child due to key change.
   218  				// Objects are not equal.
   219  				return false
   220  			} else if !child.CachedDeepEqual() {
   221  				// If one child is not equal the entire object is not equal
   222  				return false
   223  			}
   224  		}
   225  
   226  		return true
   227  
   228  	default:
   229  		// Primitive: use reflect.DeepEqual
   230  		return reflect.DeepEqual(r.OldValue, r.Value)
   231  	}
   232  }
   233  
   234  // Key returns the child of the receiver with the given name.
   235  // Returns nil if the given name is does not exist in the new object, or its
   236  // value is not correlatable to an old value.
   237  // If receiver is nil or if the new value is not an object/map, returns nil.
   238  func (r *CorrelatedObject) Key(field string) *CorrelatedObject {
   239  	start := time.Now()
   240  	defer func() {
   241  		if r != nil && r.Duration != nil {
   242  			*r.Duration += time.Since(start)
   243  		}
   244  	}()
   245  
   246  	if r == nil || r.Schema == nil {
   247  		return nil
   248  	} else if existing, exists := r.children[field]; exists {
   249  		return existing
   250  	}
   251  
   252  	// Find correlated old value
   253  	oldAsMap, okOld := r.OldValue.(map[string]interface{})
   254  	newAsMap, okNew := r.Value.(map[string]interface{})
   255  	if !okOld || !okNew {
   256  		return nil
   257  	}
   258  
   259  	oldValueForField, okOld := oldAsMap[field]
   260  	newValueForField, okNew := newAsMap[field]
   261  	if !okOld || !okNew {
   262  		return nil
   263  	}
   264  
   265  	var propertySchema Schema
   266  	if prop, exists := r.Schema.Properties()[field]; exists {
   267  		propertySchema = prop
   268  	} else if addP := r.Schema.AdditionalProperties(); addP != nil && addP.Schema() != nil {
   269  		propertySchema = addP.Schema()
   270  	} else {
   271  		return nil
   272  	}
   273  
   274  	if r.children == nil {
   275  		r.children = make(map[interface{}]*CorrelatedObject, len(newAsMap))
   276  	}
   277  
   278  	res := &CorrelatedObject{
   279  		OldValue: oldValueForField,
   280  		Value:    newValueForField,
   281  		Schema:   propertySchema,
   282  		Duration: r.Duration,
   283  	}
   284  	r.children[field] = res
   285  	return res
   286  }
   287  
   288  // Index returns the child of the receiver at the given index.
   289  // Returns nil if the given index is out of bounds, or its value is not
   290  // correlatable to an old value.
   291  // If receiver is nil or if the new value is not an array, returns nil.
   292  func (r *CorrelatedObject) Index(i int) *CorrelatedObject {
   293  	start := time.Now()
   294  	defer func() {
   295  		if r != nil && r.Duration != nil {
   296  			*r.Duration += time.Since(start)
   297  		}
   298  	}()
   299  
   300  	if r == nil || r.Schema == nil {
   301  		return nil
   302  	} else if existing, exists := r.children[i]; exists {
   303  		return existing
   304  	}
   305  
   306  	asList, ok := r.Value.([]interface{})
   307  	if !ok || len(asList) <= i {
   308  		return nil
   309  	}
   310  
   311  	oldValueForIndex := r.correlateOldValueForChildAtNewIndex(i)
   312  	if oldValueForIndex == nil {
   313  		return nil
   314  	}
   315  	var itemSchema Schema
   316  	if i := r.Schema.Items(); i != nil {
   317  		itemSchema = i
   318  	} else {
   319  		return nil
   320  	}
   321  
   322  	if r.children == nil {
   323  		r.children = make(map[interface{}]*CorrelatedObject, len(asList))
   324  	}
   325  
   326  	res := &CorrelatedObject{
   327  		OldValue: oldValueForIndex,
   328  		Value:    asList[i],
   329  		Schema:   itemSchema,
   330  		Duration: r.Duration,
   331  	}
   332  	r.children[i] = res
   333  	return res
   334  }