mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 15:25:57 +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/api/admissionregistration/v1beta1"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
"k8s.io/apimachinery/pkg/util/version"
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
@ -55,6 +56,8 @@ type TypeCheckingContext struct {
|
|||||||
declTypes []*apiservercel.DeclType
|
declTypes []*apiservercel.DeclType
|
||||||
paramGVK schema.GroupVersionKind
|
paramGVK schema.GroupVersionKind
|
||||||
paramDeclType *apiservercel.DeclType
|
paramDeclType *apiservercel.DeclType
|
||||||
|
|
||||||
|
variables []v1beta1.Variable
|
||||||
}
|
}
|
||||||
|
|
||||||
type typeOverwrite struct {
|
type typeOverwrite struct {
|
||||||
@ -68,7 +71,7 @@ type TypeCheckingResult struct {
|
|||||||
// GVK is the associated GVK
|
// GVK is the associated GVK
|
||||||
GVK schema.GroupVersionKind
|
GVK schema.GroupVersionKind
|
||||||
// Issues contain machine-readable information about the typechecking result.
|
// 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 is the possible error that was encounter during type checking.
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
@ -168,26 +171,70 @@ func (c *TypeChecker) CreateContext(policy *v1beta1.ValidatingAdmissionPolicy) *
|
|||||||
}
|
}
|
||||||
ctx.paramGVK = paramsGVK
|
ctx.paramGVK = paramsGVK
|
||||||
ctx.paramDeclType = paramsDeclType
|
ctx.paramDeclType = paramsDeclType
|
||||||
|
ctx.variables = policy.Spec.Variables
|
||||||
return ctx
|
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
|
// CheckExpression type checks a single expression, given the context
|
||||||
func (c *TypeChecker) CheckExpression(ctx *TypeCheckingContext, expression string) TypeCheckingResults {
|
func (c *TypeChecker) CheckExpression(ctx *TypeCheckingContext, expression string) TypeCheckingResults {
|
||||||
var results TypeCheckingResults
|
var results TypeCheckingResults
|
||||||
for i, gvk := range ctx.gvks {
|
for i, gvk := range ctx.gvks {
|
||||||
declType := ctx.declTypes[i]
|
declType := ctx.declTypes[i]
|
||||||
// TODO(jiahuif) hasAuthorizer always true for now, will change after expending type checking to all fields.
|
compiler, err := c.compiler(ctx, typeOverwrite{
|
||||||
issues, err := c.checkExpression(expression, ctx.paramDeclType != nil, true, typeOverwrite{
|
|
||||||
object: declType,
|
object: declType,
|
||||||
params: ctx.paramDeclType,
|
params: ctx.paramDeclType,
|
||||||
})
|
})
|
||||||
if issues != nil || err != nil {
|
if err != nil {
|
||||||
results = append(results, &TypeCheckingResult{Issues: issues, Err: err, GVK: gvk})
|
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
|
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 {
|
func generateUniqueTypeName(kind string) string {
|
||||||
return fmt.Sprintf("%s%d", kind, time.Now().Nanosecond())
|
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)
|
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
|
// 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
|
// the result is sorted in the order of Group, Version, and Kind
|
||||||
func (c *TypeChecker) typesToCheck(p *v1beta1.ValidatingAdmissionPolicy) []schema.GroupVersionKind {
|
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())
|
baseEnv := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())
|
||||||
requestType := plugincel.BuildRequestType()
|
requestType := plugincel.BuildRequestType()
|
||||||
namespaceType := plugincel.BuildNamespaceType()
|
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))
|
varOpts = append(varOpts, cel.Variable("authorizer", library.AuthorizerType))
|
||||||
}
|
}
|
||||||
|
|
||||||
env, err := baseEnv.Extend(
|
return baseEnv.Extend(
|
||||||
environment.VersionedOptions{
|
environment.VersionedOptions{
|
||||||
// Feature epoch was actually 1.26, but we artificially set it to 1.0 because these
|
// Feature epoch was actually 1.26, but we artificially set it to 1.0 because these
|
||||||
// options should always be present.
|
// options should always be present.
|
||||||
@ -401,10 +431,6 @@ func buildEnv(hasParams bool, hasAuthorizer bool, types typeOverwrite) (*cel.Env
|
|||||||
DeclTypes: declTypes,
|
DeclTypes: declTypes,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return env.Env(environment.StoredExpressions)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// createVariableOpts creates a slice of EnvOption
|
// createVariableOpts creates a slice of EnvOption
|
||||||
@ -421,3 +447,41 @@ func createVariableOpts(declType *apiservercel.DeclType, variables ...string) []
|
|||||||
}
|
}
|
||||||
return opts
|
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