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  }