Feat: Support Native Cue in HealthPolicy and CustomStatus (#6859)

* Feat: Support Native Cue in HealthPolicy and CustomStatus

Signed-off-by: Brian Kane <briankane1@gmail.com>

* Feat: Support Native Cue in HealthPolicy and CustomStatus - Fix PR Comments & Bugs

Signed-off-by: Brian Kane <briankane1@gmail.com>

---------

Signed-off-by: Brian Kane <briankane1@gmail.com>
This commit is contained in:
Brian Kane 2025-08-22 05:24:21 +01:00 committed by GitHub
parent 3aa94842fb
commit a5de74ec1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 1447 additions and 73 deletions

View File

@ -27,14 +27,22 @@ import (
const (
// status is the path to the status field in the metadata
status = "attributes.status.details"
status = "attributes.status.details"
healthPolicy = "attributes.status.healthPolicy"
customStatus = "attributes.status.customStatus"
// localFieldPrefix is the prefix for local fields not output to the status
localFieldPrefix = "$"
)
// EncodeMetadata encodes native CUE in the metadata fields to a CUE string literal
func EncodeMetadata(field *ast.Field) error {
if err := marshalStatusDetailsField(field); err != nil {
if err := marshalField[*ast.StructLit](field, healthPolicy, validateHealthPolicyField); err != nil {
return err
}
if err := marshalField[*ast.StructLit](field, customStatus, validateCustomStatusField); err != nil {
return err
}
if err := marshalField[*ast.StructLit](field, status, validateStatusField); err != nil {
return err
}
return nil
@ -42,65 +50,75 @@ func EncodeMetadata(field *ast.Field) error {
// DecodeMetadata decodes a CUE string literal in the metadata fields to native CUE expressions
func DecodeMetadata(field *ast.Field) error {
if err := unmarshalStatusDetailsField(field); err != nil {
if err := unmarshalField[*ast.StructLit](field, healthPolicy, validateHealthPolicyField); err != nil {
return err
}
if err := unmarshalField[*ast.StructLit](field, customStatus, validateCustomStatusField); err != nil {
return err
}
if err := unmarshalField[*ast.StructLit](field, status, validateStatusField); err != nil {
return err
}
return nil
}
func marshalStatusDetailsField(field *ast.Field) error {
if statusField, ok := GetFieldByPath(field, status); ok {
func marshalField[T ast.Node](field *ast.Field, key string, validator func(T) error) error {
if statusField, ok := GetFieldByPath(field, key); ok {
switch expr := statusField.Value.(type) {
case *ast.BasicLit:
if expr.Kind != token.STRING {
return fmt.Errorf("expected status field to be string, got %v", expr.Kind)
return fmt.Errorf("expected %s field to be string, got %v", key, expr.Kind)
}
if err := ValidateCueStringLiteral[*ast.StructLit](expr, validateStatusField); err != nil {
return fmt.Errorf("status.details field failed validation: %w", err)
if err := ValidateCueStringLiteral[T](expr, validator); err != nil {
return fmt.Errorf("%s field failed validation: %w", key, err)
}
return nil
case *ast.StructLit:
v, _ := statusField.Value.(*ast.StructLit)
err := validateStatusField(v)
structLit := expr
v, ok := ast.Node(structLit).(T)
if !ok {
return fmt.Errorf("%s field: cannot convert *ast.StructLit to expected type", key)
}
err := validator(v)
if err != nil {
return err
}
strLit, err := StringifyStructLitAsCueString(v)
strLit, err := StringifyStructLitAsCueString(structLit)
if err != nil {
return err
}
UpdateNodeByPath(field, status, strLit)
UpdateNodeByPath(field, key, strLit)
return nil
default:
return fmt.Errorf("unexpected type for status field: %T", expr)
return fmt.Errorf("unexpected type for %s field: %T", key, expr)
}
}
return nil
}
func unmarshalStatusDetailsField(field *ast.Field) error {
if statusField, ok := GetFieldByPath(field, status); ok {
func unmarshalField[T ast.Node](field *ast.Field, key string, validator func(T) error) error {
if statusField, ok := GetFieldByPath(field, key); ok {
basicLit, ok := statusField.Value.(*ast.BasicLit)
if !ok || basicLit.Kind != token.STRING {
return fmt.Errorf("status.details field is not a string literal")
return fmt.Errorf("%s field is not a string literal", key)
}
err := ValidateCueStringLiteral[*ast.StructLit](basicLit, validateStatusField)
err := ValidateCueStringLiteral[T](basicLit, validator)
if err != nil {
return fmt.Errorf("status field failed validation: %w", err)
return fmt.Errorf("%s field failed validation: %w", key, err)
}
unquoted := strings.TrimSpace(TrimCueRawString(basicLit.Value))
expr, err := parser.ParseExpr("-", WrapCueStruct(unquoted))
if err != nil {
return fmt.Errorf("unexpected error re-parsing validated string: %w", err)
return fmt.Errorf("unexpected error re-parsing validated %s string: %w", key, err)
}
structLit, ok := expr.(*ast.StructLit)
if !ok {
return fmt.Errorf("expected struct after validation")
return fmt.Errorf("expected struct after validation in field %s", key)
}
statusField.Value = structLit
@ -134,3 +152,55 @@ func validateStatusField(sl *ast.StructLit) error {
}
return nil
}
func validateCustomStatusField(sl *ast.StructLit) error {
validator := func(expr ast.Expr) error {
switch v := expr.(type) {
case *ast.BasicLit:
if v.Kind != token.STRING {
return fmt.Errorf("customStatus field 'message' must be a string, got %v", v.Kind)
}
case *ast.Interpolation, *ast.CallExpr, *ast.SelectorExpr, *ast.Ident, *ast.BinaryExpr, *ast.ParenExpr,
*ast.ListLit, *ast.IndexExpr, *ast.SliceExpr, *ast.Comprehension:
default:
return fmt.Errorf("customStatus field 'message' must be a string expression, got %T", v)
}
return nil
}
found, err := FindAndValidateField(sl, "message", validator)
if err != nil {
return err
}
if !found {
return fmt.Errorf("customStatus must contain a 'message' field")
}
return nil
}
func validateHealthPolicyField(sl *ast.StructLit) error {
validator := func(expr ast.Expr) error {
switch v := expr.(type) {
case *ast.Ident:
case *ast.BasicLit:
if v.Kind != token.TRUE && v.Kind != token.FALSE {
return fmt.Errorf("healthPolicy field 'isHealth' must be a boolean literal (true/false), got %v", v.Kind)
}
case *ast.BinaryExpr, *ast.UnaryExpr, *ast.CallExpr, *ast.SelectorExpr, *ast.ParenExpr:
default:
return fmt.Errorf("healthPolicy field 'isHealth' must be a boolean expression, got %T", v)
}
return nil
}
found, err := FindAndValidateField(sl, "isHealth", validator)
if err != nil {
return err
}
if !found {
return fmt.Errorf("healthPolicy must contain an 'isHealth' field")
}
return nil
}

View File

@ -229,3 +229,891 @@ func TestMarshalAndUnmarshalMetadata(t *testing.T) {
})
}
}
func TestMarshalAndUnmarshalHealthPolicy(t *testing.T) {
tests := []struct {
name string
input string
expectMarshalErr string
expectUnmarshalErr string
expectContains string
}{
{
name: "valid healthPolicy with boolean literal",
input: `
attributes: {
status: {
healthPolicy: {
isHealth: true
}
}
}
`,
expectContains: "isHealth",
},
{
name: "valid healthPolicy with binary expression",
input: `
attributes: {
status: {
healthPolicy: {
isHealth: context.output.status.phase == "Running"
}
}
}
`,
expectContains: "isHealth",
},
{
name: "valid healthPolicy with complex expression",
input: `
attributes: {
status: {
healthPolicy: {
isHealth: context.output.status.ready && context.output.status.replicas > 0
}
}
}
`,
expectContains: "isHealth",
},
{
name: "valid healthPolicy with selector expression",
input: `
attributes: {
status: {
healthPolicy: {
isHealth: context.output.status.conditions[0].status
}
}
}
`,
expectContains: "isHealth",
},
{
name: "valid healthPolicy with call expression",
input: `
attributes: {
status: {
healthPolicy: {
isHealth: len(context.output.status.conditions) > 0
}
}
}
`,
expectContains: "isHealth",
},
{
name: "healthPolicy missing isHealth field",
input: `
attributes: {
status: {
healthPolicy: {
someOtherField: true
}
}
}
`,
expectMarshalErr: "healthPolicy must contain an 'isHealth' field",
},
{
name: "healthPolicy with invalid isHealth type (struct)",
input: `
attributes: {
status: {
healthPolicy: {
isHealth: { nested: true }
}
}
}
`,
expectMarshalErr: "healthPolicy field 'isHealth' must be a boolean expression",
},
{
name: "healthPolicy with invalid isHealth type (list)",
input: `
attributes: {
status: {
healthPolicy: {
isHealth: [true, false]
}
}
}
`,
expectMarshalErr: "healthPolicy field 'isHealth' must be a boolean expression",
},
{
name: "valid stringified healthPolicy round trip",
input: `
attributes: {
status: {
healthPolicy: #"""
isHealth: context.output.status.phase == "Running"
"""#
}
}
`,
expectContains: "isHealth",
},
{
name: "malformed stringified healthPolicy fails validation",
input: `
attributes: {
status: {
healthPolicy: #"""
invalid cue: abc
"""#
}
}
`,
expectMarshalErr: "invalid cue content in string literal",
},
{
name: "healthPolicy as plain string is valid",
input: `
attributes: {
status: {
healthPolicy: "isHealth: true"
}
}
`,
expectContains: "isHealth",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
file, err := parser.ParseFile("-", tt.input)
require.NoError(t, err)
var rootField *ast.Field
for _, decl := range file.Decls {
if f, ok := decl.(*ast.Field); ok {
rootField = f
break
}
}
require.NotNil(t, rootField)
err = EncodeMetadata(rootField)
if tt.expectMarshalErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.expectMarshalErr)
return
} else {
require.NoError(t, err)
}
err = DecodeMetadata(rootField)
if tt.expectUnmarshalErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.expectUnmarshalErr)
return
} else {
require.NoError(t, err)
}
if tt.expectContains != "" {
healthPolicyField, ok := GetFieldByPath(rootField, "attributes.status.healthPolicy")
require.True(t, ok)
switch v := healthPolicyField.Value.(type) {
case *ast.BasicLit:
require.Contains(t, v.Value, tt.expectContains)
case *ast.StructLit:
out, err := format.Node(v)
require.NoError(t, err)
require.Contains(t, string(out), tt.expectContains)
default:
t.Fatalf("unexpected healthPolicy value type: %T", v)
}
}
})
}
}
func TestMarshalAndUnmarshalCustomStatus(t *testing.T) {
tests := []struct {
name string
input string
expectMarshalErr string
expectUnmarshalErr string
expectContains string
}{
{
name: "valid customStatus with string message",
input: `
attributes: {
status: {
customStatus: {
message: "Service is healthy"
}
}
}
`,
expectContains: "message",
},
{
name: "valid customStatus with interpolation",
input: `
attributes: {
status: {
customStatus: {
message: "\(context.output.metadata.name) is running"
}
}
}
`,
expectContains: "message",
},
{
name: "valid customStatus with selector expression",
input: `
attributes: {
status: {
customStatus: {
message: context.output.status.message
}
}
}
`,
expectContains: "message",
},
{
name: "valid customStatus with binary expression",
input: `
attributes: {
status: {
customStatus: {
message: "Replicas: " + context.output.status.replicas
}
}
}
`,
expectContains: "message",
},
{
name: "valid customStatus with call expression",
input: `
attributes: {
status: {
customStatus: {
message: strconv.FormatInt(context.output.status.replicas, 10)
}
}
}
`,
expectContains: "message",
},
{
name: "valid customStatus with list expression",
input: `
attributes: {
status: {
customStatus: {
message: [for c in context.output.status.conditions if c.type == "Ready" { c.message }][0]
}
}
}
`,
expectContains: "message",
},
{
name: "customStatus missing message field",
input: `
attributes: {
status: {
customStatus: {
someOtherField: "value"
}
}
}
`,
expectMarshalErr: "customStatus must contain a 'message' field",
},
{
name: "customStatus with invalid message type (struct)",
input: `
attributes: {
status: {
customStatus: {
message: { nested: "value" }
}
}
}
`,
expectMarshalErr: "customStatus field 'message' must be a string expression",
},
{
name: "customStatus with integer literal message",
input: `
attributes: {
status: {
customStatus: {
message: 42
}
}
}
`,
expectMarshalErr: "customStatus field 'message' must be a string",
},
{
name: "valid stringified customStatus round trip",
input: `
attributes: {
status: {
customStatus: #"""
message: "Pod \(context.output.metadata.name) is running"
"""#
}
}
`,
expectContains: "message",
},
{
name: "malformed stringified customStatus fails validation",
input: `
attributes: {
status: {
customStatus: #"""
invalid cue: abc
"""#
}
}
`,
expectMarshalErr: "invalid cue content in string literal",
},
{
name: "customStatus with additional fields alongside message",
input: `
attributes: {
status: {
customStatus: {
message: "Service is healthy"
severity: "info"
timestamp: context.output.metadata.creationTimestamp
}
}
}
`,
expectContains: "message",
},
{
name: "customStatus as plain string is valid",
input: `
attributes: {
status: {
customStatus: "message: \"Hello\""
}
}
`,
expectContains: "message",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
file, err := parser.ParseFile("-", tt.input)
require.NoError(t, err)
var rootField *ast.Field
for _, decl := range file.Decls {
if f, ok := decl.(*ast.Field); ok {
rootField = f
break
}
}
require.NotNil(t, rootField)
err = EncodeMetadata(rootField)
if tt.expectMarshalErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.expectMarshalErr)
return
} else {
require.NoError(t, err)
}
err = DecodeMetadata(rootField)
if tt.expectUnmarshalErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.expectUnmarshalErr)
return
} else {
require.NoError(t, err)
}
if tt.expectContains != "" {
customStatusField, ok := GetFieldByPath(rootField, "attributes.status.customStatus")
require.True(t, ok)
switch v := customStatusField.Value.(type) {
case *ast.BasicLit:
require.Contains(t, v.Value, tt.expectContains)
case *ast.StructLit:
out, err := format.Node(v)
require.NoError(t, err)
require.Contains(t, string(out), tt.expectContains)
default:
t.Fatalf("unexpected customStatus value type: %T", v)
}
}
})
}
}
func TestHealthPolicyEdgeCases(t *testing.T) {
tests := []struct {
name string
input string
expectMarshalErr string
expectUnmarshalErr string
}{
{
name: "healthPolicy with unary expression",
input: `
attributes: {
status: {
healthPolicy: {
isHealth: !context.output.status.failed
}
}
}
`,
},
{
name: "healthPolicy with parenthesized expression",
input: `
attributes: {
status: {
healthPolicy: {
isHealth: (context.output.status.phase == "Running" || context.output.status.phase == "Succeeded")
}
}
}
`,
},
{
name: "healthPolicy with nested binary expressions",
input: `
attributes: {
status: {
healthPolicy: {
isHealth: context.output.status.ready && (context.output.status.replicas > 0 || context.output.status.phase == "Ready")
}
}
}
`,
},
{
name: "healthPolicy with string literal should fail",
input: `
attributes: {
status: {
healthPolicy: {
isHealth: "true"
}
}
}
`,
expectMarshalErr: "healthPolicy field 'isHealth' must be a boolean literal (true/false)",
},
{
name: "healthPolicy with comprehension should fail",
input: `
attributes: {
status: {
healthPolicy: {
isHealth: [for c in context.output.status.conditions if c.type == "Ready" { c.status }][0]
}
}
}
`,
expectMarshalErr: "healthPolicy field 'isHealth' must be a boolean expression",
},
{
name: "healthPolicy with empty struct should fail",
input: `
attributes: {
status: {
healthPolicy: {}
}
}
`,
expectMarshalErr: "healthPolicy must contain an 'isHealth' field",
},
{
name: "healthPolicy with additional fields is allowed",
input: `
attributes: {
status: {
healthPolicy: {
isHealth: true
reason: "always healthy"
}
}
}
`,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
file, err := parser.ParseFile("-", tt.input)
require.NoError(t, err)
var rootField *ast.Field
for _, decl := range file.Decls {
if f, ok := decl.(*ast.Field); ok {
rootField = f
break
}
}
require.NotNil(t, rootField)
err = EncodeMetadata(rootField)
if tt.expectMarshalErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.expectMarshalErr)
return
} else {
require.NoError(t, err)
}
err = DecodeMetadata(rootField)
if tt.expectUnmarshalErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.expectUnmarshalErr)
return
} else {
require.NoError(t, err)
}
})
}
}
func TestCustomStatusEdgeCases(t *testing.T) {
tests := []struct {
name string
input string
expectMarshalErr string
expectUnmarshalErr string
}{
{
name: "customStatus with comprehension expression",
input: `
attributes: {
status: {
customStatus: {
message: [for i, v in context.output.status.conditions { "\(i): \(v.message)" }][0]
}
}
}
`,
},
{
name: "customStatus with index expression",
input: `
attributes: {
status: {
customStatus: {
message: context.output.status.conditions[0].message
}
}
}
`,
},
{
name: "customStatus with slice expression",
input: `
attributes: {
status: {
customStatus: {
message: context.output.status.message[0:10]
}
}
}
`,
},
{
name: "customStatus with parenthesized expression",
input: `
attributes: {
status: {
customStatus: {
message: ("Status: " + context.output.status.phase)
}
}
}
`,
},
{
name: "customStatus with nested interpolation",
input: `
attributes: {
status: {
customStatus: {
message: "Pod \(context.output.metadata.name) has \(context.output.status.replicas) replicas"
}
}
}
`,
},
{
name: "customStatus with boolean literal should fail",
input: `
attributes: {
status: {
customStatus: {
message: true
}
}
}
`,
expectMarshalErr: "customStatus field 'message' must be a string",
},
{
name: "customStatus with empty struct should fail",
input: `
attributes: {
status: {
customStatus: {}
}
}
`,
expectMarshalErr: "customStatus must contain a 'message' field",
},
{
name: "customStatus with only non-message fields should fail",
input: `
attributes: {
status: {
customStatus: {
severity: "error"
code: 500
}
}
}
`,
expectMarshalErr: "customStatus must contain a 'message' field",
},
{
name: "customStatus with field expression should fail",
input: `
attributes: {
status: {
customStatus: {
message: { template: "Hello" }
}
}
}
`,
expectMarshalErr: "customStatus field 'message' must be a string expression",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
file, err := parser.ParseFile("-", tt.input)
require.NoError(t, err)
var rootField *ast.Field
for _, decl := range file.Decls {
if f, ok := decl.(*ast.Field); ok {
rootField = f
break
}
}
require.NotNil(t, rootField)
err = EncodeMetadata(rootField)
if tt.expectMarshalErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.expectMarshalErr)
return
} else {
require.NoError(t, err)
}
err = DecodeMetadata(rootField)
if tt.expectUnmarshalErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.expectUnmarshalErr)
return
} else {
require.NoError(t, err)
}
})
}
}
func TestBackwardCompatibility(t *testing.T) {
tests := []struct {
name string
input string
}{
{
name: "existing worker component healthPolicy format",
input: `
attributes: {
status: {
healthPolicy: #"""
isHealth: context.output.status.readyReplicas > 0 && context.output.status.readyReplicas == context.output.status.replicas
"""#
}
}
`,
},
{
name: "existing worker component customStatus format",
input: `
attributes: {
status: {
customStatus: #"""
appName: context.appName
internal: "\($appName) is running"
exposeType: *"" | string
if context.outputs.service != _|_ {
exposeType: context.outputs.service.spec.type
}
if exposeType == "ClusterIP" {
message: "\(appName) has ClusterIP service"
}
if exposeType == "NodePort" {
message: "\(appName) has NodePort service"
}
if exposeType == "LoadBalancer" {
message: "\(appName) has LoadBalancer service"
}
if exposeType == "" {
message: internal
}
"""#
}
}
`,
},
{
name: "complex multi-field definition with both healthPolicy and customStatus",
input: `
attributes: {
status: {
healthPolicy: #"""
isHealth: context.output.status.phase == "Running" || (context.output.status.phase == "Succeeded" && context.output.spec.restartPolicy == "Never")
"""#
customStatus: #"""
ready: [ for c in context.output.status.conditions if c.type == "Ready" { c.status }][0] == "True"
message: "Pod \(context.output.metadata.name): phase=\(context.output.status.phase), ready=\(ready)"
"""#
}
}
`,
},
{
name: "simple string format for healthPolicy",
input: `
attributes: {
status: {
healthPolicy: "isHealth: true"
}
}
`,
},
{
name: "simple string format for customStatus",
input: `
attributes: {
status: {
customStatus: "message: \"Service is healthy\""
}
}
`,
},
{
name: "healthPolicy with list comprehensions and complex conditions",
input: `
attributes: {
status: {
healthPolicy: #"""
conditions: [ for c in context.output.status.conditions if c.type == "Ready" || c.type == "ContainersReady" { c.status }]
isHealth: len($conditions) > 0 && ![ for c in conditions if c != "True" { c }] != []
"""#
}
}
`,
},
{
name: "customStatus with nested conditionals and string interpolation",
input: `
attributes: {
status: {
customStatus: #"""
phase: context.output.status.phase
replicas: context.output.status.replicas
readyReplicas: *0 | int
if context.output.status.readyReplicas != _|_ {
readyReplicas: context.output.status.readyReplicas
}
if phase == "Running" {
if readyReplicas == replicas {
message: "All \(replicas) replicas are ready"
}
if readyReplicas < replicas {
message: "Only \(readyReplicas) of \(replicas) replicas are ready"
}
}
if phase != "Running" {
message: "Deployment is in phase: \(phase)"
}
"""#
}
}
`,
},
{
name: "preserving local fields that start with $",
input: `
attributes: {
status: {
customStatus: #"""
internal: "internal state"
compute: context.output.status.replicas * 2
debugInfo: {
phase: context.output.status.phase
replicas: context.output.status.replicas
}
message: "Status: \(internal), computed: \(compute)"
"""#
}
}
`,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
file, err := parser.ParseFile("-", tt.input)
require.NoError(t, err)
var rootField *ast.Field
for _, decl := range file.Decls {
if f, ok := decl.(*ast.Field); ok {
rootField = f
break
}
}
require.NotNil(t, rootField)
err = EncodeMetadata(rootField)
require.NoError(t, err)
err = DecodeMetadata(rootField)
require.NoError(t, err)
})
}
}

