internal/readiness/readiness.go (98 lines of code) (raw):

package readiness import ( "context" "fmt" "sort" "time" celtypes "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "github.com/google/cel-go/cel" ) var defaultEnv *cel.Env func init() { initDefaultEnv() } func initDefaultEnv() { var err error defaultEnv, err = cel.NewEnv(cel.Variable("self", cel.DynType)) if err != nil { panic(fmt.Sprintf("failed to create default CEL environment: %v", err)) } } // Check represents a parsed readiness check CEL expression. type Check struct { Name string program cel.Program } // ParseCheck parses the given CEL expression in the context of an environment, // and returns a reusable execution handle. func ParseCheck(expr string) (*Check, error) { ast, iss := defaultEnv.Compile(expr) if iss != nil && iss.Err() != nil { return nil, iss.Err() } prgm, err := defaultEnv.Program(ast, cel.InterruptCheckFrequency(10)) if err != nil { return nil, err } return &Check{program: prgm}, nil } // Eval executes the compiled check against a given resource. func (r *Check) Eval(ctx context.Context, resource *unstructured.Unstructured) (*Status, bool) { if resource == nil { return nil, false } val, _, err := r.program.ContextEval(ctx, map[string]any{"self": resource.Object}) if err != nil { return nil, false } // Support matching on condition structs. // This allows us to grab the transition time instead of just using the current time. if list, ok := val.Value().([]ref.Val); ok { for _, ref := range list { if mp, ok := ref.Value().(map[string]any); ok { if mp != nil && mp["status"] == "True" && mp["type"] != nil && mp["reason"] != nil { ts := metav1.Now() if str, ok := mp["lastTransitionTime"].(string); ok { parsed, err := time.Parse(time.RFC3339, str) if err == nil { ts.Time = parsed } } return &Status{ReadyTime: ts, PreciseTime: err == nil}, true } } } } if val == celtypes.True { return &Status{ReadyTime: metav1.Now()}, true } return nil, false } type Checks []*Check // Eval evaluates and prioritizes the set of readiness checks. // // - Nil is returned when less than all of the checks are ready // - If some precise and some inprecise times are given, the precise times are favored // - Within precise or non-precise times, the max of that group is always used func (r Checks) Eval(ctx context.Context, resource *unstructured.Unstructured) (*Status, bool) { var all []*Status for _, check := range r { if ready, ok := check.Eval(ctx, resource); ok { all = append(all, ready) } } if len(all) == 0 || len(all) != len(r) { return nil, false } sort.Slice(all, func(i, j int) bool { return all[j].ReadyTime.Before(&all[i].ReadyTime) }) // Use the max precise time if any are precise for _, ready := range all { ready := ready if !ready.PreciseTime { continue } return ready, true } // We don't have any precise times, fall back to the max return all[0], true } // EvalOptionally is identical to Eval, except it returns the current time in the status if no checks are set. func (r Checks) EvalOptionally(ctx context.Context, resource *unstructured.Unstructured) (*Status, bool) { if len(r) == 0 { return &Status{ReadyTime: metav1.Now()}, true } return r.Eval(ctx, resource) } type Status struct { ReadyTime metav1.Time PreciseTime bool // true when time came from a condition, not the controller's metav1.Now }