k8s.io/apiserver@v0.31.1/pkg/cel/library/lists.go (about) 1 /* 2 Copyright 2022 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 library 18 19 import ( 20 "fmt" 21 22 "github.com/google/cel-go/cel" 23 "github.com/google/cel-go/common/types" 24 "github.com/google/cel-go/common/types/ref" 25 "github.com/google/cel-go/common/types/traits" 26 "github.com/google/cel-go/interpreter/functions" 27 ) 28 29 // Lists provides a CEL function library extension of list utility functions. 30 // 31 // isSorted 32 // 33 // Returns true if the provided list of comparable elements is sorted, else returns false. 34 // 35 // <list<T>>.isSorted() <bool>, T must be a comparable type 36 // 37 // Examples: 38 // 39 // [1, 2, 3].isSorted() // return true 40 // ['a', 'b', 'b', 'c'].isSorted() // return true 41 // [2.0, 1.0].isSorted() // return false 42 // [1].isSorted() // return true 43 // [].isSorted() // return true 44 // 45 // sum 46 // 47 // Returns the sum of the elements of the provided list. Supports CEL number (int, uint, double) and duration types. 48 // 49 // <list<T>>.sum() <T>, T must be a numeric type or a duration 50 // 51 // Examples: 52 // 53 // [1, 3].sum() // returns 4 54 // [1.0, 3.0].sum() // returns 4.0 55 // ['1m', '1s'].sum() // returns '1m1s' 56 // emptyIntList.sum() // returns 0 57 // emptyDoubleList.sum() // returns 0.0 58 // [].sum() // returns 0 59 // 60 // min / max 61 // 62 // Returns the minimum/maximum valued element of the provided list. Supports all comparable types. 63 // If the list is empty, an error is returned. 64 // 65 // <list<T>>.min() <T>, T must be a comparable type 66 // <list<T>>.max() <T>, T must be a comparable type 67 // 68 // Examples: 69 // 70 // [1, 3].min() // returns 1 71 // [1, 3].max() // returns 3 72 // [].min() // error 73 // [1].min() // returns 1 74 // ([0] + emptyList).min() // returns 0 75 // 76 // indexOf / lastIndexOf 77 // 78 // Returns either the first or last positional index of the provided element in the list. 79 // If the element is not found, -1 is returned. Supports all equatable types. 80 // 81 // <list<T>>.indexOf(<T>) <int>, T must be an equatable type 82 // <list<T>>.lastIndexOf(<T>) <int>, T must be an equatable type 83 // 84 // Examples: 85 // 86 // [1, 2, 2, 3].indexOf(2) // returns 1 87 // ['a', 'b', 'b', 'c'].lastIndexOf('b') // returns 2 88 // [1.0].indexOf(1.1) // returns -1 89 // [].indexOf('string') // returns -1 90 func Lists() cel.EnvOption { 91 return cel.Lib(listsLib) 92 } 93 94 var listsLib = &lists{} 95 96 type lists struct{} 97 98 func (*lists) LibraryName() string { 99 return "k8s.lists" 100 } 101 102 var paramA = cel.TypeParamType("A") 103 104 // CEL typeParams can be used to constraint to a specific trait (e.g. traits.ComparableType) if the 1st operand is the type to constrain. 105 // But the functions we need to constrain are <list<paramType>>, not just <paramType>. 106 // Make sure the order of overload set is deterministic 107 type namedCELType struct { 108 typeName string 109 celType *cel.Type 110 } 111 112 var summableTypes = []namedCELType{ 113 {typeName: "int", celType: cel.IntType}, 114 {typeName: "uint", celType: cel.UintType}, 115 {typeName: "double", celType: cel.DoubleType}, 116 {typeName: "duration", celType: cel.DurationType}, 117 } 118 119 var zeroValuesOfSummableTypes = map[string]ref.Val{ 120 "int": types.Int(0), 121 "uint": types.Uint(0), 122 "double": types.Double(0.0), 123 "duration": types.Duration{Duration: 0}, 124 } 125 var comparableTypes = []namedCELType{ 126 {typeName: "int", celType: cel.IntType}, 127 {typeName: "uint", celType: cel.UintType}, 128 {typeName: "double", celType: cel.DoubleType}, 129 {typeName: "bool", celType: cel.BoolType}, 130 {typeName: "duration", celType: cel.DurationType}, 131 {typeName: "timestamp", celType: cel.TimestampType}, 132 {typeName: "string", celType: cel.StringType}, 133 {typeName: "bytes", celType: cel.BytesType}, 134 } 135 136 // WARNING: All library additions or modifications must follow 137 // https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/2876-crd-validation-expression-language#function-library-updates 138 var listsLibraryDecls = map[string][]cel.FunctionOpt{ 139 "isSorted": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt { 140 return cel.MemberOverload(fmt.Sprintf("list_%s_is_sorted_bool", name), 141 []*cel.Type{cel.ListType(paramType)}, cel.BoolType, cel.UnaryBinding(isSorted)) 142 }), 143 "sum": templatedOverloads(summableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt { 144 return cel.MemberOverload(fmt.Sprintf("list_%s_sum_%s", name, name), 145 []*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(func(list ref.Val) ref.Val { 146 return sum( 147 func() ref.Val { 148 return zeroValuesOfSummableTypes[name] 149 })(list) 150 })) 151 }), 152 "max": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt { 153 return cel.MemberOverload(fmt.Sprintf("list_%s_max_%s", name, name), 154 []*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(max())) 155 }), 156 "min": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt { 157 return cel.MemberOverload(fmt.Sprintf("list_%s_min_%s", name, name), 158 []*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(min())) 159 }), 160 "indexOf": { 161 cel.MemberOverload("list_a_index_of_int", []*cel.Type{cel.ListType(paramA), paramA}, cel.IntType, 162 cel.BinaryBinding(indexOf)), 163 }, 164 "lastIndexOf": { 165 cel.MemberOverload("list_a_last_index_of_int", []*cel.Type{cel.ListType(paramA), paramA}, cel.IntType, 166 cel.BinaryBinding(lastIndexOf)), 167 }, 168 } 169 170 func (*lists) CompileOptions() []cel.EnvOption { 171 options := []cel.EnvOption{} 172 for name, overloads := range listsLibraryDecls { 173 options = append(options, cel.Function(name, overloads...)) 174 } 175 return options 176 } 177 178 func (*lists) ProgramOptions() []cel.ProgramOption { 179 return []cel.ProgramOption{} 180 } 181 182 func isSorted(val ref.Val) ref.Val { 183 var prev traits.Comparer 184 iterable, ok := val.(traits.Iterable) 185 if !ok { 186 return types.MaybeNoSuchOverloadErr(val) 187 } 188 for it := iterable.Iterator(); it.HasNext() == types.True; { 189 next := it.Next() 190 nextCmp, ok := next.(traits.Comparer) 191 if !ok { 192 return types.MaybeNoSuchOverloadErr(next) 193 } 194 if prev != nil { 195 cmp := prev.Compare(next) 196 if cmp == types.IntOne { 197 return types.False 198 } 199 } 200 prev = nextCmp 201 } 202 return types.True 203 } 204 205 func sum(init func() ref.Val) functions.UnaryOp { 206 return func(val ref.Val) ref.Val { 207 i := init() 208 acc, ok := i.(traits.Adder) 209 if !ok { 210 // Should never happen since all passed in init values are valid 211 return types.MaybeNoSuchOverloadErr(i) 212 } 213 iterable, ok := val.(traits.Iterable) 214 if !ok { 215 return types.MaybeNoSuchOverloadErr(val) 216 } 217 for it := iterable.Iterator(); it.HasNext() == types.True; { 218 next := it.Next() 219 nextAdder, ok := next.(traits.Adder) 220 if !ok { 221 // Should never happen for type checked CEL programs 222 return types.MaybeNoSuchOverloadErr(next) 223 } 224 if acc != nil { 225 s := acc.Add(next) 226 sum, ok := s.(traits.Adder) 227 if !ok { 228 // Should never happen for type checked CEL programs 229 return types.MaybeNoSuchOverloadErr(s) 230 } 231 acc = sum 232 } else { 233 acc = nextAdder 234 } 235 } 236 return acc.(ref.Val) 237 } 238 } 239 240 func min() functions.UnaryOp { 241 return cmp("min", types.IntOne) 242 } 243 244 func max() functions.UnaryOp { 245 return cmp("max", types.IntNegOne) 246 } 247 248 func cmp(opName string, opPreferCmpResult ref.Val) functions.UnaryOp { 249 return func(val ref.Val) ref.Val { 250 var result traits.Comparer 251 iterable, ok := val.(traits.Iterable) 252 if !ok { 253 return types.MaybeNoSuchOverloadErr(val) 254 } 255 for it := iterable.Iterator(); it.HasNext() == types.True; { 256 next := it.Next() 257 nextCmp, ok := next.(traits.Comparer) 258 if !ok { 259 // Should never happen for type checked CEL programs 260 return types.MaybeNoSuchOverloadErr(next) 261 } 262 if result == nil { 263 result = nextCmp 264 } else { 265 cmp := result.Compare(next) 266 if cmp == opPreferCmpResult { 267 result = nextCmp 268 } 269 } 270 } 271 if result == nil { 272 return types.NewErr("%s called on empty list", opName) 273 } 274 return result.(ref.Val) 275 } 276 } 277 278 func indexOf(list ref.Val, item ref.Val) ref.Val { 279 lister, ok := list.(traits.Lister) 280 if !ok { 281 return types.MaybeNoSuchOverloadErr(list) 282 } 283 sz := lister.Size().(types.Int) 284 for i := types.Int(0); i < sz; i++ { 285 if lister.Get(types.Int(i)).Equal(item) == types.True { 286 return types.Int(i) 287 } 288 } 289 return types.Int(-1) 290 } 291 292 func lastIndexOf(list ref.Val, item ref.Val) ref.Val { 293 lister, ok := list.(traits.Lister) 294 if !ok { 295 return types.MaybeNoSuchOverloadErr(list) 296 } 297 sz := lister.Size().(types.Int) 298 for i := sz - 1; i >= 0; i-- { 299 if lister.Get(types.Int(i)).Equal(item) == types.True { 300 return types.Int(i) 301 } 302 } 303 return types.Int(-1) 304 } 305 306 // templatedOverloads returns overloads for each of the provided types. The template function is called with each type 307 // name (map key) and type to construct the overloads. 308 func templatedOverloads(types []namedCELType, template func(name string, t *cel.Type) cel.FunctionOpt) []cel.FunctionOpt { 309 overloads := make([]cel.FunctionOpt, len(types)) 310 i := 0 311 for _, t := range types { 312 overloads[i] = template(t.typeName, t.celType) 313 i++ 314 } 315 return overloads 316 }