kubectl-utils/pkg/kel/info.go (109 lines of code) (raw):

// Copyright 2025 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 kel import ( "context" "fmt" "strings" "github.com/google/cel-go/cel" exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" "google.golang.org/protobuf/proto" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/klog/v2" ) type InfoFunction func(ctx context.Context, self *unstructured.Unstructured) string // BuildStatusPrinter returns an InfoFunction that attempts to report important values from the evaluation of the CEL expression func (x *Expression) BuildStatusPrinter(ctx context.Context) (InfoFunction, error) { log := klog.FromContext(ctx) checkedExpr, err := cel.AstToCheckedExpr(x.AST) if err != nil { return nil, fmt.Errorf("parsing CEL ast: %w", err) } v := checkedExpr.Expr.ExprKind switch v := v.(type) { case *exprpb.Expr_CallExpr: printFunction := "" switch v.CallExpr.Function { case "_==_": printFunction = "=" case "_>=_": printFunction = ">=" case "_<=_": printFunction = "<=" case "_>_": printFunction = ">" case "_<_": printFunction = "<" default: klog.Warningf("unhandled function %q", v.CallExpr.Function) return nil, nil } log.V(2).Info("recognized function", "function", printFunction) return x.buildFunctionPrinterFor(v.CallExpr.Args) default: klog.Warningf("unhandled expression kind %T", checkedExpr.Expr.ExprKind) return nil, nil } } func (x *Expression) buildFunctionPrinterFor(args []*exprpb.Expr) (InfoFunction, error) { checkedExpr, err := cel.AstToCheckedExpr(x.AST) if err != nil { return nil, fmt.Errorf("parsing CEL ast: %w", err) } type debugValue struct { Key string Program cel.Program } var debugValues []debugValue for _, arg := range args { shouldPrint := true v := arg.ExprKind switch v := v.(type) { case *exprpb.Expr_ConstExpr: // Don't print constants, 2=2 is not informative shouldPrint = false case *exprpb.Expr_SelectExpr: shouldPrint = true default: klog.Warningf("unhandled expression kind %T", v) } if !shouldPrint { continue } checkedArg := proto.Clone(checkedExpr).(*exprpb.CheckedExpr) checkedArg.Expr = arg ast := cel.CheckedExprToAst(checkedArg) celExpression, err := cel.AstToString(ast) if err != nil { return nil, fmt.Errorf("converting expression to string: %w", err) } compiled, issues := x.Env.Compile(celExpression) if issues != nil && issues.Err() != nil { return nil, fmt.Errorf("invalid expression %q: %w", celExpression, issues.Err()) } prg, err := x.Env.Program(compiled) if err != nil { return nil, fmt.Errorf("invalid expression %q: %w", celExpression, err) } debugValues = append(debugValues, debugValue{ Key: celExpression, Program: prg, }) } if len(debugValues) == 0 { return nil, nil } return func(ctx context.Context, self *unstructured.Unstructured) string { log := klog.FromContext(ctx) inputs := x.buildInputs(self) var values []string for _, debugValue := range debugValues { s := "" out, details, err := debugValue.Program.Eval(inputs) log.V(2).Info("evaluated CEL expression", "out", out, "details", details, "error", err) if err == nil { s = fmt.Sprintf("%s=%v", debugValue.Key, out.Value()) } else { s = fmt.Sprintf("%s=%v", debugValue.Key, "???") } values = append(values, s) } return strings.Join(values, "; ") }, nil }