mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-03-10 09:29:30 +00:00
Several packages are only used while running the e2e suite. These packages are less important to update, as the they can not influence the final executable that is part of the Ceph-CSI container-image. By moving these dependencies out of the main Ceph-CSI go.mod, it is easier to identify if a reported CVE affects Ceph-CSI, or only the testing (like most of the Kubernetes CVEs). Signed-off-by: Niels de Vos <ndevos@ibm.com>
337 lines
9.5 KiB
Go
337 lines
9.5 KiB
Go
// Copyright 2023 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package ext
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/google/cel-go/cel"
|
|
"github.com/google/cel-go/common/ast"
|
|
"github.com/google/cel-go/common/types"
|
|
"github.com/google/cel-go/common/types/ref"
|
|
"github.com/google/cel-go/common/types/traits"
|
|
"github.com/google/cel-go/interpreter"
|
|
)
|
|
|
|
// Bindings returns a cel.EnvOption to configure support for local variable
|
|
// bindings in expressions.
|
|
//
|
|
// # Cel.Bind
|
|
//
|
|
// Binds a simple identifier to an initialization expression which may be used
|
|
// in a subsequenct result expression. Bindings may also be nested within each
|
|
// other.
|
|
//
|
|
// cel.bind(<varName>, <initExpr>, <resultExpr>)
|
|
//
|
|
// Examples:
|
|
//
|
|
// cel.bind(a, 'hello',
|
|
// cel.bind(b, 'world', a + b + b + a)) // "helloworldworldhello"
|
|
//
|
|
// // Avoid a list allocation within the exists comprehension.
|
|
// cel.bind(valid_values, [a, b, c],
|
|
// [d, e, f].exists(elem, elem in valid_values))
|
|
//
|
|
// Local bindings are not guaranteed to be evaluated before use.
|
|
func Bindings(options ...BindingsOption) cel.EnvOption {
|
|
b := &celBindings{version: math.MaxUint32}
|
|
for _, o := range options {
|
|
b = o(b)
|
|
}
|
|
return cel.Lib(b)
|
|
}
|
|
|
|
const (
|
|
celNamespace = "cel"
|
|
bindMacro = "bind"
|
|
blockFunc = "@block"
|
|
unusedIterVar = "#unused"
|
|
)
|
|
|
|
// BindingsOption declares a functional operator for configuring the Bindings library behavior.
|
|
type BindingsOption func(*celBindings) *celBindings
|
|
|
|
// BindingsVersion sets the version of the bindings library to an explicit version.
|
|
func BindingsVersion(version uint32) BindingsOption {
|
|
return func(lib *celBindings) *celBindings {
|
|
lib.version = version
|
|
return lib
|
|
}
|
|
}
|
|
|
|
type celBindings struct {
|
|
version uint32
|
|
}
|
|
|
|
func (*celBindings) LibraryName() string {
|
|
return "cel.lib.ext.cel.bindings"
|
|
}
|
|
|
|
func (lib *celBindings) CompileOptions() []cel.EnvOption {
|
|
opts := []cel.EnvOption{
|
|
cel.Macros(
|
|
// cel.bind(var, <init>, <expr>)
|
|
cel.ReceiverMacro(bindMacro, 3, celBind),
|
|
),
|
|
}
|
|
if lib.version >= 1 {
|
|
// The cel.@block signature takes a list of subexpressions and a typed expression which is
|
|
// used as the output type.
|
|
paramType := cel.TypeParamType("T")
|
|
opts = append(opts,
|
|
cel.Function("cel.@block",
|
|
cel.Overload("cel_block_list",
|
|
[]*cel.Type{cel.ListType(cel.DynType), paramType}, paramType)),
|
|
)
|
|
opts = append(opts, cel.ASTValidators(blockValidationExemption{}))
|
|
}
|
|
return opts
|
|
}
|
|
|
|
func (lib *celBindings) ProgramOptions() []cel.ProgramOption {
|
|
if lib.version >= 1 {
|
|
celBlockPlan := func(i interpreter.Interpretable) (interpreter.Interpretable, error) {
|
|
call, ok := i.(interpreter.InterpretableCall)
|
|
if !ok {
|
|
return i, nil
|
|
}
|
|
switch call.Function() {
|
|
case "cel.@block":
|
|
args := call.Args()
|
|
if len(args) != 2 {
|
|
return nil, fmt.Errorf("cel.@block expects two arguments, but got %d", len(args))
|
|
}
|
|
expr := args[1]
|
|
// Non-empty block
|
|
if block, ok := args[0].(interpreter.InterpretableConstructor); ok {
|
|
slotExprs := block.InitVals()
|
|
return newDynamicBlock(slotExprs, expr), nil
|
|
}
|
|
// Constant valued block which can happen during runtime optimization.
|
|
if cons, ok := args[0].(interpreter.InterpretableConst); ok {
|
|
if cons.Value().Type() == types.ListType {
|
|
l := cons.Value().(traits.Lister)
|
|
if l.Size().Equal(types.IntZero) == types.True {
|
|
return args[1], nil
|
|
}
|
|
return newConstantBlock(l, expr), nil
|
|
}
|
|
}
|
|
return nil, errors.New("cel.@block expects a list constructor as the first argument")
|
|
default:
|
|
return i, nil
|
|
}
|
|
}
|
|
return []cel.ProgramOption{cel.CustomDecorator(celBlockPlan)}
|
|
}
|
|
return []cel.ProgramOption{}
|
|
}
|
|
|
|
type blockValidationExemption struct{}
|
|
|
|
// Name returns the name of the validator.
|
|
func (blockValidationExemption) Name() string {
|
|
return "cel.lib.ext.validate.functions.cel.block"
|
|
}
|
|
|
|
// Configure implements the ASTValidatorConfigurer interface and augments the list of functions to skip
|
|
// during homogeneous aggregate literal type-checks.
|
|
func (blockValidationExemption) Configure(config cel.MutableValidatorConfig) error {
|
|
functions := config.GetOrDefault(cel.HomogeneousAggregateLiteralExemptFunctions, []string{}).([]string)
|
|
functions = append(functions, "cel.@block")
|
|
return config.Set(cel.HomogeneousAggregateLiteralExemptFunctions, functions)
|
|
}
|
|
|
|
// Validate is a no-op as the intent is to simply disable strong type-checks for list literals during
|
|
// when they occur within cel.@block calls as the arg types have already been validated.
|
|
func (blockValidationExemption) Validate(env *cel.Env, _ cel.ValidatorConfig, a *ast.AST, iss *cel.Issues) {
|
|
}
|
|
|
|
func celBind(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *cel.Error) {
|
|
if !macroTargetMatchesNamespace(celNamespace, target) {
|
|
return nil, nil
|
|
}
|
|
varIdent := args[0]
|
|
varName := ""
|
|
switch varIdent.Kind() {
|
|
case ast.IdentKind:
|
|
varName = varIdent.AsIdent()
|
|
default:
|
|
return nil, mef.NewError(varIdent.ID(), "cel.bind() variable names must be simple identifiers")
|
|
}
|
|
varInit := args[1]
|
|
resultExpr := args[2]
|
|
return mef.NewComprehension(
|
|
mef.NewList(),
|
|
unusedIterVar,
|
|
varName,
|
|
varInit,
|
|
mef.NewLiteral(types.False),
|
|
mef.NewIdent(varName),
|
|
resultExpr,
|
|
), nil
|
|
}
|
|
|
|
func newDynamicBlock(slotExprs []interpreter.Interpretable, expr interpreter.Interpretable) interpreter.Interpretable {
|
|
bs := &dynamicBlock{
|
|
slotExprs: slotExprs,
|
|
expr: expr,
|
|
}
|
|
bs.slotActivationPool = &sync.Pool{
|
|
New: func() any {
|
|
slotCount := len(slotExprs)
|
|
sa := &dynamicSlotActivation{
|
|
slotExprs: slotExprs,
|
|
slotCount: slotCount,
|
|
slotVals: make([]*slotVal, slotCount),
|
|
}
|
|
for i := 0; i < slotCount; i++ {
|
|
sa.slotVals[i] = &slotVal{}
|
|
}
|
|
return sa
|
|
},
|
|
}
|
|
return bs
|
|
}
|
|
|
|
type dynamicBlock struct {
|
|
slotExprs []interpreter.Interpretable
|
|
expr interpreter.Interpretable
|
|
slotActivationPool *sync.Pool
|
|
}
|
|
|
|
// ID implements the Interpretable interface method.
|
|
func (b *dynamicBlock) ID() int64 {
|
|
return b.expr.ID()
|
|
}
|
|
|
|
// Eval implements the Interpretable interface method.
|
|
func (b *dynamicBlock) Eval(activation interpreter.Activation) ref.Val {
|
|
sa := b.slotActivationPool.Get().(*dynamicSlotActivation)
|
|
sa.Activation = activation
|
|
defer b.clearSlots(sa)
|
|
return b.expr.Eval(sa)
|
|
}
|
|
|
|
func (b *dynamicBlock) clearSlots(sa *dynamicSlotActivation) {
|
|
sa.reset()
|
|
b.slotActivationPool.Put(sa)
|
|
}
|
|
|
|
type slotVal struct {
|
|
value *ref.Val
|
|
visited bool
|
|
}
|
|
|
|
type dynamicSlotActivation struct {
|
|
interpreter.Activation
|
|
slotExprs []interpreter.Interpretable
|
|
slotCount int
|
|
slotVals []*slotVal
|
|
}
|
|
|
|
// ResolveName implements the Activation interface method but handles variables prefixed with `@index`
|
|
// as special variables which exist within the slot-based memory of the cel.@block() where each slot
|
|
// refers to an expression which must be computed only once.
|
|
func (sa *dynamicSlotActivation) ResolveName(name string) (any, bool) {
|
|
if idx, found := matchSlot(name, sa.slotCount); found {
|
|
v := sa.slotVals[idx]
|
|
if v.visited {
|
|
// Return not found if the index expression refers to itself
|
|
if v.value == nil {
|
|
return nil, false
|
|
}
|
|
return *v.value, true
|
|
}
|
|
v.visited = true
|
|
val := sa.slotExprs[idx].Eval(sa)
|
|
v.value = &val
|
|
return val, true
|
|
}
|
|
return sa.Activation.ResolveName(name)
|
|
}
|
|
|
|
func (sa *dynamicSlotActivation) reset() {
|
|
sa.Activation = nil
|
|
for _, sv := range sa.slotVals {
|
|
sv.visited = false
|
|
sv.value = nil
|
|
}
|
|
}
|
|
|
|
func newConstantBlock(slots traits.Lister, expr interpreter.Interpretable) interpreter.Interpretable {
|
|
count := slots.Size().(types.Int)
|
|
return &constantBlock{slots: slots, slotCount: int(count), expr: expr}
|
|
}
|
|
|
|
type constantBlock struct {
|
|
slots traits.Lister
|
|
slotCount int
|
|
expr interpreter.Interpretable
|
|
}
|
|
|
|
// ID implements the interpreter.Interpretable interface method.
|
|
func (b *constantBlock) ID() int64 {
|
|
return b.expr.ID()
|
|
}
|
|
|
|
// Eval implements the interpreter.Interpretable interface method, and will proxy @index prefixed variable
|
|
// lookups into a set of constant slots determined from the plan step.
|
|
func (b *constantBlock) Eval(activation interpreter.Activation) ref.Val {
|
|
vars := constantSlotActivation{Activation: activation, slots: b.slots, slotCount: b.slotCount}
|
|
return b.expr.Eval(vars)
|
|
}
|
|
|
|
type constantSlotActivation struct {
|
|
interpreter.Activation
|
|
slots traits.Lister
|
|
slotCount int
|
|
}
|
|
|
|
// ResolveName implements Activation interface method and proxies @index prefixed lookups into the slot
|
|
// activation associated with the block scope.
|
|
func (sa constantSlotActivation) ResolveName(name string) (any, bool) {
|
|
if idx, found := matchSlot(name, sa.slotCount); found {
|
|
return sa.slots.Get(types.Int(idx)), true
|
|
}
|
|
return sa.Activation.ResolveName(name)
|
|
}
|
|
|
|
func matchSlot(name string, slotCount int) (int, bool) {
|
|
if idx, found := strings.CutPrefix(name, indexPrefix); found {
|
|
idx, err := strconv.Atoi(idx)
|
|
// Return not found if the index is not numeric
|
|
if err != nil {
|
|
return -1, false
|
|
}
|
|
// Return not found if the index is not a valid slot
|
|
if idx < 0 || idx >= slotCount {
|
|
return -1, false
|
|
}
|
|
return idx, true
|
|
}
|
|
return -1, false
|
|
}
|
|
|
|
var (
|
|
indexPrefix = "@index"
|
|
)
|