Files
kubevela/pkg/definition/defkit/status_expr.go
Ayush Kumar 413e881e04 Feat: Defkit Refactor and Clean-Up (#7042)
* feat: Enhance status and health policy CUE generation with field grouping, column alignment, `_isHealth` pattern, and annotation-based health disable.

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* feat: introduce defkit package for a structured Go API to define KubeVela component and trait templates with outputs, patches, and helpers.

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* refactor: Consolidate two `append` calls into one for health expression parts.

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

---------

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>
2026-02-16 09:50:02 +00:00

747 lines
21 KiB
Go

/*
Copyright 2025 The KubeVela Authors.
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 defkit
import (
"fmt"
"strings"
)
// StatusExpression is the composable unit for building custom status messages.
// All status primitives implement this interface and can be combined
// to create dynamic status messages.
type StatusExpression interface {
// ToCUE generates the CUE expression for this status component.
// The expression should evaluate to a string for the message.
ToCUE() string
// Preamble returns any CUE definitions needed before the message expression.
// For example, field extractions need helper variables.
Preamble() string
// IsStringExpr returns true if this expression produces a string value
// that can be used directly in CUE interpolation.
IsStringExpr() bool
}
// StatusPolicy wraps a StatusExpression and generates the complete customStatus CUE block.
func StatusPolicy(expr StatusExpression) string {
preamble := expr.Preamble()
if preamble != "" {
return preamble + "\nmessage: " + expr.ToCUE()
}
return "message: " + expr.ToCUE()
}
// --- Status Field Expressions ---
// StatusFieldExpr extracts a field value from the output status.
type StatusFieldExpr struct {
path string
isSpec bool // true if this is from spec, false if from status
defaultValue any
hasDefault bool
varName string // generated variable name for preamble
}
// Default sets a default value if the field doesn't exist.
func (f *StatusFieldExpr) Default(value any) *StatusFieldExpr {
f.defaultValue = value
f.hasDefault = true
return f
}
// Eq checks if the field equals a value (returns a StatusConditionExpr for use in Switch).
func (f *StatusFieldExpr) Eq(value any) StatusCondition {
return &statusFieldCondition{field: f, op: "==", value: value}
}
// Ne checks if the field does not equal a value.
func (f *StatusFieldExpr) Ne(value any) StatusCondition {
return &statusFieldCondition{field: f, op: "!=", value: value}
}
// Gt checks if the field is greater than a value.
func (f *StatusFieldExpr) Gt(value any) StatusCondition {
return &statusFieldCondition{field: f, op: ">", value: value}
}
// Gte checks if the field is greater than or equal to a value.
func (f *StatusFieldExpr) Gte(value any) StatusCondition {
return &statusFieldCondition{field: f, op: ">=", value: value}
}
// Lt checks if the field is less than a value.
func (f *StatusFieldExpr) Lt(value any) StatusCondition {
return &statusFieldCondition{field: f, op: "<", value: value}
}
// Lte checks if the field is less than or equal to a value.
func (f *StatusFieldExpr) Lte(value any) StatusCondition {
return &statusFieldCondition{field: f, op: "<=", value: value}
}
func (f *StatusFieldExpr) getVarName() string {
if f.varName != "" {
return f.varName
}
// Generate a variable name from the path
// e.g., "status.readyReplicas" -> "_readyReplicas"
parts := strings.Split(f.path, ".")
if len(parts) > 0 {
lastPart := parts[len(parts)-1]
f.varName = "_" + strings.ToLower(lastPart[:1]) + lastPart[1:]
} else {
f.varName = "_field"
}
return f.varName
}
func (f *StatusFieldExpr) getFullPath() string {
return "context.output." + f.path
}
func (f *StatusFieldExpr) Preamble() string {
if !f.hasDefault {
return ""
}
varName := f.getVarName()
fullPath := f.getFullPath()
var defaultExpr string
switch v := f.defaultValue.(type) {
case string:
defaultExpr = fmt.Sprintf("*%q | string", v)
case int, int32, int64:
defaultExpr = fmt.Sprintf("*%v | int", v)
case float32, float64:
defaultExpr = fmt.Sprintf("*%v | number", v)
case bool:
defaultExpr = fmt.Sprintf("*%t | bool", v)
default:
defaultExpr = fmt.Sprintf("*%v | _", v)
}
return fmt.Sprintf(`%s: %s
if %s != _|_ {
%s: %s
}`, varName, defaultExpr, fullPath, varName, fullPath)
}
func (f *StatusFieldExpr) ToCUE() string {
if f.hasDefault {
return fmt.Sprintf(`"\(%s)"`, f.getVarName())
}
return fmt.Sprintf(`"\(%s)"`, f.getFullPath())
}
func (f *StatusFieldExpr) IsStringExpr() bool {
return true
}
// --- Status Condition Expressions (for conditions array) ---
// StatusConditionFieldExpr provides access to fields within a status condition.
type StatusConditionFieldExpr struct {
condType string
field string // "status", "message", "reason"
}
func (c *StatusConditionFieldExpr) getVarName() string {
return "_" + strings.ToLower(c.condType) + "Cond"
}
func (c *StatusConditionFieldExpr) getFieldVarName() string {
return "_" + strings.ToLower(c.condType) + strings.ToUpper(c.field[:1]) + c.field[1:]
}
func (c *StatusConditionFieldExpr) Preamble() string {
varName := c.getVarName()
fieldVar := c.getFieldVarName()
var defaultVal string
switch c.field {
case "status":
defaultVal = `*"Unknown" | string`
case "message", "reason":
defaultVal = `*"" | string`
default:
defaultVal = `*"" | string`
}
return fmt.Sprintf(`%s: [ for cond in context.output.status.conditions if cond.type == "%s" { cond } ]
%s: %s
if len(%s) > 0 {
%s: %s[0].%s
}`, varName, c.condType, fieldVar, defaultVal, varName, fieldVar, varName, c.field)
}
func (c *StatusConditionFieldExpr) ToCUE() string {
return fmt.Sprintf(`"\(%s)"`, c.getFieldVarName())
}
func (c *StatusConditionFieldExpr) IsStringExpr() bool {
return true
}
// Is checks if the condition field equals a value.
func (c *StatusConditionFieldExpr) Is(value string) StatusCondition {
return &statusConditionCheck{condType: c.condType, field: c.field, expectedValue: value}
}
// StatusConditionAccessor provides methods to access different fields of a condition.
type StatusConditionAccessor struct {
condType string
}
// StatusValue returns an expression for the condition's status field.
func (c *StatusConditionAccessor) StatusValue() *StatusConditionFieldExpr {
return &StatusConditionFieldExpr{condType: c.condType, field: "status"}
}
// Message returns an expression for the condition's message field.
func (c *StatusConditionAccessor) Message() *StatusConditionFieldExpr {
return &StatusConditionFieldExpr{condType: c.condType, field: "message"}
}
// Reason returns an expression for the condition's reason field.
func (c *StatusConditionAccessor) Reason() *StatusConditionFieldExpr {
return &StatusConditionFieldExpr{condType: c.condType, field: "reason"}
}
// Is checks if the condition's status equals a value (for use in Switch).
func (c *StatusConditionAccessor) Is(status string) StatusCondition {
return &statusConditionCheck{condType: c.condType, field: "status", expectedValue: status}
}
// --- Status Condition (boolean check for Switch) ---
// StatusCondition represents a boolean condition for use in Switch cases.
type StatusCondition interface {
// ToCUECondition generates the CUE boolean expression.
ToCUECondition() string
// Preamble returns any CUE definitions needed.
Preamble() string
}
// statusFieldCondition is a field comparison condition.
type statusFieldCondition struct {
field *StatusFieldExpr
op string
value any
}
func (c *statusFieldCondition) ToCUECondition() string {
return fmt.Sprintf("%s %s %s", c.field.getFullPath(), c.op, formatValue(c.value))
}
func (c *statusFieldCondition) Preamble() string {
return ""
}
// statusConditionCheck checks a condition field value.
type statusConditionCheck struct {
condType string
field string
expectedValue string
}
func (c *statusConditionCheck) getVarName() string {
return "_" + strings.ToLower(c.condType) + "Cond"
}
func (c *statusConditionCheck) ToCUECondition() string {
varName := c.getVarName()
return fmt.Sprintf(`len(%s) > 0 && %s[0].%s == "%s"`, varName, varName, c.field, c.expectedValue)
}
func (c *statusConditionCheck) Preamble() string {
varName := c.getVarName()
return fmt.Sprintf(`%s: [ for cond in context.output.status.conditions if cond.type == "%s" { cond } ]`,
varName, c.condType)
}
// --- Exists Expression ---
// statusExistsExpr checks if a field exists.
type statusExistsExpr struct {
path string
negate bool
}
func (e *statusExistsExpr) Preamble() string {
return ""
}
func (e *statusExistsExpr) ToCUE() string {
fullPath := "context.output." + e.path
if e.negate {
return fmt.Sprintf(`if %s == _|_ { "not exists" }`, fullPath)
}
return fmt.Sprintf(`if %s != _|_ { "exists" }`, fullPath)
}
func (e *statusExistsExpr) IsStringExpr() bool {
return false // This is a condition, not a string value
}
// ToCUECondition returns the CUE condition for use in Switch.
func (e *statusExistsExpr) ToCUECondition() string {
fullPath := "context.output." + e.path
if e.negate {
return fmt.Sprintf("%s == _|_", fullPath)
}
return fmt.Sprintf("%s != _|_", fullPath)
}
// --- Message Building Expressions ---
// statusLiteralExpr is a literal string value.
type statusLiteralExpr struct {
value string
}
func (l *statusLiteralExpr) Preamble() string {
return ""
}
func (l *statusLiteralExpr) ToCUE() string {
return fmt.Sprintf("%q", l.value)
}
func (l *statusLiteralExpr) IsStringExpr() bool {
return true
}
// statusConcatExpr concatenates multiple expressions.
type statusConcatExpr struct {
parts []any // Can be StatusExpression or string literals
}
func (c *statusConcatExpr) Preamble() string {
var preambles []string
for _, part := range c.parts {
if expr, ok := part.(StatusExpression); ok {
if p := expr.Preamble(); p != "" {
preambles = append(preambles, p)
}
}
}
return strings.Join(preambles, "\n")
}
func (c *statusConcatExpr) ToCUE() string {
var sb strings.Builder
sb.WriteString(`"`)
for _, part := range c.parts {
switch p := part.(type) {
case string:
sb.WriteString(p)
case *StatusFieldExpr:
if p.hasDefault {
sb.WriteString(fmt.Sprintf(`\(%s)`, p.getVarName()))
} else {
sb.WriteString(fmt.Sprintf(`\(%s)`, p.getFullPath()))
}
case *StatusConditionFieldExpr:
sb.WriteString(fmt.Sprintf(`\(%s)`, p.getFieldVarName()))
case StatusExpression:
// Extract the interpolation part from the expression
cue := p.ToCUE()
// Remove surrounding quotes if present
cue = strings.TrimPrefix(cue, `"`)
cue = strings.TrimSuffix(cue, `"`)
sb.WriteString(cue)
default:
sb.WriteString(fmt.Sprintf("%v", p))
}
}
sb.WriteString(`"`)
return sb.String()
}
func (c *statusConcatExpr) IsStringExpr() bool {
return true
}
// statusFormatExpr provides printf-style formatting.
type statusFormatExpr struct {
template string
args []StatusExpression
}
func (f *statusFormatExpr) Preamble() string {
var preambles []string
for _, arg := range f.args {
if p := arg.Preamble(); p != "" {
preambles = append(preambles, p)
}
}
return strings.Join(preambles, "\n")
}
func (f *statusFormatExpr) ToCUE() string {
// Replace %v with CUE interpolations
result := f.template
for _, arg := range f.args {
cue := arg.ToCUE()
// Remove surrounding quotes
cue = strings.TrimPrefix(cue, `"`)
cue = strings.TrimSuffix(cue, `"`)
// Replace first %v with the interpolation
result = strings.Replace(result, "%v", cue, 1)
}
return fmt.Sprintf(`"%s"`, result)
}
func (f *statusFormatExpr) IsStringExpr() bool {
return true
}
// --- Switch Expression ---
// StatusCase represents a case in a Switch expression.
type StatusCase struct {
condition StatusCondition
message StatusExpression
}
// statusSwitchExpr provides conditional message selection.
type statusSwitchExpr struct {
cases []*StatusCase
defaultValue StatusExpression
}
func (s *statusSwitchExpr) Preamble() string {
var preambles []string
seen := make(map[string]bool)
// Collect preambles from conditions
for _, c := range s.cases {
if p := c.condition.Preamble(); p != "" && !seen[p] {
preambles = append(preambles, p)
seen[p] = true
}
if p := c.message.Preamble(); p != "" && !seen[p] {
preambles = append(preambles, p)
seen[p] = true
}
}
if s.defaultValue != nil {
if p := s.defaultValue.Preamble(); p != "" && !seen[p] {
preambles = append(preambles, p)
}
}
return strings.Join(preambles, "\n")
}
func (s *statusSwitchExpr) ToCUE() string {
// For Switch, we generate the full message block with conditionals
// The caller (StatusPolicy) will handle wrapping
return s.buildMessageBlock()
}
func (s *statusSwitchExpr) buildMessageBlock() string {
var sb strings.Builder
// Default value first (will be overridden by conditions)
if s.defaultValue != nil {
sb.WriteString(fmt.Sprintf("*%s | string", s.defaultValue.ToCUE()))
} else {
sb.WriteString(`*"" | string`)
}
return sb.String()
}
func (s *statusSwitchExpr) IsStringExpr() bool {
return true
}
// Override Preamble to include the switch logic
func (s *statusSwitchExpr) BuildFull() string {
var sb strings.Builder
// Collect all preambles first
preambles := s.Preamble()
if preambles != "" {
sb.WriteString(preambles)
sb.WriteString("\n")
}
// Write message with default
if s.defaultValue != nil {
sb.WriteString(fmt.Sprintf("message: *%s | string\n", s.defaultValue.ToCUE()))
} else {
sb.WriteString(`message: *"" | string`)
sb.WriteString("\n")
}
// Write conditional overrides
for _, c := range s.cases {
sb.WriteString(fmt.Sprintf("if %s {\n\tmessage: %s\n}\n",
c.condition.ToCUECondition(), c.message.ToCUE()))
}
return strings.TrimSuffix(sb.String(), "\n")
}
// --- Health-Aware Expression ---
// statusHealthAwareExpr provides different messages based on health status.
type statusHealthAwareExpr struct {
healthyMsg StatusExpression
unhealthyMsg StatusExpression
}
func (h *statusHealthAwareExpr) Preamble() string {
var preambles []string
if p := h.healthyMsg.Preamble(); p != "" {
preambles = append(preambles, p)
}
if p := h.unhealthyMsg.Preamble(); p != "" {
preambles = append(preambles, p)
}
return strings.Join(preambles, "\n")
}
func (h *statusHealthAwareExpr) ToCUE() string {
// Will be handled by BuildFull
return h.healthyMsg.ToCUE()
}
func (h *statusHealthAwareExpr) IsStringExpr() bool {
return true
}
func (h *statusHealthAwareExpr) BuildFull() string {
var sb strings.Builder
preamble := h.Preamble()
if preamble != "" {
sb.WriteString(preamble)
sb.WriteString("\n")
}
sb.WriteString(fmt.Sprintf("message: *%s | string\n", h.unhealthyMsg.ToCUE()))
sb.WriteString(fmt.Sprintf("if context.status.healthy {\n\tmessage: %s\n}", h.healthyMsg.ToCUE()))
return sb.String()
}
// --- Status Details Expression ---
// StatusDetail represents a key-value detail.
type StatusDetail struct {
key string
value StatusExpression
}
// statusWithDetailsExpr adds structured details to the status.
type statusWithDetailsExpr struct {
message StatusExpression
details []*StatusDetail
}
func (d *statusWithDetailsExpr) Preamble() string {
var preambles []string
if p := d.message.Preamble(); p != "" {
preambles = append(preambles, p)
}
for _, detail := range d.details {
if p := detail.value.Preamble(); p != "" {
preambles = append(preambles, p)
}
}
return strings.Join(preambles, "\n")
}
func (d *statusWithDetailsExpr) ToCUE() string {
return d.message.ToCUE()
}
func (d *statusWithDetailsExpr) IsStringExpr() bool {
return true
}
// --- StatusBuilder Extension Methods ---
// Field creates an expression to extract a status field value.
// Example: Status().Field("status.readyReplicas").Default(0)
func (s *StatusBuilder) Field(path string) *StatusFieldExpr {
return &StatusFieldExpr{path: path}
}
// SpecField creates an expression to extract a spec field value.
// Example: Status().SpecField("spec.replicas")
func (s *StatusBuilder) SpecField(path string) *StatusFieldExpr {
return &StatusFieldExpr{path: path, isSpec: true}
}
// Condition creates an accessor for a status condition.
// Example: Status().Condition("Ready").Message()
func (s *StatusBuilder) Condition(condType string) *StatusConditionAccessor {
return &StatusConditionAccessor{condType: condType}
}
// Exists creates an expression to check if a field exists.
// Example: Status().Exists("status.endpoint")
func (s *StatusBuilder) Exists(path string) *statusExistsExpr {
return &statusExistsExpr{path: path, negate: false}
}
// NotExists creates an expression to check if a field does not exist.
// Example: Status().NotExists("status.error")
func (s *StatusBuilder) NotExists(path string) *statusExistsExpr {
return &statusExistsExpr{path: path, negate: true}
}
// Literal creates a literal string expression.
// Example: Status().Literal("Service is running")
func (s *StatusBuilder) Literal(value string) StatusExpression {
return &statusLiteralExpr{value: value}
}
// Concat creates a concatenation of multiple expressions or strings.
// Example: Status().Concat("Ready: ", s.Field("status.ready"), "/", s.Field("status.total"))
func (s *StatusBuilder) Concat(parts ...any) StatusExpression {
return &statusConcatExpr{parts: parts}
}
// Format creates a printf-style formatted message.
// Example: Status().Format("Ready: %v/%v", readyField, totalField)
func (s *StatusBuilder) Format(template string, args ...StatusExpression) StatusExpression {
return &statusFormatExpr{template: template, args: args}
}
// Case creates a case for use in Switch.
// Example: s.Case(s.Field("status.phase").Eq("Running"), "Service is running")
func (s *StatusBuilder) Case(condition StatusCondition, message any) *StatusCase {
var msgExpr StatusExpression
switch m := message.(type) {
case string:
msgExpr = &statusLiteralExpr{value: m}
case StatusExpression:
msgExpr = m
default:
msgExpr = &statusLiteralExpr{value: fmt.Sprintf("%v", m)}
}
return &StatusCase{condition: condition, message: msgExpr}
}
// Default creates a default case for Switch.
// Example: s.Default("Unknown status")
func (s *StatusBuilder) Default(message any) StatusExpression {
switch m := message.(type) {
case string:
return &statusLiteralExpr{value: m}
case StatusExpression:
return m
default:
return &statusLiteralExpr{value: fmt.Sprintf("%v", m)}
}
}
// Switch creates a conditional message selection.
// Example: Status().Switch(case1, case2, s.Default("Unknown"))
func (s *StatusBuilder) Switch(casesAndDefault ...any) StatusExpression {
sw := &statusSwitchExpr{}
for _, item := range casesAndDefault {
switch v := item.(type) {
case *StatusCase:
sw.cases = append(sw.cases, v)
case StatusExpression:
sw.defaultValue = v
}
}
return sw
}
// HealthAware creates a message that differs based on health status.
// Example: Status().HealthAware("All systems operational", "Service degraded")
func (s *StatusBuilder) HealthAware(healthyMsg, unhealthyMsg any) StatusExpression {
var healthy, unhealthy StatusExpression
switch m := healthyMsg.(type) {
case string:
healthy = &statusLiteralExpr{value: m}
case StatusExpression:
healthy = m
}
switch m := unhealthyMsg.(type) {
case string:
unhealthy = &statusLiteralExpr{value: m}
case StatusExpression:
unhealthy = m
}
return &statusHealthAwareExpr{healthyMsg: healthy, unhealthyMsg: unhealthy}
}
// Detail creates a detail entry for use with WithDetails.
// Example: s.Detail("endpoint", s.Field("status.endpoint"))
func (s *StatusBuilder) Detail(key string, value StatusExpression) *StatusDetail {
return &StatusDetail{key: key, value: value}
}
// WithDetails creates a status expression that includes structured details alongside the message.
// The details are key-value pairs that provide additional context.
//
// Example:
//
// s := Status()
// CustomStatusExpr(s.WithDetails(
// s.Format("Ready: %v/%v", s.Field("status.readyReplicas").Default(0), s.SpecField("spec.replicas")),
// s.Detail("endpoint", s.Field("status.endpoint")),
// s.Detail("version", s.Field("status.version")),
// ))
func (s *StatusBuilder) WithDetails(message StatusExpression, details ...*StatusDetail) StatusExpression {
return &statusWithDetailsExpr{message: message, details: details}
}
// StatusExpr sets the status expression using a composable StatusExpression.
// This generates the complete customStatus CUE block.
// Example: Status().StatusExpr(s.Concat("Ready: ", s.Field("status.ready")))
func (s *StatusBuilder) StatusExpr(expr StatusExpression) *StatusBuilder {
// Handle special expression types that need full generation
switch e := expr.(type) {
case *statusSwitchExpr:
s.rawCUE = e.BuildFull()
case *statusHealthAwareExpr:
s.rawCUE = e.BuildFull()
default:
s.rawCUE = StatusPolicy(expr)
}
return s
}
// CustomStatusExpr is a convenience function for component definitions.
// It creates a StatusBuilder with the given expression and returns it for use with CustomStatus().
// Example: CustomStatusExpr(Status().Concat("Ready: ", s.Field("status.ready")))
func CustomStatusExpr(expr StatusExpression) string {
switch e := expr.(type) {
case *statusSwitchExpr:
return e.BuildFull()
case *statusHealthAwareExpr:
return e.BuildFull()
default:
return StatusPolicy(expr)
}
}