View File

@ -104,50 +104,40 @@ func StringifyStructLitAsCueString(structLit *ast.StructLit) (*ast.BasicLit, err
}, nil
}
formatted, err := format.Node(structLit)
if err != nil {
return nil, fmt.Errorf("failed to format struct: %w", err)
}
content := string(formatted)
content = strings.TrimSpace(content)
if strings.HasPrefix(content, "{") && strings.HasSuffix(content, "}") {
content = strings.TrimPrefix(content, "{")
content = strings.TrimSuffix(content, "}")
content = strings.Trim(content, "\n")
}
if content == "" {
return &ast.BasicLit{
Kind: token.STRING,
Value: `"{}"`,
}, nil
}
lines := strings.Split(content, "\n")
var sb strings.Builder
sb.WriteString(`#"""`)
sb.WriteString("\n")
for _, elt := range structLit.Elts {
field, ok := elt.(*ast.Field)
if !ok {
continue
}
var labelStr, valueStr string
switch l := field.Label.(type) {
case *ast.Ident:
labelStr = l.Name
case *ast.BasicLit:
labelStr = strings.Trim(l.Value, `"`)
default:
labelStr = "<unknown>"
}
for _, attr := range field.Attrs {
sb.WriteString(" " + attr.Text + "\n")
}
b, err := format.Node(field.Value)
if err != nil {
valueStr = "<complex>"
} else {
lines := strings.Split(string(b), "\n")
for i := range lines {
lines[i] = " " + lines[i]
}
valueStr = strings.Join(lines, "\n")
}
sb.WriteString(fmt.Sprintf(" %s: %s\n", labelStr, valueStr))
}
sb.WriteString(strings.Join(lines, "\n"))
sb.WriteString("\n")
sb.WriteString(`"""#`)
val := strings.ReplaceAll(sb.String(), "\t", " ")
result := strings.ReplaceAll(sb.String(), "\t", " ")
return &ast.BasicLit{
Kind: token.STRING,
Value: val,
Value: result,
}, nil
}
@ -177,20 +167,26 @@ func ValidateCueStringLiteral[T ast.Node](lit *ast.BasicLit, validator func(T) e
return validator(node)
}
// TrimCueRawString trims a CUE raw string literal
// TrimCueRawString trims a CUE raw string literal and handles escape sequences
func TrimCueRawString(s string) string {
s = strings.TrimSpace(s)
if strings.HasPrefix(s, `#"""`) && strings.HasSuffix(s, `"""#`) {
return strings.TrimSuffix(strings.TrimPrefix(s, `#"""`), `"""#`)
switch {
case strings.HasPrefix(s, `#"""`) && strings.HasSuffix(s, `"""#`):
s = strings.TrimSuffix(strings.TrimPrefix(s, `#"""`), `"""#`)
case strings.HasPrefix(s, `"""`) && strings.HasSuffix(s, `"""`):
s = strings.TrimSuffix(strings.TrimPrefix(s, `"""`), `"""`)
default:
fallback, err := strconv.Unquote(s)
if err == nil {
s = fallback
}
}
if strings.HasPrefix(s, `"""`) && strings.HasSuffix(s, `"""`) {
return strings.TrimSuffix(strings.TrimPrefix(s, `"""`), `"""`)
}
fallback, err := strconv.Unquote(s)
if err != nil {
return s
}
return fallback
// Handle escape sequences for backward compatibility with existing definitions
s = strings.ReplaceAll(s, "\\t", " ")
s = strings.ReplaceAll(s, "\\\\", "\\")
return s
}
// WrapCueStruct wraps a string in a CUE struct format
@ -198,6 +194,48 @@ func WrapCueStruct(s string) string {
return fmt.Sprintf("{\n%s\n}", s)
}
// FindAndValidateField searches for a field at the top level or within top-level if statements
func FindAndValidateField(sl *ast.StructLit, fieldName string, validator fieldValidator) (found bool, err error) {
// First check top-level fields
for _, elt := range sl.Elts {
if field, ok := elt.(*ast.Field); ok {
label := GetFieldLabel(field.Label)
if label == fieldName {
found = true
if validator != nil {
err = validator(field.Value)
}
return found, err
}
}
}
// If not found at top level, check within top-level if statements
for _, elt := range sl.Elts {
if comp, ok := elt.(*ast.Comprehension); ok {
// Check if this comprehension has if clauses (conditional fields)
hasIfClause := false
for _, clause := range comp.Clauses {
if _, ok := clause.(*ast.IfClause); ok {
hasIfClause = true
break
}
}
// If it has an if clause and the value is a struct, search within it
if hasIfClause {
if structLit, ok := comp.Value.(*ast.StructLit); ok {
if innerFound, innerErr := FindAndValidateField(structLit, fieldName, validator); innerFound {
return true, innerErr
}
}
}
}
}
return found, err
}
func lookupTopLevelField(node ast.Node, key string) (*ast.Field, ast.Expr, bool) {
switch n := node.(type) {
case *ast.Field:
@ -220,6 +258,9 @@ func lookupTopLevelField(node ast.Node, key string) (*ast.Field, ast.Expr, bool)
return nil, nil, false
}
// fieldValidator is a function that validates a field's value
type fieldValidator func(ast.Expr) error
func traversePath(val ast.Expr, pathParts []string, lastField *ast.Field) (ast.Node, *ast.Field, bool) {
currentField := lastField
currentVal := val

View File

@ -281,6 +281,25 @@ func TestStringifyStructLitAsCueString(t *testing.T) {
}`,
shouldFail: true,
},
{
name: "Conditional fields with if statements",
input: `
{
if context.status.healthy {
message: "Healthy! (\(context.status.details.replicaReadyRatio * 100)% pods running)"
}
if !context.status.healthy {
message: "Unhealthy! (\(context.status.details.replicaReadyRatio * 100)% pods running)"
}
}`,
contains: []string{
`if context.status.healthy {`,
`message: "Healthy! (\(context.status.details.replicaReadyRatio*100)% pods running)"`,
`if !context.status.healthy {`,
`message: "Unhealthy! (\(context.status.details.replicaReadyRatio*100)% pods running)"`,
},
},
}
for _, tt := range tests {
@ -536,3 +555,270 @@ func TestWrapCueStruct(t *testing.T) {
})
}
}
func TestFindAndValidateField(t *testing.T) {
tests := []struct {
name string
input string
fieldName string
expectedFound bool
expectedErrMessage string
validator fieldValidator
}{
{
name: "find field at top level without validator",
input: `{
field1: "value1"
field2: "value2"
}`,
fieldName: "field1",
expectedFound: true,
},
{
name: "field not found at any level",
input: `{
field1: "value1"
field2: "value2"
}`,
fieldName: "field3",
expectedFound: false,
},
{
name: "find field at top level with validator that passes",
input: `{
message: "hello world"
other: "value"
}`,
fieldName: "message",
expectedFound: true,
validator: func(expr ast.Expr) error {
if basicLit, ok := expr.(*ast.BasicLit); ok {
if basicLit.Value != `"hello world"` {
return fmt.Errorf("expected hello world, got %s", basicLit.Value)
}
}
return nil
},
},
{
name: "find field at top level with validator that fails",
input: `{
message: "hello world"
other: "value"
}`,
fieldName: "message",
expectedFound: true,
expectedErrMessage: "expected goodbye, got",
validator: func(expr ast.Expr) error {
if basicLit, ok := expr.(*ast.BasicLit); ok {
if basicLit.Value != `"goodbye"` {
return fmt.Errorf("expected goodbye, got %s", basicLit.Value)
}
}
return nil
},
},
{
name: "find field in simple if statement",
input: `{
otherField: "value"
if condition {
message: "found in if"
}
}`,
fieldName: "message",
expectedFound: true,
},
{
name: "find field in nested if statements",
input: `{
otherField: "value"
if condition1 {
if condition2 {
message: "deeply nested"
}
}
}`,
fieldName: "message",
expectedFound: true,
},
{
name: "find field in if-else chain",
input: `{
status: "running"
if status == "running" {
message: "service is running"
}
if status == "stopped" {
message: "service is stopped"
}
}`,
fieldName: "message",
expectedFound: true,
},
{
name: "find field in complex nested conditionals",
input: `{
phase: context.output.status.phase
replicas: context.output.status.replicas
readyReplicas: *0 | int
if context.output.status.readyReplicas != _|_ {
readyReplicas: context.output.status.readyReplicas
}
if phase == "Running" {
if readyReplicas == replicas {
message: "All replicas are ready"
}
if readyReplicas < replicas {
message: "Some replicas are not ready"
}
}
if phase != "Running" {
message: "Deployment is not running"
}
}`,
fieldName: "message",
expectedFound: true,
},
{
name: "find field in if with validator",
input: `{
condition: true
if condition {
isHealth: true
}
}`,
fieldName: "isHealth",
expectedFound: true,
validator: func(expr ast.Expr) error {
if basicLit, ok := expr.(*ast.BasicLit); ok {
if basicLit.Value != "true" {
return fmt.Errorf("expected true, got %s", basicLit.Value)
}
}
return nil
},
},
{
name: "find field in nested if with failing validator",
input: `{
condition: true
if condition {
if nestedCondition {
isHealth: false
}
}
}`,
fieldName: "isHealth",
expectedFound: true,
expectedErrMessage: "expected true, got",
validator: func(expr ast.Expr) error {
if basicLit, ok := expr.(*ast.BasicLit); ok {
if basicLit.Value != "true" {
return fmt.Errorf("expected true, got %s", basicLit.Value)
}
}
return nil
},
},
{
name: "field not found in comprehension without if clause",
input: `{
items: [for x in list { value: x }]
}`,
fieldName: "message",
expectedFound: false,
},
{
name: "find field with complex expressions in if",
input: `{
replicas: context.output.status.replicas
readyReplicas: context.output.status.readyReplicas
if (replicas | *0) != (readyReplicas | *0) {
message: "not ready"
}
}`,
fieldName: "message",
expectedFound: true,
},
{
name: "find field with quoted label in if statement",
input: `{
condition: true
if condition {
"field-with-dash": "value"
}
}`,
fieldName: "field-with-dash",
expectedFound: true,
},
{
name: "find field in multiple nested levels",
input: `{
level1: "value"
if condition1 {
level2: "value"
if condition2 {
level3: "value"
if condition3 {
message: "deeply nested field"
}
}
}
}`,
fieldName: "message",
expectedFound: true,
},
{
name: "empty struct with if statements",
input: `{
if condition {
}
}`,
fieldName: "message",
expectedFound: false,
},
{
name: "field exists in both top level and if statement (finds top level first)",
input: `{
message: "top level"
if condition {
message: "in if"
}
}`,
fieldName: "message",
expectedFound: true,
validator: func(expr ast.Expr) error {
if basicLit, ok := expr.(*ast.BasicLit); ok {
if basicLit.Value == `"top level"` {
return nil
}
return fmt.Errorf("expected top level message, got %s", basicLit.Value)
}
return fmt.Errorf("expected string literal")
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
expr, err := parser.ParseExpr("-", tt.input)
require.NoError(t, err)
structLit, ok := expr.(*ast.StructLit)
require.True(t, ok, "input should parse as struct literal")
found, err := FindAndValidateField(structLit, tt.fieldName, tt.validator)
require.Equal(t, tt.expectedFound, found, "found result should match expected")
if tt.expectedErrMessage != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.expectedErrMessage)
} else {
require.NoError(t, err)
}
})
}
}

View File

@ -280,13 +280,13 @@ func TestCueNativeStatusFromCueString(t *testing.T) {
}
}
status: {
customStatus: #"""
customStatus: {
message: "\(context.output.status.readyReplicas) / \(context.output.status.replicas) replicas are ready"
"""#
}
healthPolicy: #"""
healthPolicy: {
isHealth: context.output.status.readyReplicas == context.output.status.replicas
"""#
}
details: {
$temp: context.output.status.replicas

View File

@ -29,11 +29,14 @@ import (
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
pkgruntime "k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
pkgdef "github.com/oam-dev/kubevela/pkg/definition"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/util"
utilcommon "github.com/oam-dev/kubevela/pkg/utils/common"
@ -260,4 +263,90 @@ var _ = Describe("ComponentDefinition Normal tests", func() {
By("Verify application is running")
verifyApplicationPhase(context.TODO(), newApp.Namespace, newApp.Name, common.ApplicationRunning)
})
Context("Definition Retrieval and CUE Parsing Validation", func() {
It("should successfully parse all definitions loaded from helm chart templates", func() {
By("Loading all definition YAML files from charts/vela-core/templates/defwithtemplate")
_, file, _, _ := runtime.Caller(0)
definitionDir := filepath.Join(file, "../../../charts/vela-core/templates/defwithtemplate")
files, err := filepath.Glob(filepath.Join(definitionDir, "*.yaml"))
Expect(err).To(BeNil())
Expect(len(files)).To(BeNumerically(">", 0))
By(fmt.Sprintf("Found %d definition YAML files to test", len(files)))
// Install all definitions
installedDefinitions := []string{}
for _, definitionFile := range files {
definitionName := strings.TrimSuffix(filepath.Base(definitionFile), ".yaml")
By(fmt.Sprintf("Installing definition from %s", definitionName))
err := testdef.InstallDefinitionFromYAML(ctx, k8sClient, definitionFile, func(s string) string {
// Replace helm template placeholders like the existing notification test
s = strings.ReplaceAll(s, `{{ include "systemDefinitionNamespace" . }}`, namespace)
s = strings.ReplaceAll(s, `{{- include "systemDefinitionNamespace" . }}`, namespace)
return s
})
if err != nil {
// Some definitions might fail to install due to dependencies, that's ok
fmt.Printf("Warning: Failed to install definition from %s: %v\n", definitionFile, err)
} else {
installedDefinitions = append(installedDefinitions, definitionName)
}
}
By(fmt.Sprintf("Successfully installed %d definitions, now testing CUE parsing", len(installedDefinitions)))
// Test CUE parsing on all installed ComponentDefinitions
By("Testing CUE parsing for ComponentDefinitions")
var componentDefs v1beta1.ComponentDefinitionList
Expect(k8sClient.List(ctx, &componentDefs, &client.ListOptions{Namespace: namespace})).Should(Succeed())
componentErrorCount := 0
for i := range componentDefs.Items {
unstructuredObj, err := pkgruntime.DefaultUnstructuredConverter.ToUnstructured(&componentDefs.Items[i])
if err != nil {
componentErrorCount++
fmt.Printf("ERROR: ComponentDefinition %s failed to convert to unstructured: %v\n", componentDefs.Items[i].Name, err)
continue
}
def := &pkgdef.Definition{Unstructured: unstructured.Unstructured{Object: unstructuredObj}}
_, err = def.ToCUEString()
if err != nil {
componentErrorCount++
fmt.Printf("ERROR: ComponentDefinition %s failed CUE parsing: %v\n", componentDefs.Items[i].Name, err)
}
}
// Test CUE parsing on all installed TraitDefinitions
By("Testing CUE parsing for TraitDefinitions")
var traitDefs v1beta1.TraitDefinitionList
Expect(k8sClient.List(ctx, &traitDefs, &client.ListOptions{Namespace: namespace})).Should(Succeed())
traitErrorCount := 0
for i := range traitDefs.Items {
unstructuredObj, err := pkgruntime.DefaultUnstructuredConverter.ToUnstructured(&traitDefs.Items[i])
if err != nil {
traitErrorCount++
fmt.Printf("ERROR: TraitDefinition %s failed to convert to unstructured: %v\n", traitDefs.Items[i].Name, err)
continue
}
def := &pkgdef.Definition{Unstructured: unstructured.Unstructured{Object: unstructuredObj}}
_, err = def.ToCUEString()
if err != nil {
traitErrorCount++
fmt.Printf("ERROR: TraitDefinition %s failed CUE parsing: %v\n", traitDefs.Items[i].Name, err)
}
}
By(fmt.Sprintf("CUE parsing results: %d ComponentDefinitions tested, %d TraitDefinitions tested",
len(componentDefs.Items), len(traitDefs.Items)))
totalErrors := componentErrorCount + traitErrorCount
Expect(totalErrors).To(Equal(0), fmt.Sprintf("%d definitions failed CUE parsing with updated logic", totalErrors))
})
})
})