Move CEL env initialization out of package init()

This ensures compatibility version and feature gates can be initialized
before cached CEL environments are created.
This commit is contained in:
Jordan Liggitt 2024-06-29 21:45:55 -04:00
parent 1d2ad282cf
commit 03d48b7683
No known key found for this signature in database
7 changed files with 75 additions and 32 deletions

View File

@ -21,6 +21,7 @@ import (
"reflect" "reflect"
"regexp" "regexp"
"strings" "strings"
"sync"
genericvalidation "k8s.io/apimachinery/pkg/api/validation" genericvalidation "k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/api/validation/path" "k8s.io/apimachinery/pkg/api/validation/path"
@ -1066,9 +1067,9 @@ func validateMatchConditionsExpression(expression string, opts validationOptions
} }
var compiler plugincel.Compiler var compiler plugincel.Compiler
if opts.strictCostEnforcement { if opts.strictCostEnforcement {
compiler = strictStatelessCELCompiler compiler = getStrictStatelessCELCompiler()
} else { } else {
compiler = nonStrictStatelessCELCompiler compiler = getNonStrictStatelessCELCompiler()
} }
return validateCELCondition(compiler, &matchconditions.MatchCondition{ return validateCELCondition(compiler, &matchconditions.MatchCondition{
Expression: expression, Expression: expression,
@ -1270,15 +1271,34 @@ func validateFieldRef(fieldRef string, fldPath *field.Path) field.ErrorList {
// variable composition is not allowed, for example, when validating MatchConditions. // variable composition is not allowed, for example, when validating MatchConditions.
// strictStatelessCELCompiler is a cel Compiler that enforces strict cost enforcement. // strictStatelessCELCompiler is a cel Compiler that enforces strict cost enforcement.
// nonStrictStatelessCELCompiler is a cel Compiler that does not enforce strict cost enforcement. // nonStrictStatelessCELCompiler is a cel Compiler that does not enforce strict cost enforcement.
var strictStatelessCELCompiler = plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) var (
var nonStrictStatelessCELCompiler = plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), false)) lazyStrictStatelessCELCompilerInit sync.Once
lazyStrictStatelessCELCompiler plugincel.Compiler
lazyNonStrictStatelessCELCompilerInit sync.Once
lazyNonStrictStatelessCELCompiler plugincel.Compiler
)
func getStrictStatelessCELCompiler() plugincel.Compiler {
lazyStrictStatelessCELCompilerInit.Do(func() {
lazyStrictStatelessCELCompiler = plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))
})
return lazyStrictStatelessCELCompiler
}
func getNonStrictStatelessCELCompiler() plugincel.Compiler {
lazyNonStrictStatelessCELCompilerInit.Do(func() {
lazyNonStrictStatelessCELCompiler = plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), false))
})
return lazyNonStrictStatelessCELCompiler
}
func createCompiler(allowComposition, strictCost bool) plugincel.Compiler { func createCompiler(allowComposition, strictCost bool) plugincel.Compiler {
if !allowComposition { if !allowComposition {
if strictCost { if strictCost {
return strictStatelessCELCompiler return getStrictStatelessCELCompiler()
} else { } else {
return nonStrictStatelessCELCompiler return getNonStrictStatelessCELCompiler()
} }
} }
compiler, err := plugincel.NewCompositedCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), strictCost)) compiler, err := plugincel.NewCompositedCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), strictCost))

View File

@ -3425,14 +3425,16 @@ func TestValidateValidatingAdmissionPolicyUpdate(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if strictCost { if strictCost {
strictStatelessCELCompiler = plugincel.NewCompiler(extended) originalCompiler := getStrictStatelessCELCompiler()
lazyStrictStatelessCELCompiler = plugincel.NewCompiler(extended)
defer func() { defer func() {
strictStatelessCELCompiler = plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), strictCost)) lazyStrictStatelessCELCompiler = originalCompiler
}() }()
} else { } else {
nonStrictStatelessCELCompiler = plugincel.NewCompiler(extended) originalCompiler := getNonStrictStatelessCELCompiler()
lazyNonStrictStatelessCELCompiler = plugincel.NewCompiler(extended)
defer func() { defer func() {
nonStrictStatelessCELCompiler = plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), strictCost)) lazyNonStrictStatelessCELCompiler = originalCompiler
}() }()
} }

