k8s.io/apiserver@v0.31.1/pkg/admission/plugin/cel/composition.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 cel 18 19 import ( 20 "context" 21 "math" 22 23 "github.com/google/cel-go/cel" 24 "github.com/google/cel-go/common/types" 25 "github.com/google/cel-go/common/types/ref" 26 "github.com/google/cel-go/common/types/traits" 27 28 v1 "k8s.io/api/admission/v1" 29 corev1 "k8s.io/api/core/v1" 30 "k8s.io/apimachinery/pkg/util/version" 31 "k8s.io/apiserver/pkg/admission" 32 apiservercel "k8s.io/apiserver/pkg/cel" 33 "k8s.io/apiserver/pkg/cel/environment" 34 "k8s.io/apiserver/pkg/cel/lazy" 35 ) 36 37 const VariablesTypeName = "kubernetes.variables" 38 39 type CompositedCompiler struct { 40 Compiler 41 FilterCompiler 42 43 CompositionEnv *CompositionEnv 44 } 45 46 type CompositedFilter struct { 47 Filter 48 49 compositionEnv *CompositionEnv 50 } 51 52 func NewCompositedCompiler(envSet *environment.EnvSet) (*CompositedCompiler, error) { 53 compositionContext, err := NewCompositionEnv(VariablesTypeName, envSet) 54 if err != nil { 55 return nil, err 56 } 57 return NewCompositedCompilerFromTemplate(compositionContext), nil 58 } 59 60 func NewCompositedCompilerFromTemplate(context *CompositionEnv) *CompositedCompiler { 61 context = &CompositionEnv{ 62 MapType: context.MapType, 63 EnvSet: context.EnvSet, 64 CompiledVariables: map[string]CompilationResult{}, 65 } 66 compiler := NewCompiler(context.EnvSet) 67 filterCompiler := NewFilterCompiler(context.EnvSet) 68 return &CompositedCompiler{ 69 Compiler: compiler, 70 FilterCompiler: filterCompiler, 71 CompositionEnv: context, 72 } 73 } 74 75 func (c *CompositedCompiler) CompileAndStoreVariables(variables []NamedExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) { 76 for _, v := range variables { 77 _ = c.CompileAndStoreVariable(v, options, mode) 78 } 79 } 80 81 func (c *CompositedCompiler) CompileAndStoreVariable(variable NamedExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) CompilationResult { 82 result := c.Compiler.CompileCELExpression(variable, options, mode) 83 c.CompositionEnv.AddField(variable.GetName(), result.OutputType) 84 c.CompositionEnv.CompiledVariables[variable.GetName()] = result 85 return result 86 } 87 88 func (c *CompositedCompiler) Compile(expressions []ExpressionAccessor, optionalDecls OptionalVariableDeclarations, envType environment.Type) Filter { 89 filter := c.FilterCompiler.Compile(expressions, optionalDecls, envType) 90 return &CompositedFilter{ 91 Filter: filter, 92 compositionEnv: c.CompositionEnv, 93 } 94 } 95 96 type CompositionEnv struct { 97 *environment.EnvSet 98 99 MapType *apiservercel.DeclType 100 CompiledVariables map[string]CompilationResult 101 } 102 103 func (c *CompositionEnv) AddField(name string, celType *cel.Type) { 104 c.MapType.Fields[name] = apiservercel.NewDeclField(name, convertCelTypeToDeclType(celType), true, nil, nil) 105 } 106 107 func NewCompositionEnv(typeName string, baseEnvSet *environment.EnvSet) (*CompositionEnv, error) { 108 declType := apiservercel.NewObjectType(typeName, map[string]*apiservercel.DeclField{}) 109 envSet, err := baseEnvSet.Extend(environment.VersionedOptions{ 110 // set to 1.0 because composition is one of the fundamental components 111 IntroducedVersion: version.MajorMinor(1, 0), 112 EnvOptions: []cel.EnvOption{ 113 cel.Variable("variables", declType.CelType()), 114 }, 115 DeclTypes: []*apiservercel.DeclType{ 116 declType, 117 }, 118 }) 119 if err != nil { 120 return nil, err 121 } 122 return &CompositionEnv{ 123 MapType: declType, 124 EnvSet: envSet, 125 CompiledVariables: map[string]CompilationResult{}, 126 }, nil 127 } 128 129 func (c *CompositionEnv) CreateContext(parent context.Context) CompositionContext { 130 return &compositionContext{ 131 Context: parent, 132 compositionEnv: c, 133 } 134 } 135 136 type CompositionContext interface { 137 context.Context 138 Variables(activation any) ref.Val 139 GetAndResetCost() int64 140 } 141 142 type compositionContext struct { 143 context.Context 144 145 compositionEnv *CompositionEnv 146 accumulatedCost int64 147 } 148 149 func (c *compositionContext) Variables(activation any) ref.Val { 150 lazyMap := lazy.NewMapValue(c.compositionEnv.MapType) 151 for name, result := range c.compositionEnv.CompiledVariables { 152 accessor := &variableAccessor{ 153 name: name, 154 result: result, 155 activation: activation, 156 context: c, 157 } 158 lazyMap.Append(name, accessor.Callback) 159 } 160 return lazyMap 161 } 162 163 func (f *CompositedFilter) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, namespace *corev1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) { 164 ctx = f.compositionEnv.CreateContext(ctx) 165 return f.Filter.ForInput(ctx, versionedAttr, request, optionalVars, namespace, runtimeCELCostBudget) 166 } 167 168 func (c *compositionContext) reportCost(cost int64) { 169 c.accumulatedCost += cost 170 } 171 172 func (c *compositionContext) GetAndResetCost() int64 { 173 cost := c.accumulatedCost 174 c.accumulatedCost = 0 175 return cost 176 } 177 178 type variableAccessor struct { 179 name string 180 result CompilationResult 181 activation any 182 context *compositionContext 183 } 184 185 func (a *variableAccessor) Callback(_ *lazy.MapValue) ref.Val { 186 if a.result.Error != nil { 187 return types.NewErr("composited variable %q fails to compile: %v", a.name, a.result.Error) 188 } 189 190 v, details, err := a.result.Program.ContextEval(a.context, a.activation) 191 if details == nil { 192 return types.NewErr("unable to get evaluation details of variable %q", a.name) 193 } 194 costPtr := details.ActualCost() 195 if costPtr == nil { 196 return types.NewErr("unable to calculate cost of variable %q", a.name) 197 } 198 cost := int64(*costPtr) 199 if *costPtr > math.MaxInt64 { 200 cost = math.MaxInt64 201 } 202 a.context.reportCost(cost) 203 204 if err != nil { 205 return types.NewErr("composited variable %q fails to evaluate: %v", a.name, err) 206 } 207 return v 208 } 209 210 // convertCelTypeToDeclType converts a cel.Type to DeclType, for the use of 211 // the TypeProvider and the cost estimator. 212 // List and map types are created on-demand with their parameters converted recursively. 213 func convertCelTypeToDeclType(celType *cel.Type) *apiservercel.DeclType { 214 if celType == nil { 215 return apiservercel.DynType 216 } 217 switch celType { 218 case cel.AnyType: 219 return apiservercel.AnyType 220 case cel.BoolType: 221 return apiservercel.BoolType 222 case cel.BytesType: 223 return apiservercel.BytesType 224 case cel.DoubleType: 225 return apiservercel.DoubleType 226 case cel.DurationType: 227 return apiservercel.DurationType 228 case cel.IntType: 229 return apiservercel.IntType 230 case cel.NullType: 231 return apiservercel.NullType 232 case cel.StringType: 233 return apiservercel.StringType 234 case cel.TimestampType: 235 return apiservercel.TimestampType 236 case cel.UintType: 237 return apiservercel.UintType 238 default: 239 if celType.HasTrait(traits.ContainerType) && celType.HasTrait(traits.IndexerType) { 240 parameters := celType.Parameters() 241 switch len(parameters) { 242 case 1: 243 elemType := convertCelTypeToDeclType(parameters[0]) 244 return apiservercel.NewListType(elemType, -1) 245 case 2: 246 keyType := convertCelTypeToDeclType(parameters[0]) 247 valueType := convertCelTypeToDeclType(parameters[1]) 248 return apiservercel.NewMapType(keyType, valueType, -1) 249 } 250 } 251 return apiservercel.DynType 252 } 253 }