From 43bc759359e47b15e80dad074c4ec3939ce345c6 Mon Sep 17 00:00:00 2001 From: Himanshu Sharma Date: Tue, 11 Mar 2025 09:33:43 +0530 Subject: [PATCH 1/2] Adding function in the types --- api/v1alpha1/types.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/api/v1alpha1/types.go b/api/v1alpha1/types.go index 77aa01f7..efe90b3b 100644 --- a/api/v1alpha1/types.go +++ b/api/v1alpha1/types.go @@ -36,6 +36,10 @@ type ResourceGraphDefinitionSpec struct { // // +kubebuilder:validation:Optional Resources []*Resource `json:"resources,omitempty"` + + // Functions defines custom CEL functions that can be used in expressions + // +kubebuilder:validation:Optional + Functions []Function `json:"functions,omitempty"` // ServiceAccount configuration for controller impersonation. // Key is the namespace, value is the service account name to use. // Special key "*" defines the default service account for any @@ -45,6 +49,18 @@ type ResourceGraphDefinitionSpec struct { DefaultServiceAccounts map[string]string `json:"defaultServiceAccounts,omitempty"` } +type Function struct { + // The unique identifier for the function + ID string `json:"id"` + + // Inputs defines the expected input types + // +kubebuilder:validation:MinItems=1 + Inputs []string `json:"inputs"` + + // The CEL expression template that defines the function logic + Template string `json:"template"` +} + // Schema represents the attributes that define an instance of // a resourcegraphdefinition. type Schema struct { From 34c40bf816906faa9d9fd899a49bed1e4d4aa245 Mon Sep 17 00:00:00 2001 From: Himanshu Sharma Date: Tue, 11 Mar 2025 09:34:32 +0530 Subject: [PATCH 2/2] Adding custom functions support --- pkg/cel/environment.go | 4 ++ pkg/cel/function/function.go | 108 +++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 pkg/cel/function/function.go diff --git a/pkg/cel/environment.go b/pkg/cel/environment.go index 5cf278d4..e4c80740 100644 --- a/pkg/cel/environment.go +++ b/pkg/cel/environment.go @@ -59,8 +59,12 @@ func DefaultEnvironment(options ...EnvOption) (*cel.Env, error) { ext.Strings(), } + // Add resource IDs for _, name := range opts.resourceIDs { declarations = append(declarations, cel.Variable(name, cel.AnyType)) } + + declarations = append(declarations, opts.customDeclarations...) + return cel.NewEnv(declarations...) } diff --git a/pkg/cel/function/function.go b/pkg/cel/function/function.go new file mode 100644 index 00000000..3b075835 --- /dev/null +++ b/pkg/cel/function/function.go @@ -0,0 +1,108 @@ +package function + +import ( + "fmt" + + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" + xv1alpha1 "github.com/kro-run/kro/api/v1alpha1" +) + +// FunctionRegistry manages custom CEL functions +type FunctionRegistry struct { + functions map[string]*xv1alpha1.Function +} + +// NewFunctionRegistry creates a new function registry +func NewFunctionRegistry() *FunctionRegistry { + return &FunctionRegistry{ + functions: make(map[string]*xv1alpha1.Function), + } +} + +// RegisterFunction adds a function to the registry +func (r *FunctionRegistry) RegisterFunction(fn *xv1alpha1.Function) error { + if _, exists := r.functions[fn.ID]; exists { + return fmt.Errorf("function %s already registered", fn.ID) + } + r.functions[fn.ID] = fn + return nil +} + +// GetCELFunction converts a Function to a CEL function declaration +func (r *FunctionRegistry) GetCELFunction(fn *xv1alpha1.Function) (cel.EnvOption, error) { + + //convert input types to CEL types + celTypes := make([]*cel.Type, len((fn.Inputs))) + envOpts := make([]cel.EnvOption, len(fn.Inputs)) + + for i, inputType := range fn.Inputs { + celType, err := getCelType(inputType) + if err != nil { + return nil, err + } + celTypes[i] = celType + envOpts[i] = cel.Variable(fmt.Sprintf("_%d", i), celType) + } + + // Create CEL environment for function template + env, err := cel.NewEnv(envOpts...) + if err != nil { + return nil, fmt.Errorf("environment creation failed: %w", err) + } + + // Compile function template + ast, iss := env.Compile(fn.Template) + if iss.Err() != nil { + return nil, fmt.Errorf("template compilation failed: %w", iss.Err()) + } + + prg, err := env.Program(ast) + if err != nil { + return nil, fmt.Errorf("program creation failed: %w", err) + } + + // Return CEL function declaration + return cel.Function(fn.ID, + cel.Overload(fn.ID, + celTypes, + ast.OutputType(), + cel.FunctionBinding(func(args ...ref.Val) ref.Val { + vars := map[string]any{} + for i, arg := range args { + vars[fmt.Sprintf("_%d", i)] = arg + } + out, _, err := prg.Eval(vars) + if err != nil { + return types.WrapErr(err) + } + return out + }), + ), + ), nil + +} + +func getCelType(typeName string) (*cel.Type, error) { + switch typeName { + case "string": + return cel.StringType, nil + case "int": + return cel.IntType, nil + case "bool": + return cel.BoolType, nil + case "double", "float": + return cel.DoubleType, nil + case "bytes": + return cel.BytesType, nil + case "any": + return cel.AnyType, nil + case "list": + return cel.ListType(cel.AnyType), nil + case "map": + return cel.MapType(cel.StringType, cel.AnyType), nil + default: + return nil, fmt.Errorf("unsupported type: %s", typeName) + } +}