View File

@ -153,7 +153,7 @@ func validateSelector(opts Options, selector string, fldPath *field.Path) field.
if opts.StoredExpressions { if opts.StoredExpressions {
envType = environment.StoredExpressions envType = environment.StoredExpressions
} }
result := namedresourcescel.Compiler.CompileCELExpression(selector, envType) result := namedresourcescel.GetCompiler().CompileCELExpression(selector, envType)
if result.Error != nil { if result.Error != nil {
allErrs = append(allErrs, convertCELErrorToValidationError(fldPath, selector, result.Error)) allErrs = append(allErrs, convertCELErrorToValidationError(fldPath, selector, result.Error))
} }

View File

@ -68,7 +68,7 @@ func AddAllocation(m *Model, result *resourceapi.NamedResourcesAllocationResult)
func NewClaimController(filter *resourceapi.NamedResourcesFilter, requests []*resourceapi.NamedResourcesRequest) (*Controller, error) { func NewClaimController(filter *resourceapi.NamedResourcesFilter, requests []*resourceapi.NamedResourcesRequest) (*Controller, error) {
c := &Controller{} c := &Controller{}
if filter != nil { if filter != nil {
compilation := cel.Compiler.CompileCELExpression(filter.Selector, environment.StoredExpressions) compilation := cel.GetCompiler().CompileCELExpression(filter.Selector, environment.StoredExpressions)
if compilation.Error != nil { if compilation.Error != nil {
// Shouldn't happen because of validation. // Shouldn't happen because of validation.
return nil, fmt.Errorf("compile class filter CEL expression: %w", compilation.Error) return nil, fmt.Errorf("compile class filter CEL expression: %w", compilation.Error)
@ -76,7 +76,7 @@ func NewClaimController(filter *resourceapi.NamedResourcesFilter, requests []*re
c.filter = &compilation c.filter = &compilation
} }
for _, request := range requests { for _, request := range requests {
compilation := cel.Compiler.CompileCELExpression(request.Selector, environment.StoredExpressions) compilation := cel.GetCompiler().CompileCELExpression(request.Selector, environment.StoredExpressions)
if compilation.Error != nil { if compilation.Error != nil {
// Shouldn't happen because of validation. // Shouldn't happen because of validation.
return nil, fmt.Errorf("compile request CEL expression: %w", compilation.Error) return nil, fmt.Errorf("compile request CEL expression: %w", compilation.Error)

View File

@ -19,6 +19,7 @@ package validating
import ( import (
"context" "context"
"io" "io"
"sync"
v1 "k8s.io/api/admissionregistration/v1" v1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
@ -44,24 +45,35 @@ const (
) )
var ( var (
compositionEnvTemplateWithStrictCost *cel.CompositionEnv = func() *cel.CompositionEnv { lazyCompositionEnvTemplateWithStrictCostInit sync.Once
compositionEnvTemplateWithStrictCost, err := cel.NewCompositionEnv(cel.VariablesTypeName, environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) lazyCompositionEnvTemplateWithStrictCost *cel.CompositionEnv
if err != nil {
panic(err)
}
return compositionEnvTemplateWithStrictCost lazyCompositionEnvTemplateWithoutStrictCostInit sync.Once
}() lazyCompositionEnvTemplateWithoutStrictCost *cel.CompositionEnv
compositionEnvTemplateWithoutStrictCost *cel.CompositionEnv = func() *cel.CompositionEnv {
compositionEnvTemplateWithoutStrictCost, err := cel.NewCompositionEnv(cel.VariablesTypeName, environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), false))
if err != nil {
panic(err)
}
return compositionEnvTemplateWithoutStrictCost
}()
) )
func getCompositionEnvTemplateWithStrictCost() *cel.CompositionEnv {
lazyCompositionEnvTemplateWithStrictCostInit.Do(func() {
env, err := cel.NewCompositionEnv(cel.VariablesTypeName, environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))
if err != nil {
panic(err)
}
lazyCompositionEnvTemplateWithStrictCost = env
})
return lazyCompositionEnvTemplateWithStrictCost
}
func getCompositionEnvTemplateWithoutStrictCost() *cel.CompositionEnv {
lazyCompositionEnvTemplateWithoutStrictCostInit.Do(func() {
env, err := cel.NewCompositionEnv(cel.VariablesTypeName, environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), false))
if err != nil {
panic(err)
}
lazyCompositionEnvTemplateWithoutStrictCost = env
})
return lazyCompositionEnvTemplateWithoutStrictCost
}
// Register registers a plugin // Register registers a plugin
func Register(plugins *admission.Plugins) { func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(configFile io.Reader) (admission.Interface, error) { plugins.Register(PluginName, func(configFile io.Reader) (admission.Interface, error) {
@ -131,9 +143,9 @@ func compilePolicy(policy *Policy) Validator {
matchConditions := policy.Spec.MatchConditions matchConditions := policy.Spec.MatchConditions
var compositionEnvTemplate *cel.CompositionEnv var compositionEnvTemplate *cel.CompositionEnv
if strictCost { if strictCost {
compositionEnvTemplate = compositionEnvTemplateWithStrictCost compositionEnvTemplate = getCompositionEnvTemplateWithStrictCost()
} else { } else {
compositionEnvTemplate = compositionEnvTemplateWithoutStrictCost compositionEnvTemplate = getCompositionEnvTemplateWithoutStrictCost()
} }
filterCompiler := cel.NewCompositedCompilerFromTemplate(compositionEnvTemplate) filterCompiler := cel.NewCompositedCompilerFromTemplate(compositionEnvTemplate)
filterCompiler.CompileAndStoreVariables(convertv1beta1Variables(policy.Spec.Variables), optionalVars, environment.StoredExpressions) filterCompiler.CompileAndStoreVariables(convertv1beta1Variables(policy.Spec.Variables), optionalVars, environment.StoredExpressions)

View File

@ -20,6 +20,7 @@ import (
"context" "context"
"fmt" "fmt"
"reflect" "reflect"
"sync"
"github.com/blang/semver/v4" "github.com/blang/semver/v4"
"github.com/google/cel-go/cel" "github.com/google/cel-go/cel"
@ -38,9 +39,17 @@ const (
) )
var ( var (
Compiler = newCompiler() lazyCompilerInit sync.Once
lazyCompiler *compiler
) )
func GetCompiler() *compiler {
lazyCompilerInit.Do(func() {
lazyCompiler = newCompiler()
})
return lazyCompiler
}
// CompilationResult represents a compiled expression. // CompilationResult represents a compiled expression.
type CompilationResult struct { type CompilationResult struct {
Program cel.Program Program cel.Program

View File

@ -124,7 +124,7 @@ attributes.stringslice["stringslice"].isSorted()`,
} { } {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
_, ctx := ktesting.NewTestContext(t) _, ctx := ktesting.NewTestContext(t)
result := Compiler.CompileCELExpression(scenario.expression, environment.StoredExpressions) result := GetCompiler().CompileCELExpression(scenario.expression, environment.StoredExpressions)
if scenario.expectCompileError != "" && result.Error == nil { if scenario.expectCompileError != "" && result.Error == nil {
t.Fatalf("expected compile error %q, got none", scenario.expectCompileError) t.Fatalf("expected compile error %q, got none", scenario.expectCompileError)
} }