mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-30 15:05:27 +00:00
refactor type checking to use CompositedCompiler.
This commit is contained in:
parent
d2904845cd
commit
21ba0d59d3
@ -28,6 +28,7 @@ import (
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
@ -55,6 +56,8 @@ type TypeCheckingContext struct {
|
||||
declTypes []*apiservercel.DeclType
|
||||
paramGVK schema.GroupVersionKind
|
||||
paramDeclType *apiservercel.DeclType
|
||||
|
||||
variables []v1beta1.Variable
|
||||
}
|
||||
|
||||
type typeOverwrite struct {
|
||||
@ -68,7 +71,7 @@ type TypeCheckingResult struct {
|
||||
// GVK is the associated GVK
|
||||
GVK schema.GroupVersionKind
|
||||
// Issues contain machine-readable information about the typechecking result.
|
||||
Issues *cel.Issues
|
||||
Issues error
|
||||
// Err is the possible error that was encounter during type checking.
|
||||
Err error
|
||||
}
|
||||
@ -168,26 +171,70 @@ func (c *TypeChecker) CreateContext(policy *v1beta1.ValidatingAdmissionPolicy) *
|
||||
}
|
||||
ctx.paramGVK = paramsGVK
|
||||
ctx.paramDeclType = paramsDeclType
|
||||
ctx.variables = policy.Spec.Variables
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (c *TypeChecker) compiler(ctx *TypeCheckingContext, typeOverwrite typeOverwrite) (*plugincel.CompositedCompiler, error) {
|
||||
envSet, err := buildEnvSet(
|
||||
/* hasParams */ ctx.paramDeclType != nil,
|
||||
/* hasAuthorizer */ true,
|
||||
typeOverwrite)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
env, err := plugincel.NewCompositionEnv(plugincel.VariablesTypeName, envSet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
compiler := &plugincel.CompositedCompiler{
|
||||
Compiler: &typeCheckingCompiler{typeOverwrite: typeOverwrite},
|
||||
CompositionEnv: env,
|
||||
}
|
||||
return compiler, nil
|
||||
}
|
||||
|
||||
// CheckExpression type checks a single expression, given the context
|
||||
func (c *TypeChecker) CheckExpression(ctx *TypeCheckingContext, expression string) TypeCheckingResults {
|
||||
var results TypeCheckingResults
|
||||
for i, gvk := range ctx.gvks {
|
||||
declType := ctx.declTypes[i]
|
||||
// TODO(jiahuif) hasAuthorizer always true for now, will change after expending type checking to all fields.
|
||||
issues, err := c.checkExpression(expression, ctx.paramDeclType != nil, true, typeOverwrite{
|
||||
compiler, err := c.compiler(ctx, typeOverwrite{
|
||||
object: declType,
|
||||
params: ctx.paramDeclType,
|
||||
})
|
||||
if issues != nil || err != nil {
|
||||
results = append(results, &TypeCheckingResult{Issues: issues, Err: err, GVK: gvk})
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
continue
|
||||
}
|
||||
options := plugincel.OptionalVariableDeclarations{
|
||||
HasParams: ctx.paramDeclType != nil,
|
||||
HasAuthorizer: true,
|
||||
}
|
||||
compiler.CompileAndStoreVariables(convertv1beta1Variables(ctx.variables), options, environment.StoredExpressions)
|
||||
result := compiler.CompileCELExpression(celExpression(expression), options, environment.StoredExpressions)
|
||||
if err := result.Error; err != nil {
|
||||
typeCheckingResult := &TypeCheckingResult{GVK: gvk}
|
||||
if err.Type == apiservercel.ErrorTypeInvalid {
|
||||
typeCheckingResult.Issues = err
|
||||
} else {
|
||||
typeCheckingResult.Err = err
|
||||
}
|
||||
results = append(results, typeCheckingResult)
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
type celExpression string
|
||||
|
||||
func (c celExpression) GetExpression() string {
|
||||
return string(c)
|
||||
}
|
||||
|
||||
func (c celExpression) ReturnTypes() []*cel.Type {
|
||||
return []*cel.Type{cel.AnyType}
|
||||
}
|
||||
func generateUniqueTypeName(kind string) string {
|
||||
return fmt.Sprintf("%s%d", kind, time.Now().Nanosecond())
|
||||
}
|
||||
@ -214,23 +261,6 @@ func (c *TypeChecker) paramsGVK(policy *v1beta1.ValidatingAdmissionPolicy) schem
|
||||
return gv.WithKind(policy.Spec.ParamKind.Kind)
|
||||
}
|
||||
|
||||
func (c *TypeChecker) checkExpression(expression string, hasParams, hasAuthorizer bool, types typeOverwrite) (*cel.Issues, error) {
|
||||
env, err := buildEnv(hasParams, hasAuthorizer, types)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We cannot reuse an AST that is parsed by another env, so reparse it here.
|
||||
// Compile = Parse + Check, we especially want the results of Check.
|
||||
//
|
||||
// Paradoxically, we discard the type-checked result and let the admission
|
||||
// controller use the dynamic typed program.
|
||||
// This is a compromise that is defined in the KEP. We can revisit this
|
||||
// decision and expect a change with limited size.
|
||||
_, issues := env.Compile(expression)
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
// typesToCheck extracts a list of GVKs that needs type checking from the policy
|
||||
// the result is sorted in the order of Group, Version, and Kind
|
||||
func (c *TypeChecker) typesToCheck(p *v1beta1.ValidatingAdmissionPolicy) []schema.GroupVersionKind {
|
||||
@ -360,7 +390,7 @@ func (c *TypeChecker) tryRefreshRESTMapper() {
|
||||
}
|
||||
}
|
||||
|
||||
func buildEnv(hasParams bool, hasAuthorizer bool, types typeOverwrite) (*cel.Env, error) {
|
||||
func buildEnvSet(hasParams bool, hasAuthorizer bool, types typeOverwrite) (*environment.EnvSet, error) {
|
||||
baseEnv := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())
|
||||
requestType := plugincel.BuildRequestType()
|
||||
namespaceType := plugincel.BuildNamespaceType()
|
||||
@ -392,7 +422,7 @@ func buildEnv(hasParams bool, hasAuthorizer bool, types typeOverwrite) (*cel.Env
|
||||
varOpts = append(varOpts, cel.Variable("authorizer", library.AuthorizerType))
|
||||
}
|
||||
|
||||
env, err := baseEnv.Extend(
|
||||
return baseEnv.Extend(
|
||||
environment.VersionedOptions{
|
||||
// Feature epoch was actually 1.26, but we artificially set it to 1.0 because these
|
||||
// options should always be present.
|
||||
@ -401,10 +431,6 @@ func buildEnv(hasParams bool, hasAuthorizer bool, types typeOverwrite) (*cel.Env
|
||||
DeclTypes: declTypes,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return env.Env(environment.StoredExpressions)
|
||||
}
|
||||
|
||||
// createVariableOpts creates a slice of EnvOption
|
||||
@ -421,3 +447,41 @@ func createVariableOpts(declType *apiservercel.DeclType, variables ...string) []
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
type typeCheckingCompiler struct {
|
||||
typeOverwrite typeOverwrite
|
||||
}
|
||||
|
||||
// CompileCELExpression compiles the given expression.
|
||||
// The implementation is the same as that of staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/compile.go
|
||||
// except that:
|
||||
// - object, oldObject, and params are typed instead of Dyn
|
||||
// - compiler does not enforce the output type
|
||||
// - the compiler does not initialize the program
|
||||
func (c *typeCheckingCompiler) CompileCELExpression(expressionAccessor plugincel.ExpressionAccessor, options plugincel.OptionalVariableDeclarations, mode environment.Type) plugincel.CompilationResult {
|
||||
resultError := func(errorString string, errType apiservercel.ErrorType) plugincel.CompilationResult {
|
||||
return plugincel.CompilationResult{
|
||||
Error: &apiservercel.Error{
|
||||
Type: errType,
|
||||
Detail: errorString,
|
||||
},
|
||||
ExpressionAccessor: expressionAccessor,
|
||||
}
|
||||
}
|
||||
envSet, err := buildEnvSet(options.HasParams, options.HasAuthorizer, c.typeOverwrite)
|
||||
if err != nil {
|
||||
return resultError(fmt.Sprintf("fail to build env set: %v", err), apiservercel.ErrorTypeInternal)
|
||||
}
|
||||
env, err := envSet.Env(mode)
|
||||
if err != nil {
|
||||
return resultError(fmt.Sprintf("fail to build env: %v", err), apiservercel.ErrorTypeInternal)
|
||||
}
|
||||
_, issues := env.Compile(expressionAccessor.GetExpression())
|
||||
if issues != nil {
|
||||
return resultError(issues.String(), apiservercel.ErrorTypeInvalid)
|
||||
}
|
||||
// type checker does not require the program, an empty result indicates successful compilation.
|
||||
return plugincel.CompilationResult{}
|
||||
}
|
||||
|
||||
var _ plugincel.Compiler = (*typeCheckingCompiler)(nil)
|
||||
|
Loading…
Reference in New Issue
Block a user