mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-18 03:56:36 +00:00
Chore: fix trait definition translation discrepancies (#7044)
* Fix: Enhance CUE generation for optional fields in collection operations Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Feat: Add tests for optional fields and conditional logic in trait definitions Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Add namespace field to podAffinityTerm in affinity trait definition Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Add support for ForEachMap operation in CUE generation Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Add tests for ForEachMap let bindings and custom rendering in trait definitions Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Add optional description fields for PatchContainer and enhance parameter generation logic Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Update parameter access to bracket notation in CUE generation and tests Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Implement PatchStrategyAnnotation for CUE generation and enhance condition hoisting logic Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Add SpreadAll operation for array patches and enhance IntParam constraints handling Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Refactor writeSpreadAllOp to improve condition handling and element processing Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Correct nodeSelector spelling and references in affinity trait definition Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Add optional namespace field to podAffinityTerm in affinity trait definition Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * test: re-trigger test pipelines Signed-off-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com> Co-authored-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com> * ci: retrigger checks Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> Co-authored-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com> * Fix: Update workflow API import paths to use the correct package location Signed-off-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com> Co-authored-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Update dependency versions in go.mod for improved compatibility Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> Co-authored-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com> * ci: retrigger checks Signed-off-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com> Co-authored-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Add tests for SpreadAll operation and conditional handling in trait definitions Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Enhance optional field handling for non-string conditions in PatchContainer Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Implement ForceOptional parameter handling to retain optionality with defaults Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Add PatchStrategy field to PatchContainerConfig and update related logic in trait definitions Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Implement PatchFieldBuilder for fluent API construction of PatchContainerField Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Update test descriptions and enhance CUE generation for PatchFields with NotEmpty and IsSet conditions Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> --------- Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> Signed-off-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com> Co-authored-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com>
This commit is contained in:
co-authored by
Chaitanya Reddy Onteddu
Ayush Kumar
parent
3cd345c243
commit
4010da6765
@@ -131,20 +131,21 @@ spec:
|
||||
|
||||
#podAffinityTerm: {
|
||||
labelSelector?: #labelSelector
|
||||
namespace?: string
|
||||
namespaces?: [...string]
|
||||
topologyKey: string
|
||||
namespaceSelector?: #labelSelector
|
||||
}
|
||||
|
||||
#nodeSelecor: {
|
||||
#nodeSelector: {
|
||||
key: string
|
||||
operator: *"In" | "NotIn" | "Exists" | "DoesNotExist" | "Gt" | "Lt"
|
||||
values?: [...string]
|
||||
}
|
||||
|
||||
#nodeSelectorTerm: {
|
||||
matchExpressions?: [...#nodeSelecor]
|
||||
matchFields?: [...#nodeSelecor]
|
||||
matchExpressions?: [...#nodeSelector]
|
||||
matchFields?: [...#nodeSelector]
|
||||
}
|
||||
|
||||
parameter: {
|
||||
|
||||
+111
-21
@@ -417,6 +417,20 @@ func (g *CUEGenerator) writeHelperDefFromParam(sb *strings.Builder, param Param,
|
||||
} else {
|
||||
sb.WriteString("[...]\n")
|
||||
}
|
||||
case *IntParam:
|
||||
// For int types with optional constraints: int & >=1 & <=65535
|
||||
var constraints []string
|
||||
if minVal := p.GetMin(); minVal != nil {
|
||||
constraints = append(constraints, fmt.Sprintf(">=%d", *minVal))
|
||||
}
|
||||
if maxVal := p.GetMax(); maxVal != nil {
|
||||
constraints = append(constraints, fmt.Sprintf("<=%d", *maxVal))
|
||||
}
|
||||
if len(constraints) > 0 {
|
||||
sb.WriteString(fmt.Sprintf("int & %s\n", strings.Join(constraints, " & ")))
|
||||
} else {
|
||||
sb.WriteString("int\n")
|
||||
}
|
||||
default:
|
||||
// Fallback for other types
|
||||
sb.WriteString("_\n")
|
||||
@@ -459,9 +473,10 @@ func (g *CUEGenerator) writeStructFieldForHelper(sb *strings.Builder, f *StructF
|
||||
return
|
||||
}
|
||||
|
||||
// Check for nested struct
|
||||
// Check for nested struct (or array of structs)
|
||||
if nested := f.GetNested(); nested != nil {
|
||||
if f.FieldType() == ParamTypeArray {
|
||||
// Array of structs: [...{fields}]
|
||||
sb.WriteString(fmt.Sprintf("%s%s%s: [...{\n", indent, name, optional))
|
||||
for _, nestedField := range nested.GetFields() {
|
||||
g.writeStructFieldForHelper(sb, nestedField, depth+1)
|
||||
@@ -906,15 +921,17 @@ func (g *CUEGenerator) writeResourceOutput(sb *strings.Builder, name string, res
|
||||
|
||||
// fieldNode represents a node in the field tree being built.
|
||||
type fieldNode struct {
|
||||
value Value // Direct value (if leaf)
|
||||
cond Condition // Condition for this field
|
||||
children map[string]*fieldNode
|
||||
childOrder []string // Track insertion order
|
||||
isArray bool
|
||||
arrayIndex int
|
||||
spreads []spreadEntry // Spread operations at this node level
|
||||
forEach *ForEachOp // ForEach operation (for trait patches)
|
||||
patchKey *PatchKeyOp // PatchKey operation (for array patches with merge key)
|
||||
value Value // Direct value (if leaf)
|
||||
cond Condition // Condition for this field
|
||||
children map[string]*fieldNode
|
||||
childOrder []string // Track insertion order
|
||||
isArray bool
|
||||
arrayIndex int
|
||||
spreads []spreadEntry // Spread operations at this node level
|
||||
forEach *ForEachOp // ForEach operation (for trait patches)
|
||||
patchKey *PatchKeyOp // PatchKey operation (for array patches with merge key)
|
||||
spreadAll *SpreadAllOp // SpreadAll operation (for array constraint patches)
|
||||
patchStrategy string // e.g. "retainKeys" → generates // +patchStrategy=retainKeys
|
||||
}
|
||||
|
||||
// spreadEntry represents a conditional spread operation.
|
||||
@@ -948,6 +965,11 @@ func (g *CUEGenerator) buildFieldTree(ops []ResourceOp) *fieldNode {
|
||||
case *PatchKeyOp:
|
||||
// PatchKeyOp creates an array patch with merge key annotation
|
||||
g.insertPatchKeyIntoTree(root, o, nil)
|
||||
case *SpreadAllOp:
|
||||
// SpreadAllOp constrains all array elements
|
||||
g.insertSpreadAllIntoTree(root, o, nil)
|
||||
case *PatchStrategyAnnotationOp:
|
||||
g.insertAnnotationIntoTree(root, o.Path(), o.Strategy())
|
||||
case *IfBlock:
|
||||
// For if blocks, process inner ops with the block's condition
|
||||
for _, innerOp := range o.Ops() {
|
||||
@@ -968,6 +990,11 @@ func (g *CUEGenerator) buildFieldTree(ops []ResourceOp) *fieldNode {
|
||||
case *PatchKeyOp:
|
||||
// PatchKey inside an if block - pass the block's condition
|
||||
g.insertPatchKeyIntoTree(root, inner, o.Cond())
|
||||
case *SpreadAllOp:
|
||||
// SpreadAll inside an if block - pass the block's condition
|
||||
g.insertSpreadAllIntoTree(root, inner, o.Cond())
|
||||
case *PatchStrategyAnnotationOp:
|
||||
g.insertAnnotationIntoTree(root, inner.Path(), inner.Strategy())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -976,6 +1003,20 @@ func (g *CUEGenerator) buildFieldTree(ops []ResourceOp) *fieldNode {
|
||||
return root
|
||||
}
|
||||
|
||||
// insertAnnotationIntoTree navigates to a node by path and sets its patchStrategy annotation.
|
||||
func (g *CUEGenerator) insertAnnotationIntoTree(root *fieldNode, path string, strategy string) {
|
||||
parts := splitPath(path)
|
||||
current := root
|
||||
for _, part := range parts {
|
||||
if _, exists := current.children[part]; !exists {
|
||||
current.children[part] = newFieldNode()
|
||||
current.childOrder = append(current.childOrder, part)
|
||||
}
|
||||
current = current.children[part]
|
||||
}
|
||||
current.patchStrategy = strategy
|
||||
}
|
||||
|
||||
// insertIntoTree inserts a value at a path into the field tree.
|
||||
func (g *CUEGenerator) insertIntoTree(root *fieldNode, path string, value Value, cond Condition) {
|
||||
parts := splitPath(path)
|
||||
@@ -1159,6 +1200,47 @@ func (g *CUEGenerator) insertPatchKeyIntoTree(root *fieldNode, op *PatchKeyOp, c
|
||||
current.cond = cond
|
||||
}
|
||||
|
||||
// insertSpreadAllIntoTree inserts a SpreadAllOp into the field tree.
|
||||
// This navigates to the target path and sets the spreadAll field.
|
||||
func (g *CUEGenerator) insertSpreadAllIntoTree(root *fieldNode, op *SpreadAllOp, cond Condition) {
|
||||
parts := splitPath(op.Path())
|
||||
current := root
|
||||
|
||||
for _, part := range parts {
|
||||
name, key, index := parseBracketAccess(part)
|
||||
|
||||
if _, exists := current.children[name]; !exists {
|
||||
current.children[name] = newFieldNode()
|
||||
current.childOrder = append(current.childOrder, name)
|
||||
}
|
||||
node := current.children[name]
|
||||
|
||||
switch {
|
||||
case index >= 0:
|
||||
node.isArray = true
|
||||
idxKey := fmt.Sprintf("[%d]", index)
|
||||
if _, exists := node.children[idxKey]; !exists {
|
||||
node.children[idxKey] = newFieldNode()
|
||||
node.children[idxKey].arrayIndex = index
|
||||
node.childOrder = append(node.childOrder, idxKey)
|
||||
}
|
||||
current = node.children[idxKey]
|
||||
case key != "":
|
||||
keyNode := fmt.Sprintf("[%s]", key)
|
||||
if _, exists := node.children[keyNode]; !exists {
|
||||
node.children[keyNode] = newFieldNode()
|
||||
node.childOrder = append(node.childOrder, keyNode)
|
||||
}
|
||||
current = node.children[keyNode]
|
||||
default:
|
||||
current = node
|
||||
}
|
||||
}
|
||||
|
||||
current.spreadAll = op
|
||||
current.cond = cond
|
||||
}
|
||||
|
||||
// writeFieldTree writes the field tree as CUE syntax.
|
||||
func (g *CUEGenerator) writeFieldTree(sb *strings.Builder, node *fieldNode, depth int) {
|
||||
indent := strings.Repeat(g.indent, depth)
|
||||
@@ -1689,8 +1771,14 @@ func (g *CUEGenerator) collectionOpToCUE(col *CollectionOp) string {
|
||||
for _, op := range ops {
|
||||
if mOp, ok := op.(*mapOp); ok {
|
||||
for fieldName, fieldVal := range mOp.mappings {
|
||||
valStr := g.fieldValueToCUE(fieldVal)
|
||||
sb.WriteString(fmt.Sprintf("\t\t\t\t\t%s: %s\n", fieldName, valStr))
|
||||
if optField, isOptional := fieldVal.(*OptionalField); isOptional {
|
||||
sb.WriteString(fmt.Sprintf("\t\t\t\t\tif v.%s != _|_ {\n", optField.field))
|
||||
sb.WriteString(fmt.Sprintf("\t\t\t\t\t\t%s: v.%s\n", fieldName, optField.field))
|
||||
sb.WriteString("\t\t\t\t\t}\n")
|
||||
} else {
|
||||
valStr := g.fieldValueToCUE(fieldVal)
|
||||
sb.WriteString(fmt.Sprintf("\t\t\t\t\t%s: %s\n", fieldName, valStr))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1968,7 +2056,7 @@ func (g *CUEGenerator) concatExprToCUE(ce *ConcatExprValue) string {
|
||||
func (g *CUEGenerator) conditionToCUE(cond Condition) string {
|
||||
switch c := cond.(type) {
|
||||
case *IsSetCondition:
|
||||
return fmt.Sprintf("parameter.%s != _|_", c.ParamName())
|
||||
return fmt.Sprintf("parameter[%q] != _|_", c.ParamName())
|
||||
case *ParamPathIsSetCondition:
|
||||
// Check if a nested parameter path is set: parameter.path != _|_
|
||||
return fmt.Sprintf("parameter.%s != _|_", c.Path())
|
||||
@@ -2012,9 +2100,9 @@ func (g *CUEGenerator) conditionToCUE(cond Condition) string {
|
||||
right := g.conditionToCUE(c.right)
|
||||
return fmt.Sprintf("(%s) || (%s)", left, right)
|
||||
case *NotCondition:
|
||||
// Special case: Not(IsSet("x")) -> parameter.x == _|_ (cleaner than !(parameter.x != _|_))
|
||||
// Special case: Not(IsSet("x")) -> parameter["x"] == _|_ (cleaner than !(parameter["x"] != _|_))
|
||||
if isSet, ok := c.Inner().(*IsSetCondition); ok {
|
||||
return fmt.Sprintf("parameter.%s == _|_", isSet.ParamName())
|
||||
return fmt.Sprintf("parameter[%q] == _|_", isSet.ParamName())
|
||||
}
|
||||
inner := g.conditionToCUE(c.Inner())
|
||||
return fmt.Sprintf("!(%s)", inner)
|
||||
@@ -2181,8 +2269,13 @@ func (g *CUEGenerator) writeParam(sb *strings.Builder, param Param, depth int) {
|
||||
|
||||
name := param.Name()
|
||||
optional := "?"
|
||||
if param.IsRequired() || param.HasDefault() {
|
||||
forceOptional := false
|
||||
if bp, ok := param.(interface{ IsForceOptional() bool }); ok {
|
||||
forceOptional = bp.IsForceOptional()
|
||||
}
|
||||
if param.IsRequired() || (param.HasDefault() && !forceOptional) {
|
||||
// No ? for required fields or fields with defaults (defaults make them effectively present)
|
||||
// Unless forceOptional is set, which keeps ? even with a default
|
||||
optional = ""
|
||||
}
|
||||
|
||||
@@ -2437,11 +2530,8 @@ func (g *CUEGenerator) writeMapParam(sb *strings.Builder, p *MapParam, indent, n
|
||||
}
|
||||
|
||||
// writeStringKeyMapParam writes a string-to-string map parameter.
|
||||
func (g *CUEGenerator) writeStringKeyMapParam(sb *strings.Builder, p *StringKeyMapParam, indent, name, optional string) {
|
||||
// Write description as comment if present
|
||||
if desc := p.GetDescription(); desc != "" {
|
||||
sb.WriteString(fmt.Sprintf("%s// +usage=%s\n", indent, desc))
|
||||
}
|
||||
// Note: description is already written by writeParam, so we don't write it here.
|
||||
func (g *CUEGenerator) writeStringKeyMapParam(sb *strings.Builder, _ *StringKeyMapParam, indent, name, optional string) {
|
||||
sb.WriteString(fmt.Sprintf("%s%s%s: [string]: string\n", indent, name, optional))
|
||||
}
|
||||
|
||||
|
||||
@@ -120,6 +120,23 @@ var _ = Describe("CUEGenerator", func() {
|
||||
Expect(cue).To(ContainSubstring("required: string"))
|
||||
Expect(cue).To(ContainSubstring("optional?: string"))
|
||||
})
|
||||
|
||||
It("should keep ? for ForceOptional parameters even with defaults", func() {
|
||||
comp := defkit.NewComponent("test").
|
||||
Params(
|
||||
defkit.String("normalDefault").Default("Honor").Enum("Honor", "Ignore"),
|
||||
defkit.String("optionalDefault").Default("Honor").ForceOptional().Enum("Honor", "Ignore"),
|
||||
)
|
||||
|
||||
cue := gen.GenerateParameterSchema(comp)
|
||||
|
||||
// Normal default: no ? (field is always present)
|
||||
Expect(cue).To(ContainSubstring(`normalDefault: *"Honor" | "Ignore"`))
|
||||
Expect(cue).NotTo(ContainSubstring(`normalDefault?:`))
|
||||
|
||||
// ForceOptional with default: has ? (field can be absent, defaults when present)
|
||||
Expect(cue).To(ContainSubstring(`optionalDefault?: *"Honor" | "Ignore"`))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GenerateParameterSchema with complex types", func() {
|
||||
|
||||
@@ -445,6 +445,22 @@ func (p *PatchKeyOp) Key() string { return p.key }
|
||||
// Elements returns the array elements to patch.
|
||||
func (p *PatchKeyOp) Elements() []Value { return p.elements }
|
||||
|
||||
// SpreadAllOp represents a patch operation that constrains all array elements.
|
||||
// This generates: path: [...{element}]
|
||||
// Used for applying the same patch to every element in an array (e.g., all containers).
|
||||
type SpreadAllOp struct {
|
||||
path string
|
||||
elements []Value
|
||||
}
|
||||
|
||||
func (s *SpreadAllOp) resourceOp() {}
|
||||
|
||||
// Path returns the path being patched.
|
||||
func (s *SpreadAllOp) Path() string { return s.path }
|
||||
|
||||
// Elements returns the array elements to constrain.
|
||||
func (s *SpreadAllOp) Elements() []Value { return s.elements }
|
||||
|
||||
// --- Context Path Exists Check ---
|
||||
|
||||
// PathExistsCondition checks if a path exists in CUE (path != _|_).
|
||||
|
||||
@@ -96,4 +96,98 @@ var _ = Describe("HelperDefinition", func() {
|
||||
Expect(helpers).To(BeEmpty())
|
||||
})
|
||||
})
|
||||
|
||||
Context("Map-based helper with StringKeyMap and typed arrays", func() {
|
||||
It("should render StringKeyMap as [string]: string in helper definition", func() {
|
||||
helper := defkit.Map("labelSelector").WithFields(
|
||||
defkit.StringKeyMap("matchLabels").Description("A map of {key,value} pairs"),
|
||||
defkit.Array("matchExpressions").Description("Label selector requirements").WithFields(
|
||||
defkit.String("key").Required(),
|
||||
defkit.String("operator").Default("In").Enum("In", "NotIn", "Exists", "DoesNotExist"),
|
||||
defkit.Array("values").Of(defkit.ParamTypeString),
|
||||
),
|
||||
)
|
||||
|
||||
trait := defkit.NewTrait("helper-test").
|
||||
Description("Test helper rendering").
|
||||
AppliesTo("deployments.apps").
|
||||
Helper("labelSelector", helper).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.Patch().Set("spec.selector", defkit.Lit("test"))
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// StringKeyMap renders as [string]: string
|
||||
Expect(cue).To(ContainSubstring("matchLabels?: [string]: string"))
|
||||
// Should NOT have duplicate description comments
|
||||
Expect(cue).NotTo(ContainSubstring("// +usage=A map of {key,value} pairs\n\t// +usage=A map of {key,value} pairs"))
|
||||
// Array with fields renders as [...{...}]
|
||||
Expect(cue).To(ContainSubstring("matchExpressions?:"))
|
||||
Expect(cue).To(ContainSubstring("[...{"))
|
||||
// Typed array renders as [...string]
|
||||
Expect(cue).To(ContainSubstring("values?: [...string]"))
|
||||
// Enum with default renders correctly
|
||||
Expect(cue).To(ContainSubstring(`*"In"`))
|
||||
})
|
||||
|
||||
It("should render ArrayOf(ParamTypeString) in Struct-based helper fields", func() {
|
||||
helper := defkit.Struct("nodeSelector").Fields(
|
||||
defkit.Field("key", defkit.ParamTypeString).Required(),
|
||||
defkit.Field("operator", defkit.ParamTypeString).Default("In").Enum("In", "NotIn"),
|
||||
defkit.Field("values", defkit.ParamTypeArray).ArrayOf(defkit.ParamTypeString),
|
||||
)
|
||||
|
||||
trait := defkit.NewTrait("typed-array-test").
|
||||
Description("Test typed arrays in helpers").
|
||||
AppliesTo("deployments.apps").
|
||||
Helper("nodeSelector", helper).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.Patch().Set("spec.selector", defkit.Lit("test"))
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
Expect(cue).To(ContainSubstring("#nodeSelector"))
|
||||
// key is required (no ?) - CUE formatter may add tab alignment
|
||||
Expect(cue).To(MatchRegexp(`key:\s+string`))
|
||||
Expect(cue).To(ContainSubstring("values?: [...string]"))
|
||||
// Untyped array should NOT appear
|
||||
Expect(cue).NotTo(MatchRegexp(`values\?\: \[\.\.\.\]\s*\n`))
|
||||
})
|
||||
|
||||
It("should render schemaRef with ArrayOf correctly in Struct helper", func() {
|
||||
selectorHelper := defkit.Struct("nodeSelectorTerm").Fields(
|
||||
defkit.Field("matchExpressions", defkit.ParamTypeArray).WithSchemaRef("nodeSelector"),
|
||||
defkit.Field("matchFields", defkit.ParamTypeArray).WithSchemaRef("nodeSelector"),
|
||||
)
|
||||
|
||||
affinityHelper := defkit.Struct("podAffinityTerm").Fields(
|
||||
defkit.Field("labelSelector", defkit.ParamTypeStruct).WithSchemaRef("labelSelector"),
|
||||
defkit.Field("namespaces", defkit.ParamTypeArray).ArrayOf(defkit.ParamTypeString),
|
||||
defkit.Field("topologyKey", defkit.ParamTypeString).Required(),
|
||||
)
|
||||
|
||||
trait := defkit.NewTrait("schemaref-test").
|
||||
Description("Test schemaRef in helpers").
|
||||
AppliesTo("deployments.apps").
|
||||
Helper("nodeSelectorTerm", selectorHelper).
|
||||
Helper("podAffinityTerm", affinityHelper).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.Patch().Set("spec.selector", defkit.Lit("test"))
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// SchemaRef on array field renders as [...#name]
|
||||
Expect(cue).To(ContainSubstring("matchExpressions?: [...#nodeSelector]"))
|
||||
Expect(cue).To(ContainSubstring("matchFields?: [...#nodeSelector]"))
|
||||
// SchemaRef on struct field renders as #name
|
||||
Expect(cue).To(ContainSubstring("labelSelector?: #labelSelector"))
|
||||
// ArrayOf(ParamTypeString) renders as [...string]
|
||||
Expect(cue).To(ContainSubstring("namespaces?: [...string]"))
|
||||
// Required field has no ?
|
||||
Expect(cue).To(ContainSubstring("topologyKey: string"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -18,11 +18,12 @@ package defkit
|
||||
|
||||
// baseParam provides common parameter functionality.
|
||||
type baseParam struct {
|
||||
name string
|
||||
paramType ParamType
|
||||
required bool
|
||||
defaultValue any
|
||||
description string
|
||||
name string
|
||||
paramType ParamType
|
||||
required bool
|
||||
defaultValue any
|
||||
description string
|
||||
forceOptional bool // when true, field stays optional even with a default value
|
||||
}
|
||||
|
||||
func (p *baseParam) expr() {}
|
||||
@@ -35,6 +36,7 @@ func (p *baseParam) IsOptional() bool { return !p.required }
|
||||
func (p *baseParam) HasDefault() bool { return p.defaultValue != nil }
|
||||
func (p *baseParam) GetDefault() any { return p.defaultValue }
|
||||
func (p *baseParam) GetDescription() string { return p.description }
|
||||
func (p *baseParam) IsForceOptional() bool { return p.forceOptional }
|
||||
|
||||
// IsSet returns a condition that checks if the parameter has a value.
|
||||
// This is used with SetIf for conditional field assignment.
|
||||
@@ -132,6 +134,14 @@ func (p *StringParam) Optional() *StringParam {
|
||||
return p
|
||||
}
|
||||
|
||||
// ForceOptional makes the field optional even when it has a default value.
|
||||
// Normally, fields with defaults are treated as always-present (no ? in CUE).
|
||||
// This generates field?: *default | type instead of field: *default | type.
|
||||
func (p *StringParam) ForceOptional() *StringParam {
|
||||
p.forceOptional = true
|
||||
return p
|
||||
}
|
||||
|
||||
// Default sets a default value for the parameter.
|
||||
func (p *StringParam) Default(value string) *StringParam {
|
||||
p.defaultValue = value
|
||||
@@ -837,6 +847,8 @@ func (f *StructField) Description(desc string) *StructField {
|
||||
}
|
||||
|
||||
// Nested sets a nested struct definition for this field.
|
||||
// For ParamTypeStruct fields, this defines the struct's shape.
|
||||
// For ParamTypeArray fields, this defines the array element struct shape (generates [...{fields}]).
|
||||
func (f *StructField) Nested(s *StructParam) *StructField {
|
||||
f.nested = s
|
||||
if f.fieldType == ParamTypeArray {
|
||||
|
||||
@@ -61,6 +61,19 @@ var _ = Describe("Parameters", func() {
|
||||
Expect(p.GetDefault()).To(Equal("nginx:latest"))
|
||||
Expect(p.GetDescription()).To(Equal("Container image"))
|
||||
})
|
||||
|
||||
It("should support ForceOptional to stay optional even with a default", func() {
|
||||
p := defkit.String("policy").Default("Honor").ForceOptional().Enum("Honor", "Ignore")
|
||||
Expect(p.HasDefault()).To(BeTrue())
|
||||
Expect(p.GetDefault()).To(Equal("Honor"))
|
||||
Expect(p.IsForceOptional()).To(BeTrue())
|
||||
Expect(p.IsRequired()).To(BeFalse())
|
||||
})
|
||||
|
||||
It("should not be force-optional by default", func() {
|
||||
p := defkit.String("policy").Default("Honor")
|
||||
Expect(p.IsForceOptional()).To(BeFalse())
|
||||
})
|
||||
})
|
||||
|
||||
Context("IntParam", func() {
|
||||
|
||||
@@ -122,6 +122,42 @@ func (p *PatchResource) PatchKey(path string, key string, elements ...Value) *Pa
|
||||
return p
|
||||
}
|
||||
|
||||
// SpreadAll adds a spread constraint that applies to all array elements.
|
||||
// This generates: path: [...{element1}, ...{element2}]
|
||||
// Used for applying the same patch to every element in an array.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// lifecycleObj := defkit.NewArrayElement().
|
||||
// SetIf(postStart.IsSet(), "lifecycle.postStart", postStart)
|
||||
// tpl.Patch().SpreadAll("spec.template.spec.containers", lifecycleObj)
|
||||
// // Generates: containers: [...{lifecycle: { if ... { postStart: ... } }}]
|
||||
func (p *PatchResource) SpreadAll(path string, elements ...Value) *PatchResource {
|
||||
op := &SpreadAllOp{path: path, elements: elements}
|
||||
if p.currentIf != nil {
|
||||
p.currentIf.ops = append(p.currentIf.ops, op)
|
||||
} else {
|
||||
p.ops = append(p.ops, op)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// PatchStrategyAnnotation annotates a specific field path with // +patchStrategy=strategy.
|
||||
// This generates a CUE comment annotation before the field.
|
||||
// Example: p.PatchStrategyAnnotation("spec.strategy", "retainKeys")
|
||||
// Generates: // +patchStrategy=retainKeys
|
||||
//
|
||||
// strategy: { ... }
|
||||
func (p *PatchResource) PatchStrategyAnnotation(path string, strategy string) *PatchResource {
|
||||
op := &PatchStrategyAnnotationOp{path: path, strategy: strategy}
|
||||
if p.currentIf != nil {
|
||||
p.currentIf.ops = append(p.currentIf.ops, op)
|
||||
} else {
|
||||
p.ops = append(p.ops, op)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Ops returns all recorded operations.
|
||||
func (p *PatchResource) Ops() []ResourceOp { return p.ops }
|
||||
|
||||
@@ -138,6 +174,20 @@ type PassthroughOp struct{}
|
||||
|
||||
func (p *PassthroughOp) resourceOp() {}
|
||||
|
||||
// PatchStrategyAnnotationOp records a patchStrategy annotation on a field path.
|
||||
type PatchStrategyAnnotationOp struct {
|
||||
path string
|
||||
strategy string
|
||||
}
|
||||
|
||||
func (p *PatchStrategyAnnotationOp) resourceOp() {}
|
||||
|
||||
// Path returns the path being annotated.
|
||||
func (p *PatchStrategyAnnotationOp) Path() string { return p.path }
|
||||
|
||||
// Strategy returns the patch strategy value.
|
||||
func (p *PatchStrategyAnnotationOp) Strategy() string { return p.strategy }
|
||||
|
||||
// ForEachOp represents a for-each spread operation in a patch.
|
||||
// This generates CUE like: for k, v in source { (k): v }
|
||||
type ForEachOp struct {
|
||||
|
||||
@@ -22,6 +22,172 @@ package defkit
|
||||
// - ListComprehension: for CUE list comprehensions with conditional fields
|
||||
// - ParamIsSet/ParamNotSet: convenience constructors for parameter existence conditions
|
||||
|
||||
// PatchFieldBuilder provides a fluent API for constructing PatchContainerField values.
|
||||
// Use PatchField() to start building.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// defkit.PatchField("exec").IsSet().Build()
|
||||
// defkit.PatchField("initialDelaySeconds").Int().IsSet().Default("0").Build()
|
||||
// defkit.PatchField("image").Strategy("retainKeys").Description("Specify the image").Build()
|
||||
type PatchFieldBuilder struct {
|
||||
paramName string
|
||||
targetField string
|
||||
patchStrategy string
|
||||
condition string
|
||||
paramType string
|
||||
paramDefault string
|
||||
description string
|
||||
}
|
||||
|
||||
// PatchField starts building a PatchContainerField with the given parameter name.
|
||||
// The TargetField defaults to the same as the parameter name.
|
||||
func PatchField(name string) *PatchFieldBuilder {
|
||||
return &PatchFieldBuilder{
|
||||
paramName: name,
|
||||
targetField: name,
|
||||
}
|
||||
}
|
||||
|
||||
// Target sets the container field to patch, if different from the parameter name.
|
||||
func (b *PatchFieldBuilder) Target(t string) *PatchFieldBuilder {
|
||||
b.targetField = t
|
||||
return b
|
||||
}
|
||||
|
||||
// Default sets an explicit default value for the parameter.
|
||||
func (b *PatchFieldBuilder) Default(val string) *PatchFieldBuilder {
|
||||
b.paramDefault = val
|
||||
return b
|
||||
}
|
||||
|
||||
// Type sets an explicit CUE type string (e.g., "string", "[...string]", "{...}").
|
||||
func (b *PatchFieldBuilder) Type(t string) *PatchFieldBuilder {
|
||||
b.paramType = t
|
||||
return b
|
||||
}
|
||||
|
||||
// Int is shorthand for Type("int").
|
||||
func (b *PatchFieldBuilder) Int() *PatchFieldBuilder {
|
||||
return b.Type("int")
|
||||
}
|
||||
|
||||
// Bool is shorthand for Type("bool").
|
||||
func (b *PatchFieldBuilder) Bool() *PatchFieldBuilder {
|
||||
return b.Type("bool")
|
||||
}
|
||||
|
||||
// Str is shorthand for Type("string").
|
||||
func (b *PatchFieldBuilder) Str() *PatchFieldBuilder {
|
||||
return b.Type("string")
|
||||
}
|
||||
|
||||
// StringArray is shorthand for Type("[...string]").
|
||||
func (b *PatchFieldBuilder) StringArray() *PatchFieldBuilder {
|
||||
return b.Type("[...string]")
|
||||
}
|
||||
|
||||
// Strategy sets the patch strategy (e.g., "replace", "retainKeys").
|
||||
func (b *PatchFieldBuilder) Strategy(s string) *PatchFieldBuilder {
|
||||
b.patchStrategy = s
|
||||
return b
|
||||
}
|
||||
|
||||
// --- Condition methods (following param.go / health_expr.go patterns) ---
|
||||
|
||||
// IsSet guards the field with an existence check (CUE: != _|_).
|
||||
// Use this for optional fields that should only be patched when provided.
|
||||
func (b *PatchFieldBuilder) IsSet() *PatchFieldBuilder {
|
||||
b.condition = "!= _|_"
|
||||
return b
|
||||
}
|
||||
|
||||
// NotEmpty guards the field with a non-empty string check (CUE: != "").
|
||||
// Use this for string fields that should only be patched when non-empty.
|
||||
func (b *PatchFieldBuilder) NotEmpty() *PatchFieldBuilder {
|
||||
b.condition = `!= ""`
|
||||
return b
|
||||
}
|
||||
|
||||
// Eq sets a condition that checks the field equals the given value.
|
||||
func (b *PatchFieldBuilder) Eq(val string) *PatchFieldBuilder {
|
||||
b.condition = "== " + val
|
||||
return b
|
||||
}
|
||||
|
||||
// Ne sets a condition that checks the field is not equal to the given value.
|
||||
func (b *PatchFieldBuilder) Ne(val string) *PatchFieldBuilder {
|
||||
b.condition = "!= " + val
|
||||
return b
|
||||
}
|
||||
|
||||
// Gt sets a condition that checks the field is greater than the given value.
|
||||
func (b *PatchFieldBuilder) Gt(val string) *PatchFieldBuilder {
|
||||
b.condition = "> " + val
|
||||
return b
|
||||
}
|
||||
|
||||
// Gte sets a condition that checks the field is greater than or equal to the given value.
|
||||
func (b *PatchFieldBuilder) Gte(val string) *PatchFieldBuilder {
|
||||
b.condition = ">= " + val
|
||||
return b
|
||||
}
|
||||
|
||||
// Lt sets a condition that checks the field is less than the given value.
|
||||
func (b *PatchFieldBuilder) Lt(val string) *PatchFieldBuilder {
|
||||
b.condition = "< " + val
|
||||
return b
|
||||
}
|
||||
|
||||
// Lte sets a condition that checks the field is less than or equal to the given value.
|
||||
func (b *PatchFieldBuilder) Lte(val string) *PatchFieldBuilder {
|
||||
b.condition = "<= " + val
|
||||
return b
|
||||
}
|
||||
|
||||
// RawCondition sets a raw CUE condition string.
|
||||
// Use this as an escape hatch for non-standard conditions not covered by the typed API.
|
||||
func (b *PatchFieldBuilder) RawCondition(c string) *PatchFieldBuilder {
|
||||
b.condition = c
|
||||
return b
|
||||
}
|
||||
|
||||
// Description sets the +usage description for this field.
|
||||
func (b *PatchFieldBuilder) Description(d string) *PatchFieldBuilder {
|
||||
b.description = d
|
||||
return b
|
||||
}
|
||||
|
||||
// Build returns the constructed PatchContainerField.
|
||||
func (b *PatchFieldBuilder) Build() PatchContainerField {
|
||||
return PatchContainerField{
|
||||
ParamName: b.paramName,
|
||||
TargetField: b.targetField,
|
||||
PatchStrategy: b.patchStrategy,
|
||||
Condition: b.condition,
|
||||
ParamType: b.paramType,
|
||||
ParamDefault: b.paramDefault,
|
||||
Description: b.description,
|
||||
}
|
||||
}
|
||||
|
||||
// PatchFields builds a slice of PatchContainerField from builders.
|
||||
// This eliminates the need to call .Build() on each field individually.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// Fields: defkit.PatchFields(
|
||||
// defkit.PatchField("exec").IsSet(),
|
||||
// defkit.PatchField("initialDelaySeconds").Int().IsSet().Default("0"),
|
||||
// )
|
||||
func PatchFields(builders ...*PatchFieldBuilder) []PatchContainerField {
|
||||
fields := make([]PatchContainerField, len(builders))
|
||||
for i, b := range builders {
|
||||
fields[i] = b.Build()
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
// PatchContainerField defines a field to be patched in the container.
|
||||
type PatchContainerField struct {
|
||||
ParamName string // the parameter name (e.g., "command", "args")
|
||||
@@ -30,6 +196,7 @@ type PatchContainerField struct {
|
||||
Condition string // optional CUE condition (e.g., "!= null")
|
||||
ParamType string // explicit CUE type (e.g., "string", "[...string]", "{...}")
|
||||
ParamDefault string // explicit default value (e.g., "0", "\"\"", "false")
|
||||
Description string // optional +usage description (auto-generated if empty)
|
||||
}
|
||||
|
||||
// PatchContainerGroup defines a group of fields under a common parent field.
|
||||
@@ -53,11 +220,13 @@ type PatchContainerConfig struct {
|
||||
Groups []PatchContainerGroup // grouped fields (e.g., startupProbe: { ... })
|
||||
AllowMultiple bool // if true, allow patching multiple containers
|
||||
ContainersParam string // for multi-container mode, the array param name
|
||||
ContainersDescription string // +usage description for the containers param (auto-generated if empty)
|
||||
CustomParamsBlock string // custom CUE block for #PatchParams (for complex types)
|
||||
MultiContainerParam string // alternate name for multi-container param (default: "probes" for probes, "containers" for others)
|
||||
CustomPatchContainerBlock string // custom CUE block for PatchContainer body (for complex merge logic)
|
||||
CustomPatchBlock string // custom CUE block for the patch: spec: template: spec: { ... } body
|
||||
CustomParameterBlock string // custom CUE block for the parameter definition
|
||||
PatchStrategy string // if set, emitted as // +patchStrategy=<value> before the patch block (e.g., "open")
|
||||
}
|
||||
|
||||
// --- Let Binding Support ---
|
||||
|
||||
@@ -17,6 +17,8 @@ limitations under the License.
|
||||
package defkit_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
@@ -25,6 +27,359 @@ import (
|
||||
|
||||
var _ = Describe("PatchContainer", func() {
|
||||
|
||||
Context("PatchFieldBuilder", func() {
|
||||
It("should set ParamName and default TargetField to the same name", func() {
|
||||
f := defkit.PatchField("exec").Build()
|
||||
Expect(f.ParamName).To(Equal("exec"))
|
||||
Expect(f.TargetField).To(Equal("exec"))
|
||||
Expect(f.Condition).To(BeEmpty())
|
||||
Expect(f.ParamType).To(BeEmpty())
|
||||
Expect(f.ParamDefault).To(BeEmpty())
|
||||
Expect(f.PatchStrategy).To(BeEmpty())
|
||||
Expect(f.Description).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("should override TargetField with Target()", func() {
|
||||
f := defkit.PatchField("addCapabilities").Target("add").Build()
|
||||
Expect(f.ParamName).To(Equal("addCapabilities"))
|
||||
Expect(f.TargetField).To(Equal("add"))
|
||||
})
|
||||
|
||||
It("should set Condition via IsSet()", func() {
|
||||
f := defkit.PatchField("exec").IsSet().Build()
|
||||
Expect(f.Condition).To(Equal("!= _|_"))
|
||||
})
|
||||
|
||||
It("should set ParamDefault via Default()", func() {
|
||||
f := defkit.PatchField("initialDelaySeconds").Default("0").Build()
|
||||
Expect(f.ParamDefault).To(Equal("0"))
|
||||
})
|
||||
|
||||
It("should set ParamType via Int(), Bool(), Str(), StringArray()", func() {
|
||||
Expect(defkit.PatchField("x").Int().Build().ParamType).To(Equal("int"))
|
||||
Expect(defkit.PatchField("x").Bool().Build().ParamType).To(Equal("bool"))
|
||||
Expect(defkit.PatchField("x").Str().Build().ParamType).To(Equal("string"))
|
||||
Expect(defkit.PatchField("x").StringArray().Build().ParamType).To(Equal("[...string]"))
|
||||
})
|
||||
|
||||
It("should set PatchStrategy via Strategy()", func() {
|
||||
f := defkit.PatchField("image").Strategy("retainKeys").Build()
|
||||
Expect(f.PatchStrategy).To(Equal("retainKeys"))
|
||||
})
|
||||
|
||||
It("should set Condition via NotEmpty()", func() {
|
||||
f := defkit.PatchField("imagePullPolicy").NotEmpty().Build()
|
||||
Expect(f.Condition).To(Equal(`!= ""`))
|
||||
})
|
||||
|
||||
It("should set Condition via comparison methods", func() {
|
||||
Expect(defkit.PatchField("x").Gt("0").Build().Condition).To(Equal("> 0"))
|
||||
Expect(defkit.PatchField("x").Gte("1").Build().Condition).To(Equal(">= 1"))
|
||||
Expect(defkit.PatchField("x").Lt("100").Build().Condition).To(Equal("< 100"))
|
||||
Expect(defkit.PatchField("x").Lte("99").Build().Condition).To(Equal("<= 99"))
|
||||
Expect(defkit.PatchField("x").Eq("42").Build().Condition).To(Equal("== 42"))
|
||||
Expect(defkit.PatchField("x").Ne("0").Build().Condition).To(Equal("!= 0"))
|
||||
})
|
||||
|
||||
It("should set Condition via RawCondition escape hatch", func() {
|
||||
f := defkit.PatchField("x").RawCondition(`!= "custom"`).Build()
|
||||
Expect(f.Condition).To(Equal(`!= "custom"`))
|
||||
})
|
||||
|
||||
It("should set Description via Description()", func() {
|
||||
f := defkit.PatchField("image").Description("Specify the image").Build()
|
||||
Expect(f.Description).To(Equal("Specify the image"))
|
||||
})
|
||||
|
||||
It("should chain all methods together", func() {
|
||||
f := defkit.PatchField("initialDelaySeconds").
|
||||
Int().
|
||||
IsSet().
|
||||
Default("0").
|
||||
Description("Seconds before probe starts").
|
||||
Build()
|
||||
Expect(f.ParamName).To(Equal("initialDelaySeconds"))
|
||||
Expect(f.TargetField).To(Equal("initialDelaySeconds"))
|
||||
Expect(f.ParamType).To(Equal("int"))
|
||||
Expect(f.Condition).To(Equal("!= _|_"))
|
||||
Expect(f.ParamDefault).To(Equal("0"))
|
||||
Expect(f.Description).To(Equal("Seconds before probe starts"))
|
||||
})
|
||||
|
||||
It("should produce struct equivalent to manual construction", func() {
|
||||
builder := defkit.PatchField("addCapabilities").
|
||||
Target("add").
|
||||
StringArray().
|
||||
IsSet().
|
||||
Description("Specify the addCapabilities of the container").
|
||||
Build()
|
||||
|
||||
manual := defkit.PatchContainerField{
|
||||
ParamName: "addCapabilities",
|
||||
TargetField: "add",
|
||||
ParamType: "[...string]",
|
||||
Condition: "!= _|_",
|
||||
Description: "Specify the addCapabilities of the container",
|
||||
}
|
||||
|
||||
Expect(builder).To(Equal(manual))
|
||||
})
|
||||
|
||||
It("PatchFields should batch-build without .Build()", func() {
|
||||
fields := defkit.PatchFields(
|
||||
defkit.PatchField("exec").IsSet(),
|
||||
defkit.PatchField("delay").Int().IsSet().Default("0"),
|
||||
)
|
||||
Expect(fields).To(HaveLen(2))
|
||||
Expect(fields[0].ParamName).To(Equal("exec"))
|
||||
Expect(fields[0].Condition).To(Equal("!= _|_"))
|
||||
Expect(fields[1].ParamName).To(Equal("delay"))
|
||||
Expect(fields[1].ParamType).To(Equal("int"))
|
||||
Expect(fields[1].ParamDefault).To(Equal("0"))
|
||||
})
|
||||
|
||||
It("PatchFields should return empty slice for zero builders", func() {
|
||||
fields := defkit.PatchFields()
|
||||
Expect(fields).To(HaveLen(0))
|
||||
Expect(fields).NotTo(BeNil())
|
||||
})
|
||||
|
||||
It("should set ParamType via Type() for custom CUE types", func() {
|
||||
f := defkit.PatchField("metadata").Type("{...}").Build()
|
||||
Expect(f.ParamType).To(Equal("{...}"))
|
||||
})
|
||||
|
||||
It("last condition method call should win", func() {
|
||||
f := defkit.PatchField("x").IsSet().NotEmpty().Build()
|
||||
Expect(f.Condition).To(Equal(`!= ""`))
|
||||
|
||||
f2 := defkit.PatchField("x").NotEmpty().IsSet().Build()
|
||||
Expect(f2.Condition).To(Equal("!= _|_"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("CUE generation with PatchFieldBuilder", func() {
|
||||
It("should produce identical CUE from builder and manual struct construction", func() {
|
||||
// Build the same trait using builder API
|
||||
builderTrait := defkit.NewTrait("builder-cue-test").
|
||||
Description("Test builder produces same CUE as manual").
|
||||
AppliesTo("deployments.apps").
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.UsePatchContainer(defkit.PatchContainerConfig{
|
||||
ContainerNameParam: "containerName",
|
||||
DefaultToContextName: true,
|
||||
Groups: []defkit.PatchContainerGroup{
|
||||
{
|
||||
TargetField: "securityContext",
|
||||
Fields: defkit.PatchFields(
|
||||
defkit.PatchField("privileged").Bool().Default("false"),
|
||||
defkit.PatchField("runAsUser").Int().IsSet(),
|
||||
),
|
||||
SubGroups: []defkit.PatchContainerGroup{
|
||||
{
|
||||
TargetField: "capabilities",
|
||||
Fields: defkit.PatchFields(
|
||||
defkit.PatchField("addCapabilities").Target("add").StringArray().IsSet(),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
// Build the same trait using manual struct construction
|
||||
manualTrait := defkit.NewTrait("builder-cue-test").
|
||||
Description("Test builder produces same CUE as manual").
|
||||
AppliesTo("deployments.apps").
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.UsePatchContainer(defkit.PatchContainerConfig{
|
||||
ContainerNameParam: "containerName",
|
||||
DefaultToContextName: true,
|
||||
Groups: []defkit.PatchContainerGroup{
|
||||
{
|
||||
TargetField: "securityContext",
|
||||
Fields: []defkit.PatchContainerField{
|
||||
{ParamName: "privileged", TargetField: "privileged", ParamType: "bool", ParamDefault: "false"},
|
||||
{ParamName: "runAsUser", TargetField: "runAsUser", ParamType: "int", Condition: "!= _|_"},
|
||||
},
|
||||
SubGroups: []defkit.PatchContainerGroup{
|
||||
{
|
||||
TargetField: "capabilities",
|
||||
Fields: []defkit.PatchContainerField{
|
||||
{ParamName: "addCapabilities", TargetField: "add", ParamType: "[...string]", Condition: "!= _|_"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
Expect(builderTrait.ToCue()).To(Equal(manualTrait.ToCue()))
|
||||
})
|
||||
|
||||
It("should generate correct CUE for PatchFields with Strategy and NotEmpty", func() {
|
||||
trait := defkit.NewTrait("builder-strategy-test").
|
||||
Description("Test Strategy and NotEmpty via builder").
|
||||
AppliesTo("deployments.apps").
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.UsePatchContainer(defkit.PatchContainerConfig{
|
||||
ContainerNameParam: "containerName",
|
||||
DefaultToContextName: true,
|
||||
PatchFields: defkit.PatchFields(
|
||||
defkit.PatchField("image").Strategy("retainKeys"),
|
||||
defkit.PatchField("imagePullPolicy").Strategy("retainKeys").NotEmpty(),
|
||||
),
|
||||
})
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Strategy should produce patchStrategy comments
|
||||
Expect(cue).To(ContainSubstring(`// +patchStrategy=retainKeys`))
|
||||
// NotEmpty() should produce conditional block in PatchContainer body
|
||||
Expect(cue).To(ContainSubstring(`if _params.imagePullPolicy != ""`))
|
||||
// Unconditional field should be assigned directly
|
||||
Expect(cue).To(ContainSubstring(`image: _params.image`))
|
||||
})
|
||||
|
||||
It("should generate correct CUE for IsSet fields in groups", func() {
|
||||
trait := defkit.NewTrait("builder-isset-group-test").
|
||||
Description("Test IsSet in groups via builder").
|
||||
AppliesTo("deployments.apps").
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.UsePatchContainer(defkit.PatchContainerConfig{
|
||||
ContainerNameParam: "containerName",
|
||||
DefaultToContextName: true,
|
||||
Groups: []defkit.PatchContainerGroup{
|
||||
{
|
||||
TargetField: "startupProbe",
|
||||
Fields: defkit.PatchFields(
|
||||
defkit.PatchField("exec").Str().IsSet(),
|
||||
defkit.PatchField("initialDelaySeconds").Int().IsSet().Default("0"),
|
||||
defkit.PatchField("terminationGracePeriodSeconds").Int().IsSet(),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Str().IsSet() → optional string param and conditional in PatchContainer body
|
||||
Expect(cue).To(ContainSubstring(`exec?: string`))
|
||||
Expect(cue).To(ContainSubstring(`if _params.exec != _|_`))
|
||||
|
||||
// Int().IsSet() → optional int in param schema and conditional in body
|
||||
Expect(cue).To(ContainSubstring(`terminationGracePeriodSeconds?: int`))
|
||||
Expect(cue).To(ContainSubstring(`if _params.terminationGracePeriodSeconds != _|_`))
|
||||
|
||||
// Int().IsSet().Default("0") → default in param schema but still conditional in body
|
||||
Expect(cue).To(ContainSubstring(`initialDelaySeconds: *0 | int`))
|
||||
Expect(cue).To(ContainSubstring(`if _params.initialDelaySeconds != _|_`))
|
||||
|
||||
// startupProbe group wrapper
|
||||
Expect(cue).To(ContainSubstring(`startupProbe: {`))
|
||||
})
|
||||
|
||||
It("should generate correct CUE for Default without IsSet (unconditional)", func() {
|
||||
trait := defkit.NewTrait("builder-default-only-test").
|
||||
Description("Test Default without IsSet via builder").
|
||||
AppliesTo("deployments.apps").
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.UsePatchContainer(defkit.PatchContainerConfig{
|
||||
ContainerNameParam: "containerName",
|
||||
DefaultToContextName: true,
|
||||
Groups: []defkit.PatchContainerGroup{
|
||||
{
|
||||
TargetField: "securityContext",
|
||||
Fields: defkit.PatchFields(
|
||||
defkit.PatchField("privileged").Bool().Default("false"),
|
||||
defkit.PatchField("runAsUser").Int().IsSet(),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Bool().Default("false") → default in param, unconditional in PatchContainer body
|
||||
Expect(cue).To(ContainSubstring(`privileged: *false | bool`))
|
||||
Expect(cue).To(ContainSubstring(`privileged: _params.privileged`))
|
||||
Expect(cue).NotTo(ContainSubstring(`if _params.privileged`))
|
||||
|
||||
// Int().IsSet() → optional param, conditional in PatchContainer body
|
||||
Expect(cue).To(ContainSubstring(`runAsUser?: int`))
|
||||
Expect(cue).To(ContainSubstring(`if _params.runAsUser != _|_`))
|
||||
})
|
||||
|
||||
It("should generate correct CUE for Target remapping in subgroups", func() {
|
||||
trait := defkit.NewTrait("builder-target-test").
|
||||
Description("Test Target remapping via builder").
|
||||
AppliesTo("deployments.apps").
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.UsePatchContainer(defkit.PatchContainerConfig{
|
||||
ContainerNameParam: "containerName",
|
||||
DefaultToContextName: true,
|
||||
Groups: []defkit.PatchContainerGroup{
|
||||
{
|
||||
TargetField: "securityContext",
|
||||
Fields: defkit.PatchFields(
|
||||
defkit.PatchField("privileged").Bool().Default("false"),
|
||||
),
|
||||
SubGroups: []defkit.PatchContainerGroup{
|
||||
{
|
||||
TargetField: "capabilities",
|
||||
Fields: defkit.PatchFields(
|
||||
defkit.PatchField("addCapabilities").Target("add").StringArray().IsSet(),
|
||||
defkit.PatchField("dropCapabilities").Target("drop").StringArray().IsSet(),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Target("add") remaps param name to different container field
|
||||
Expect(cue).To(ContainSubstring(`addCapabilities?: [...string]`))
|
||||
Expect(cue).To(ContainSubstring(`add: _params.addCapabilities`))
|
||||
|
||||
// Target("drop") remaps param name to different container field
|
||||
Expect(cue).To(ContainSubstring(`dropCapabilities?: [...string]`))
|
||||
Expect(cue).To(ContainSubstring(`drop: _params.dropCapabilities`))
|
||||
|
||||
// Nested group structure
|
||||
Expect(cue).To(ContainSubstring(`securityContext: {`))
|
||||
Expect(cue).To(ContainSubstring(`capabilities: {`))
|
||||
})
|
||||
|
||||
It("should generate correct CUE for Description on builder fields", func() {
|
||||
trait := defkit.NewTrait("builder-desc-test").
|
||||
Description("Test Description via builder").
|
||||
AppliesTo("deployments.apps").
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.UsePatchContainer(defkit.PatchContainerConfig{
|
||||
ContainerNameParam: "containerName",
|
||||
DefaultToContextName: true,
|
||||
PatchFields: defkit.PatchFields(
|
||||
defkit.PatchField("image").Strategy("retainKeys").Description("Specify the image of the container"),
|
||||
defkit.PatchField("imagePullPolicy").Strategy("retainKeys").NotEmpty().Description("Specify the image pull policy"),
|
||||
),
|
||||
})
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
Expect(cue).To(ContainSubstring("// +usage=Specify the image of the container"))
|
||||
Expect(cue).To(ContainSubstring("// +usage=Specify the image pull policy"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Trait with PatchContainer CUE Generation", func() {
|
||||
It("should generate complete PatchContainer trait CUE structure", func() {
|
||||
containerName := defkit.String("containerName").Default("")
|
||||
@@ -90,10 +445,186 @@ var _ = Describe("PatchContainer", func() {
|
||||
// Verify error collection
|
||||
Expect(cue).To(ContainSubstring(`errs: [for c in patch.spec.template.spec.containers if c.err != _|_ {c.err}]`))
|
||||
|
||||
// Verify parameter block with optional fields
|
||||
Expect(cue).To(ContainSubstring(`parameter: {`))
|
||||
Expect(cue).To(ContainSubstring(`command?: [...string]`))
|
||||
Expect(cue).To(ContainSubstring(`args?: [...string]`))
|
||||
// Verify parameter block comes from PatchContainer (no duplicate from regular params)
|
||||
Expect(cue).To(ContainSubstring(`parameter: #PatchParams`))
|
||||
// The extra parameter: {} from regular params should NOT appear
|
||||
Expect(strings.Count(cue, "parameter:")).To(Equal(1))
|
||||
})
|
||||
|
||||
It("should use optional field syntax for non-string conditions like != _|_", func() {
|
||||
trait := defkit.NewTrait("optional-field-test").
|
||||
Description("Test optional fields for non-string conditions").
|
||||
AppliesTo("deployments.apps").
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.UsePatchContainer(defkit.PatchContainerConfig{
|
||||
ContainerNameParam: "containerName",
|
||||
DefaultToContextName: true,
|
||||
Groups: []defkit.PatchContainerGroup{
|
||||
{
|
||||
TargetField: "securityContext",
|
||||
Fields: []defkit.PatchContainerField{
|
||||
{ParamName: "privileged", TargetField: "privileged", ParamType: "bool", ParamDefault: "false"},
|
||||
{ParamName: "runAsUser", TargetField: "runAsUser", ParamType: "int", Condition: "!= _|_"},
|
||||
{ParamName: "runAsGroup", TargetField: "runAsGroup", ParamType: "int", Condition: "!= _|_"},
|
||||
},
|
||||
SubGroups: []defkit.PatchContainerGroup{
|
||||
{
|
||||
TargetField: "capabilities",
|
||||
Fields: []defkit.PatchContainerField{
|
||||
{ParamName: "addCapabilities", TargetField: "add", ParamType: "[...string]", Condition: "!= _|_"},
|
||||
{ParamName: "dropCapabilities", TargetField: "drop", ParamType: "[...string]", Condition: "!= _|_"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Fields with != _|_ condition should use optional syntax (field?: type), not *null | type
|
||||
Expect(cue).To(ContainSubstring(`runAsUser?: int`))
|
||||
Expect(cue).To(ContainSubstring(`runAsGroup?: int`))
|
||||
Expect(cue).To(ContainSubstring(`addCapabilities?: [...string]`))
|
||||
Expect(cue).To(ContainSubstring(`dropCapabilities?: [...string]`))
|
||||
|
||||
// Must NOT have *null | type for these fields
|
||||
Expect(cue).NotTo(ContainSubstring(`runAsUser: *null | int`))
|
||||
Expect(cue).NotTo(ContainSubstring(`runAsGroup: *null | int`))
|
||||
Expect(cue).NotTo(ContainSubstring(`addCapabilities: *null | [...string]`))
|
||||
Expect(cue).NotTo(ContainSubstring(`dropCapabilities: *null | [...string]`))
|
||||
|
||||
// Fields with explicit defaults should still use default syntax
|
||||
Expect(cue).To(ContainSubstring(`privileged: *false | bool`))
|
||||
|
||||
// The PatchContainer body should still have conditional blocks for these fields
|
||||
Expect(cue).To(ContainSubstring(`if _params.runAsUser != _|_`))
|
||||
Expect(cue).To(ContainSubstring(`if _params.runAsGroup != _|_`))
|
||||
Expect(cue).To(ContainSubstring(`if _params.addCapabilities != _|_`))
|
||||
Expect(cue).To(ContainSubstring(`if _params.dropCapabilities != _|_`))
|
||||
})
|
||||
|
||||
It("should use *empty-string default for string-equality conditions", func() {
|
||||
trait := defkit.NewTrait("image-test").
|
||||
Description("Test string-equality condition default").
|
||||
AppliesTo("deployments.apps").
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.UsePatchContainer(defkit.PatchContainerConfig{
|
||||
ContainerNameParam: "containerName",
|
||||
DefaultToContextName: true,
|
||||
PatchFields: []defkit.PatchContainerField{
|
||||
{ParamName: "image", TargetField: "image", PatchStrategy: "retainKeys"},
|
||||
{ParamName: "imagePullPolicy", TargetField: "imagePullPolicy", PatchStrategy: "retainKeys", Condition: `!= ""`},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// String-equality condition should default to empty string, not null
|
||||
Expect(cue).To(ContainSubstring(`imagePullPolicy: *"" |`))
|
||||
Expect(cue).NotTo(ContainSubstring(`imagePullPolicy: *null |`))
|
||||
})
|
||||
|
||||
It("should map params unconditionally in single-container _params block", func() {
|
||||
trait := defkit.NewTrait("unconditional-test").
|
||||
Description("Test unconditional param mapping").
|
||||
AppliesTo("deployments.apps").
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.UsePatchContainer(defkit.PatchContainerConfig{
|
||||
ContainerNameParam: "containerName",
|
||||
DefaultToContextName: true,
|
||||
AllowMultiple: true,
|
||||
ContainersParam: "containers",
|
||||
PatchFields: []defkit.PatchContainerField{
|
||||
{ParamName: "image", TargetField: "image"},
|
||||
{ParamName: "imagePullPolicy", TargetField: "imagePullPolicy", Condition: `!= ""`},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// In the single-container _params block, all fields should be mapped unconditionally
|
||||
Expect(cue).To(ContainSubstring("image: parameter.image"))
|
||||
Expect(cue).To(ContainSubstring("imagePullPolicy: parameter.imagePullPolicy"))
|
||||
// The conditional should NOT wrap the param mapping in the _params block
|
||||
Expect(cue).NotTo(MatchRegexp(`if parameter\.imagePullPolicy != ""[^}]*\n[^}]*imagePullPolicy: parameter\.imagePullPolicy`))
|
||||
// But the PatchContainer body should still have the condition
|
||||
Expect(cue).To(ContainSubstring(`if _params.imagePullPolicy != ""`))
|
||||
})
|
||||
|
||||
It("should emit *#PatchParams with star default marker in multi-container parameter block", func() {
|
||||
trait := defkit.NewTrait("star-test").
|
||||
Description("Test star in parameter").
|
||||
AppliesTo("deployments.apps").
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.UsePatchContainer(defkit.PatchContainerConfig{
|
||||
ContainerNameParam: "containerName",
|
||||
DefaultToContextName: true,
|
||||
AllowMultiple: true,
|
||||
ContainersParam: "containers",
|
||||
PatchFields: []defkit.PatchContainerField{
|
||||
{ParamName: "image", TargetField: "image"},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Should be *#PatchParams with star (marks single-container as default branch)
|
||||
Expect(cue).To(ContainSubstring("parameter: *#PatchParams | close({"))
|
||||
})
|
||||
|
||||
It("should use custom Description and ContainersDescription", func() {
|
||||
trait := defkit.NewTrait("desc-test").
|
||||
Description("Test custom descriptions").
|
||||
AppliesTo("deployments.apps").
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.UsePatchContainer(defkit.PatchContainerConfig{
|
||||
ContainerNameParam: "containerName",
|
||||
DefaultToContextName: true,
|
||||
AllowMultiple: true,
|
||||
ContainersParam: "containers",
|
||||
ContainersDescription: "Specify the container image for multiple containers",
|
||||
PatchFields: []defkit.PatchContainerField{
|
||||
{ParamName: "image", TargetField: "image", Description: "Specify the image of the container"},
|
||||
{ParamName: "imagePullPolicy", TargetField: "imagePullPolicy", Condition: `!= ""`, Description: "Specify the image pull policy of the container"},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Custom field descriptions
|
||||
Expect(cue).To(ContainSubstring("// +usage=Specify the image of the container"))
|
||||
Expect(cue).To(ContainSubstring("// +usage=Specify the image pull policy of the container"))
|
||||
// Custom containers description
|
||||
Expect(cue).To(ContainSubstring("// +usage=Specify the container image for multiple containers"))
|
||||
})
|
||||
|
||||
It("should not emit duplicate parameter: {} when using PatchContainer", func() {
|
||||
trait := defkit.NewTrait("no-dup-param-test").
|
||||
Description("Test no duplicate parameter block").
|
||||
AppliesTo("deployments.apps").
|
||||
Params(defkit.String("image").Required()).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.UsePatchContainer(defkit.PatchContainerConfig{
|
||||
ContainerNameParam: "containerName",
|
||||
DefaultToContextName: true,
|
||||
PatchFields: []defkit.PatchContainerField{
|
||||
{ParamName: "image", TargetField: "image"},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Only one parameter: line should appear (from PatchContainer)
|
||||
Expect(strings.Count(cue, "parameter:")).To(Equal(1))
|
||||
// Should not have parameter: {}
|
||||
Expect(cue).NotTo(ContainSubstring("parameter: {}"))
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
+268
-20
@@ -439,8 +439,10 @@ func (g *TraitCUEGenerator) GenerateFullDefinition(t *TraitDefinition) string {
|
||||
func (g *TraitCUEGenerator) writeAttributes(sb *strings.Builder, t *TraitDefinition, depth int) {
|
||||
indent := strings.Repeat(g.indent, depth)
|
||||
|
||||
// podDisruptive
|
||||
sb.WriteString(fmt.Sprintf("%spodDisruptive: %v\n", indent, t.IsPodDisruptive()))
|
||||
// podDisruptive (only emit when true, false is the default)
|
||||
if t.IsPodDisruptive() {
|
||||
sb.WriteString(fmt.Sprintf("%spodDisruptive: %v\n", indent, t.IsPodDisruptive()))
|
||||
}
|
||||
|
||||
// stage (if set)
|
||||
if t.GetStage() != "" {
|
||||
@@ -508,12 +510,20 @@ func (g *TraitCUEGenerator) GenerateTemplate(t *TraitDefinition) string {
|
||||
sb.WriteString("template: {\n")
|
||||
|
||||
// Check if using Template API
|
||||
usedPatchContainer := false
|
||||
if t.HasTemplate() {
|
||||
// Check if PatchContainer is configured (it generates its own parameter block)
|
||||
tpl := NewTemplate()
|
||||
t.GetTemplate()(tpl)
|
||||
usedPatchContainer = tpl.GetPatchContainerConfig() != nil
|
||||
|
||||
g.writeUnifiedTemplate(&sb, t, 1)
|
||||
}
|
||||
|
||||
// Generate parameter section
|
||||
sb.WriteString(g.generateParameterBlock(t, 1))
|
||||
// Generate parameter section (skip if PatchContainer already generated it)
|
||||
if !usedPatchContainer {
|
||||
sb.WriteString(g.generateParameterBlock(t, 1))
|
||||
}
|
||||
|
||||
// Generate helper type definitions (like #HealthProbe, #labelSelector)
|
||||
gen := NewCUEGenerator()
|
||||
@@ -640,11 +650,164 @@ func (g *TraitCUEGenerator) writePatchResourceOps(sb *strings.Builder, gen *CUEG
|
||||
}
|
||||
}
|
||||
|
||||
// Build a tree structure from operations (similar to component resources)
|
||||
tree := gen.buildFieldTree(ops)
|
||||
// Normalize conditional nodes to avoid empty parent structs in patches
|
||||
gen.liftChildConditions(tree)
|
||||
g.writePatchFieldTree(sb, gen, tree, depth)
|
||||
// Separate IfBlocks from other ops
|
||||
var bareOps []ResourceOp
|
||||
var ifBlocks []*IfBlock
|
||||
for _, op := range ops {
|
||||
if ib, ok := op.(*IfBlock); ok {
|
||||
ifBlocks = append(ifBlocks, ib)
|
||||
} else {
|
||||
bareOps = append(bareOps, op)
|
||||
}
|
||||
}
|
||||
|
||||
// If 0 or 1 IfBlock, use existing merged approach (no path conflicts possible)
|
||||
if len(ifBlocks) <= 1 {
|
||||
tree := gen.buildFieldTree(ops)
|
||||
gen.liftChildConditions(tree)
|
||||
g.writePatchFieldTree(sb, gen, tree, depth)
|
||||
return
|
||||
}
|
||||
|
||||
// Multiple IfBlocks: process each separately to avoid path conflicts
|
||||
// when different IfBlocks target the same field paths with different conditions.
|
||||
prefix := g.findIfBlockCommonPrefix(ifBlocks, bareOps)
|
||||
var prefixParts []string
|
||||
if prefix != "" {
|
||||
prefixParts = strings.Split(prefix, ".")
|
||||
}
|
||||
|
||||
// Write the common prefix inline (e.g., "spec: ")
|
||||
for _, p := range prefixParts {
|
||||
sb.WriteString(fmt.Sprintf("%s: ", p))
|
||||
}
|
||||
|
||||
innerDepth := depth + len(prefixParts)
|
||||
|
||||
// Open block for the children
|
||||
sb.WriteString("{\n")
|
||||
|
||||
indent := strings.Repeat(g.indent, innerDepth+1)
|
||||
first := true
|
||||
|
||||
// Render bare ops first (if any)
|
||||
if len(bareOps) > 0 {
|
||||
bareTree := gen.buildFieldTree(bareOps)
|
||||
// Navigate to subtree below common prefix
|
||||
subtree := bareTree
|
||||
for _, p := range prefixParts {
|
||||
if child, ok := subtree.children[p]; ok {
|
||||
subtree = child
|
||||
}
|
||||
}
|
||||
gen.liftChildConditions(subtree)
|
||||
for _, key := range subtree.childOrder {
|
||||
node := subtree.children[key]
|
||||
sb.WriteString(indent)
|
||||
g.writePatchFieldNode(sb, gen, key, node, innerDepth+1)
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
first = false
|
||||
}
|
||||
|
||||
// Render each IfBlock as a separate subtree
|
||||
for _, ib := range ifBlocks {
|
||||
if !first {
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
|
||||
// Build tree from inner ops only (without the outer If condition)
|
||||
innerTree := gen.buildFieldTree(ib.Ops())
|
||||
|
||||
// Navigate to subtree below common prefix
|
||||
subtree := innerTree
|
||||
for _, p := range prefixParts {
|
||||
if child, ok := subtree.children[p]; ok {
|
||||
subtree = child
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize conditional nodes in the subtree
|
||||
gen.liftChildConditions(subtree)
|
||||
|
||||
// Emit: if condition { ... }
|
||||
condStr := gen.conditionToCUE(ib.Cond())
|
||||
sb.WriteString(fmt.Sprintf("%sif %s {\n", indent, condStr))
|
||||
|
||||
// Render subtree children inside the if block
|
||||
innerBlockIndent := strings.Repeat(g.indent, innerDepth+2)
|
||||
for _, key := range subtree.childOrder {
|
||||
node := subtree.children[key]
|
||||
sb.WriteString(innerBlockIndent)
|
||||
g.writePatchFieldNode(sb, gen, key, node, innerDepth+2)
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("%s}", indent))
|
||||
first = false
|
||||
}
|
||||
|
||||
// Close outer block
|
||||
sb.WriteString(fmt.Sprintf("\n%s}", strings.Repeat(g.indent, innerDepth)))
|
||||
}
|
||||
|
||||
// findIfBlockCommonPrefix finds the longest common path prefix across all IfBlocks and bare ops.
|
||||
// For example, if all paths start with "spec.", the prefix is "spec".
|
||||
func (g *TraitCUEGenerator) findIfBlockCommonPrefix(ifBlocks []*IfBlock, bareOps []ResourceOp) string {
|
||||
var allPaths []string
|
||||
|
||||
for _, ib := range ifBlocks {
|
||||
for _, op := range ib.Ops() {
|
||||
switch o := op.(type) {
|
||||
case *SetOp:
|
||||
allPaths = append(allPaths, o.Path())
|
||||
case *SetIfOp:
|
||||
allPaths = append(allPaths, o.Path())
|
||||
case *PatchStrategyAnnotationOp:
|
||||
allPaths = append(allPaths, o.Path())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, op := range bareOps {
|
||||
switch o := op.(type) {
|
||||
case *SetOp:
|
||||
allPaths = append(allPaths, o.Path())
|
||||
case *SetIfOp:
|
||||
allPaths = append(allPaths, o.Path())
|
||||
}
|
||||
}
|
||||
|
||||
if len(allPaths) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
firstParts := strings.Split(allPaths[0], ".")
|
||||
commonLen := len(firstParts)
|
||||
|
||||
for _, p := range allPaths[1:] {
|
||||
parts := strings.Split(p, ".")
|
||||
minLen := commonLen
|
||||
if len(parts) < minLen {
|
||||
minLen = len(parts)
|
||||
}
|
||||
matched := 0
|
||||
for i := 0; i < minLen; i++ {
|
||||
if parts[i] != firstParts[i] {
|
||||
break
|
||||
}
|
||||
matched++
|
||||
}
|
||||
commonLen = matched
|
||||
}
|
||||
|
||||
if commonLen == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Don't include leaf-level parts — only include the common ancestor path
|
||||
// (at least one level must remain below the prefix for each IfBlock)
|
||||
return strings.Join(firstParts[:commonLen], ".")
|
||||
}
|
||||
|
||||
// writePatchFieldTree writes a field tree as CUE patch syntax.
|
||||
@@ -684,11 +847,21 @@ func (g *TraitCUEGenerator) writePatchFieldNode(sb *strings.Builder, gen *CUEGen
|
||||
return
|
||||
}
|
||||
|
||||
// Handle SpreadAll operations (array constraint patches)
|
||||
if node.spreadAll != nil {
|
||||
g.writeSpreadAllOp(sb, gen, key, node.spreadAll, node.cond, depth)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle conditional fields
|
||||
if node.cond != nil {
|
||||
condStr := gen.conditionToCUE(node.cond)
|
||||
sb.WriteString(fmt.Sprintf("if %s {\n", condStr))
|
||||
innerIndent := strings.Repeat(g.indent, depth+1)
|
||||
// Emit patchStrategy annotation inside the if block
|
||||
if node.patchStrategy != "" {
|
||||
sb.WriteString(fmt.Sprintf("%s// +patchStrategy=%s\n", innerIndent, node.patchStrategy))
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s%s: ", innerIndent, key))
|
||||
if len(node.children) > 0 {
|
||||
g.writePatchFieldTreeFromChildren(sb, gen, node, depth+1)
|
||||
@@ -701,6 +874,9 @@ func (g *TraitCUEGenerator) writePatchFieldNode(sb *strings.Builder, gen *CUEGen
|
||||
}
|
||||
|
||||
// Regular field
|
||||
if node.patchStrategy != "" {
|
||||
sb.WriteString(fmt.Sprintf("// +patchStrategy=%s\n%s", node.patchStrategy, strings.Repeat(g.indent, depth)))
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s: ", key))
|
||||
if len(node.children) > 0 {
|
||||
g.writePatchFieldTreeFromChildren(sb, gen, node, depth)
|
||||
@@ -741,9 +917,9 @@ func (g *TraitCUEGenerator) hasConditionalDescendant(node *fieldNode) bool {
|
||||
if node.cond != nil {
|
||||
return true
|
||||
}
|
||||
// PatchKey and ForEach operations require block format because they need
|
||||
// to write comments or special syntax that can't appear inline
|
||||
if node.patchKey != nil || node.forEach != nil {
|
||||
// PatchKey, ForEach, and patchStrategy annotations require block format
|
||||
// because they need to write comments or special syntax that can't appear inline
|
||||
if node.patchKey != nil || node.forEach != nil || node.patchStrategy != "" {
|
||||
return true
|
||||
}
|
||||
for _, child := range node.children {
|
||||
@@ -845,6 +1021,57 @@ func (g *TraitCUEGenerator) writePatchKeyOp(sb *strings.Builder, gen *CUEGenerat
|
||||
}
|
||||
}
|
||||
|
||||
// writeSpreadAllOp writes a SpreadAll operation as CUE.
|
||||
// Generates: path: [...{element}]
|
||||
// If cond is provided, wraps in: if cond { ... }
|
||||
func (g *TraitCUEGenerator) writeSpreadAllOp(sb *strings.Builder, gen *CUEGenerator, key string, op *SpreadAllOp, cond Condition, depth int) {
|
||||
indent := strings.Repeat(g.indent, depth)
|
||||
|
||||
writeElements := func(sb *strings.Builder, elemDepth int) {
|
||||
for i, elem := range op.Elements() {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
if arrElem, ok := elem.(*ArrayElement); ok {
|
||||
// Build a field tree from the element's ops for proper nesting
|
||||
tree := gen.buildFieldTree(arrElem.Ops())
|
||||
gen.liftChildConditions(tree)
|
||||
// Also add direct field assignments
|
||||
for fk, fv := range arrElem.Fields() {
|
||||
gen.insertIntoTree(tree, fk, fv, nil)
|
||||
}
|
||||
sb.WriteString("...{\n")
|
||||
// Write tree children with explicit indentation
|
||||
// (avoid writePatchFieldTree's single-child inline optimization)
|
||||
innerIndent := strings.Repeat(g.indent, elemDepth+1)
|
||||
for _, tk := range tree.childOrder {
|
||||
tn := tree.children[tk]
|
||||
sb.WriteString(innerIndent)
|
||||
g.writePatchFieldNode(sb, gen, tk, tn, elemDepth+1)
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s}", strings.Repeat(g.indent, elemDepth)))
|
||||
} else {
|
||||
sb.WriteString("...")
|
||||
sb.WriteString(gen.valueToCUE(elem))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cond != nil {
|
||||
condStr := gen.conditionToCUE(cond)
|
||||
sb.WriteString(fmt.Sprintf("if %s {\n", condStr))
|
||||
sb.WriteString(fmt.Sprintf("%s\t%s: [", indent, key))
|
||||
writeElements(sb, depth+1)
|
||||
sb.WriteString("]\n")
|
||||
sb.WriteString(fmt.Sprintf("%s}", indent))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%s: [", key))
|
||||
writeElements(sb, depth)
|
||||
sb.WriteString("]")
|
||||
}
|
||||
}
|
||||
|
||||
// writeTraitResourceOutput writes a resource as CUE for trait outputs.
|
||||
// This handles OutputsIf conditions and VersionConditionals, which the old
|
||||
// writeResourceOutput method did not support.
|
||||
@@ -1003,6 +1230,9 @@ func (g *TraitCUEGenerator) writePatchContainerPattern(sb *strings.Builder, conf
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
||||
} else {
|
||||
if config.PatchStrategy != "" {
|
||||
sb.WriteString(fmt.Sprintf("%s// +patchStrategy=%s\n", indent, config.PatchStrategy))
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%spatch: spec: template: spec: {\n", indent))
|
||||
|
||||
// Determine the multi-container parameter name
|
||||
@@ -1107,7 +1337,11 @@ func (g *TraitCUEGenerator) writePatchContainerPattern(sb *strings.Builder, conf
|
||||
}
|
||||
case config.AllowMultiple && multiParam != "":
|
||||
sb.WriteString(fmt.Sprintf("%sparameter: *#PatchParams | close({\n", indent))
|
||||
sb.WriteString(fmt.Sprintf("%s// +usage=Specify the settings for multiple containers\n", innerIndent))
|
||||
containersDesc := config.ContainersDescription
|
||||
if containersDesc == "" {
|
||||
containersDesc = "Specify the settings for multiple containers"
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s// +usage=%s\n", innerIndent, containersDesc))
|
||||
sb.WriteString(fmt.Sprintf("%s%s: [...#PatchParams]\n", innerIndent, multiParam))
|
||||
sb.WriteString(fmt.Sprintf("%s})\n", indent))
|
||||
default:
|
||||
@@ -1120,7 +1354,11 @@ func (g *TraitCUEGenerator) writePatchContainerPattern(sb *strings.Builder, conf
|
||||
|
||||
// writePatchParamField writes a single field in the #PatchParams schema.
|
||||
func (g *TraitCUEGenerator) writePatchParamField(sb *strings.Builder, field PatchContainerField, indent string) {
|
||||
sb.WriteString(fmt.Sprintf("%s// +usage=Specify the %s for the container\n", indent, field.ParamName))
|
||||
desc := field.Description
|
||||
if desc == "" {
|
||||
desc = fmt.Sprintf("Specify the %s of the container", field.ParamName)
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s// +usage=%s\n", indent, desc))
|
||||
|
||||
// Determine the type string
|
||||
typeStr := field.ParamType
|
||||
@@ -1138,17 +1376,24 @@ func (g *TraitCUEGenerator) writePatchParamField(sb *strings.Builder, field Patc
|
||||
}
|
||||
}
|
||||
|
||||
// Determine default value
|
||||
// Determine default value and optionality
|
||||
defaultVal := field.ParamDefault
|
||||
optional := ""
|
||||
if defaultVal == "" && field.Condition != "" {
|
||||
// Has condition, likely optional - default to null
|
||||
defaultVal = "null"
|
||||
// Has condition, likely optional - choose appropriate default
|
||||
if field.Condition == "!= \"\"" {
|
||||
// String-equality condition: default to empty string
|
||||
defaultVal = "\"\""
|
||||
} else {
|
||||
// Non-string condition (e.g. != _|_): make field optional
|
||||
optional = "?"
|
||||
}
|
||||
}
|
||||
|
||||
if defaultVal != "" {
|
||||
sb.WriteString(fmt.Sprintf("%s%s: *%s | %s\n", indent, field.ParamName, defaultVal, typeStr))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%s%s: %s\n", indent, field.ParamName, typeStr))
|
||||
sb.WriteString(fmt.Sprintf("%s%s%s: %s\n", indent, field.ParamName, optional, typeStr))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1207,9 +1452,12 @@ func (g *TraitCUEGenerator) writePatchContainerGroup(sb *strings.Builder, group
|
||||
}
|
||||
|
||||
// writePatchParamMapping writes a parameter mapping in the patch block.
|
||||
// Fields with a "!= _|_" existence condition and no ParamDefault are truly optional
|
||||
// (defined as field?: type in the schema) and need conditional guards to avoid
|
||||
// propagating _|_ when the parameter is unset. Fields with defaults or value-based
|
||||
// conditions (like '!= ""') always have a value and can be mapped unconditionally.
|
||||
func (g *TraitCUEGenerator) writePatchParamMapping(sb *strings.Builder, field PatchContainerField, indent string, prefix string) {
|
||||
if field.Condition != "" {
|
||||
// Optional field - map conditionally
|
||||
if field.Condition == "!= _|_" && field.ParamDefault == "" {
|
||||
sb.WriteString(fmt.Sprintf("%sif %s%s %s {\n", indent, prefix, field.ParamName, field.Condition))
|
||||
sb.WriteString(fmt.Sprintf("%s\t%s: %s%s\n", indent, field.ParamName, prefix, field.ParamName))
|
||||
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
||||
|
||||
@@ -17,6 +17,8 @@ limitations under the License.
|
||||
package defkit_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
@@ -185,8 +187,8 @@ parameter: #PatchParams
|
||||
Expect(cue).To(ContainSubstring(`scaler: {`))
|
||||
Expect(cue).To(ContainSubstring(`type: "trait"`))
|
||||
Expect(cue).To(ContainSubstring(`description: "Scale workloads"`))
|
||||
Expect(cue).To(ContainSubstring(`attributes: {`))
|
||||
Expect(cue).To(ContainSubstring(`podDisruptive: false`))
|
||||
// podDisruptive: false is not emitted (it's the default)
|
||||
Expect(cue).NotTo(ContainSubstring(`podDisruptive: false`))
|
||||
Expect(cue).To(ContainSubstring(`appliesToWorkloads: ["deployments.apps"]`))
|
||||
})
|
||||
|
||||
@@ -555,5 +557,822 @@ template: {
|
||||
|
||||
Expect(cue).To(ContainSubstring("patch:"))
|
||||
})
|
||||
|
||||
It("should generate Optional field guards in Map comprehension", func() {
|
||||
items := defkit.Array("items").WithFields(
|
||||
defkit.String("name").Required(),
|
||||
defkit.String("label"),
|
||||
defkit.Int("priority"),
|
||||
)
|
||||
trait := defkit.NewTrait("optional-test").
|
||||
Description("Test optional fields").
|
||||
AppliesTo("deployments.apps").
|
||||
Params(items).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.Patch().
|
||||
SetIf(items.IsSet(), "spec.template.spec.items",
|
||||
defkit.From(items).Map(defkit.FieldMap{
|
||||
"name": defkit.F("name"),
|
||||
"label": defkit.Optional("label"),
|
||||
"priority": defkit.Optional("priority"),
|
||||
}))
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Required field should use direct access
|
||||
Expect(cue).To(ContainSubstring("name: v.name"))
|
||||
// Optional fields should have if guards
|
||||
Expect(cue).To(ContainSubstring("if v.label != _|_"))
|
||||
Expect(cue).To(ContainSubstring("label: v.label"))
|
||||
Expect(cue).To(ContainSubstring("if v.priority != _|_"))
|
||||
Expect(cue).To(ContainSubstring("priority: v.priority"))
|
||||
// Should NOT contain top-level underscore for optional fields
|
||||
Expect(cue).NotTo(ContainSubstring("label: _"))
|
||||
Expect(cue).NotTo(ContainSubstring("priority: _"))
|
||||
})
|
||||
|
||||
It("should generate If/EndIf with SetIf using sub-field conditions", func() {
|
||||
parent := defkit.Map("parent").WithFields(
|
||||
defkit.Array("required").WithFields(
|
||||
defkit.String("key").Required(),
|
||||
),
|
||||
defkit.Array("preferred").WithFields(
|
||||
defkit.Int("weight").Required(),
|
||||
),
|
||||
)
|
||||
trait := defkit.NewTrait("if-subfield-test").
|
||||
Description("Test If/EndIf with sub-field conditions").
|
||||
AppliesTo("deployments.apps").
|
||||
Params(parent).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.Patch().
|
||||
If(parent.IsSet()).
|
||||
SetIf(parent.Field("required").IsSet(),
|
||||
"spec.requiredItems",
|
||||
defkit.From(defkit.ParamPath("parent.required")).Map(defkit.FieldMap{
|
||||
"key": defkit.F("key"),
|
||||
})).
|
||||
SetIf(parent.Field("preferred").IsSet(),
|
||||
"spec.preferredItems",
|
||||
defkit.From(defkit.ParamPath("parent.preferred")).Map(defkit.FieldMap{
|
||||
"weight": defkit.F("weight"),
|
||||
})).
|
||||
EndIf()
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Both conditions should appear (AND-combined by the field tree)
|
||||
Expect(cue).To(ContainSubstring(`parameter["parent"] != _|_`))
|
||||
Expect(cue).To(ContainSubstring("parameter.parent.required != _|_"))
|
||||
Expect(cue).To(ContainSubstring("parameter.parent.preferred != _|_"))
|
||||
// Verify the For comprehension sources
|
||||
Expect(cue).To(ContainSubstring("for v in parameter.parent.required"))
|
||||
Expect(cue).To(ContainSubstring("for v in parameter.parent.preferred"))
|
||||
Expect(cue).To(ContainSubstring("key: v.key"))
|
||||
Expect(cue).To(ContainSubstring("weight: v.weight"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Let Bindings with ForEachMap", func() {
|
||||
It("should generate let binding with ForEachMap and LetVariable references", func() {
|
||||
trait := defkit.NewTrait("let-foreach-test").
|
||||
Description("Test let binding with ForEachMap").
|
||||
AppliesTo("*").
|
||||
PodDisruptive(true).
|
||||
Param(defkit.DynamicMap().ValueTypeUnion("string | null")).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.PatchStrategy("jsonMergePatch")
|
||||
tpl.AddLetBinding("content", defkit.ForEachMap())
|
||||
tpl.Patch().
|
||||
Set("metadata.annotations", defkit.LetVariable("content"))
|
||||
tpl.Patch().
|
||||
If(defkit.And(
|
||||
defkit.ContextOutput().HasPath("spec"),
|
||||
defkit.ContextOutput().HasPath("spec.template"),
|
||||
)).
|
||||
Set("spec.template.metadata.annotations", defkit.LetVariable("content")).
|
||||
EndIf()
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Let binding should appear before the patch block
|
||||
Expect(cue).To(ContainSubstring("let content ="))
|
||||
// ForEachMap should render as a struct comprehension
|
||||
Expect(cue).To(ContainSubstring("for k, v in parameter"))
|
||||
Expect(cue).To(ContainSubstring("(k): v"))
|
||||
// Let variable references should appear in the patch
|
||||
Expect(cue).To(ContainSubstring("metadata: annotations: content"))
|
||||
// Conditional block should reference the let variable
|
||||
Expect(cue).To(ContainSubstring("annotations: content"))
|
||||
Expect(cue).To(ContainSubstring("context.output.spec != _|_"))
|
||||
Expect(cue).To(ContainSubstring("context.output.spec.template != _|_"))
|
||||
// The for-each comprehension should NOT be inlined at each usage site
|
||||
// It should only appear once in the let binding
|
||||
Expect(strings.Count(cue, "for k, v in parameter")).To(Equal(1))
|
||||
// Patch strategy should be present
|
||||
Expect(cue).To(ContainSubstring("// +patchStrategy=jsonMergePatch"))
|
||||
})
|
||||
|
||||
It("should render ForEachMap with custom source and vars via valueToCUE", func() {
|
||||
trait := defkit.NewTrait("custom-foreach-test").
|
||||
Description("Test custom ForEachMap rendering").
|
||||
AppliesTo("deployments.apps").
|
||||
Param(defkit.DynamicMap().ValueTypeUnion("string | null")).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.AddLetBinding("labelContent",
|
||||
defkit.ForEachMap().Over("parameter.labels").WithVars("key", "val"))
|
||||
tpl.Patch().
|
||||
Set("metadata.labels", defkit.LetVariable("labelContent"))
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Custom variable names and source
|
||||
Expect(cue).To(ContainSubstring("let labelContent ="))
|
||||
Expect(cue).To(ContainSubstring("for key, val in parameter.labels"))
|
||||
Expect(cue).To(ContainSubstring("(key): val"))
|
||||
Expect(cue).To(ContainSubstring("metadata: labels: labelContent"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("PatchKey with ArrayParam (no array wrapping)", func() {
|
||||
It("should emit direct assignment when single element is an ArrayParam", func() {
|
||||
items := defkit.Array("items").WithFields(
|
||||
defkit.String("name").Required(),
|
||||
defkit.String("value").Required(),
|
||||
).Required()
|
||||
|
||||
trait := defkit.NewTrait("patchkey-array-test").
|
||||
Description("Test PatchKey with ArrayParam").
|
||||
AppliesTo("deployments.apps").
|
||||
Params(items).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.Patch().
|
||||
PatchKey("spec.template.spec.items", "name", items)
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Should emit patchKey annotation
|
||||
Expect(cue).To(ContainSubstring("// +patchKey=name"))
|
||||
// Should assign parameter directly, NOT wrapped in [...]
|
||||
Expect(cue).To(ContainSubstring("items: parameter.items"))
|
||||
// Should NOT have array wrapping around the parameter
|
||||
Expect(cue).NotTo(ContainSubstring("[parameter.items]"))
|
||||
})
|
||||
|
||||
It("should still wrap individual ArrayElements in array brackets", func() {
|
||||
elem := defkit.NewArrayElement().
|
||||
Set("name", defkit.Lit("test")).
|
||||
Set("value", defkit.Lit("foo"))
|
||||
|
||||
trait := defkit.NewTrait("patchkey-elem-test").
|
||||
Description("Test PatchKey with ArrayElement").
|
||||
AppliesTo("deployments.apps").
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.Patch().
|
||||
PatchKey("spec.items", "name", elem)
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// ArrayElement should still be wrapped in [...]
|
||||
Expect(cue).To(ContainSubstring("// +patchKey=name"))
|
||||
Expect(cue).To(ContainSubstring("items: [{"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("IsSet bracket notation for optional field checks", func() {
|
||||
It("should generate bracket notation for IsSet conditions", func() {
|
||||
optParam := defkit.Array("env").Of(defkit.ParamTypeString).Optional()
|
||||
|
||||
trait := defkit.NewTrait("isset-bracket-test").
|
||||
Description("Test bracket notation for IsSet").
|
||||
AppliesTo("deployments.apps").
|
||||
Params(optParam).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.Patch().
|
||||
SetIf(optParam.IsSet(), "spec.env", optParam)
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Should use bracket notation: parameter["env"] != _|_
|
||||
Expect(cue).To(ContainSubstring(`parameter["env"] != _|_`))
|
||||
// Should NOT use dot notation for the condition
|
||||
Expect(cue).NotTo(ContainSubstring("parameter.env != _|_"))
|
||||
})
|
||||
|
||||
It("should generate bracket notation for NotSet (negated IsSet) conditions", func() {
|
||||
optParam := defkit.String("debug").Optional()
|
||||
|
||||
trait := defkit.NewTrait("notset-bracket-test").
|
||||
Description("Test bracket notation for NotSet").
|
||||
AppliesTo("deployments.apps").
|
||||
Params(optParam).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.Patch().
|
||||
SetIf(optParam.NotSet(), "spec.debug", defkit.Lit(false))
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Should use bracket notation: parameter["debug"] == _|_
|
||||
Expect(cue).To(ContainSubstring(`parameter["debug"] == _|_`))
|
||||
// Should NOT use dot notation
|
||||
Expect(cue).NotTo(ContainSubstring("parameter.debug == _|_"))
|
||||
})
|
||||
|
||||
It("should use bracket notation in compound conditions", func() {
|
||||
cpu := defkit.String("cpu").Optional()
|
||||
memory := defkit.String("memory").Optional()
|
||||
|
||||
trait := defkit.NewTrait("compound-bracket-test").
|
||||
Description("Test bracket notation in compound conditions").
|
||||
AppliesTo("deployments.apps").
|
||||
Params(cpu, memory).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.Patch().
|
||||
SetIf(defkit.And(cpu.IsSet(), memory.IsSet()),
|
||||
"spec.resources", defkit.Lit("configured"))
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Both parts of the compound condition should use bracket notation
|
||||
Expect(cue).To(ContainSubstring(`parameter["cpu"] != _|_`))
|
||||
Expect(cue).To(ContainSubstring(`parameter["memory"] != _|_`))
|
||||
})
|
||||
})
|
||||
|
||||
Context("PatchStrategyAnnotation", func() {
|
||||
It("should record PatchStrategyAnnotation ops on PatchResource", func() {
|
||||
p := defkit.NewPatchResource()
|
||||
p.PatchStrategyAnnotation("spec.strategy", "retainKeys")
|
||||
|
||||
ops := p.Ops()
|
||||
Expect(ops).To(HaveLen(1))
|
||||
ann, ok := ops[0].(*defkit.PatchStrategyAnnotationOp)
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(ann.Path()).To(Equal("spec.strategy"))
|
||||
Expect(ann.Strategy()).To(Equal("retainKeys"))
|
||||
})
|
||||
|
||||
It("should record PatchStrategyAnnotation inside If block", func() {
|
||||
p := defkit.NewPatchResource()
|
||||
cond := defkit.Eq(defkit.ParameterField("kind"), defkit.Lit("Deployment"))
|
||||
p.If(cond).
|
||||
PatchStrategyAnnotation("spec.strategy", "retainKeys").
|
||||
Set("spec.strategy.type", defkit.ParameterField("strategyType")).
|
||||
EndIf()
|
||||
|
||||
ops := p.Ops()
|
||||
Expect(ops).To(HaveLen(1))
|
||||
ifBlock, ok := ops[0].(*defkit.IfBlock)
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(ifBlock.Ops()).To(HaveLen(2)) // PatchStrategyAnnotation + Set
|
||||
})
|
||||
|
||||
It("should emit patchStrategy annotation in CUE output for unconditional field", func() {
|
||||
strategy := defkit.Struct("strategy").Required().Fields(
|
||||
defkit.Field("type", defkit.ParamTypeString).Default("RollingUpdate"),
|
||||
)
|
||||
|
||||
trait := defkit.NewTrait("annotation-test").
|
||||
Description("Test patchStrategy annotation").
|
||||
AppliesTo("deployments.apps").
|
||||
Params(strategy).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.Patch().
|
||||
PatchStrategyAnnotation("spec.strategy", "retainKeys").
|
||||
Set("spec.strategy.type", defkit.ParameterField("strategy.type"))
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Should contain the annotation comment
|
||||
Expect(cue).To(ContainSubstring("// +patchStrategy=retainKeys"))
|
||||
// Annotation should appear before the field
|
||||
Expect(cue).To(ContainSubstring("strategy: type: parameter.strategy.type"))
|
||||
})
|
||||
|
||||
It("should emit patchStrategy annotation inside conditional block", func() {
|
||||
kind := defkit.String("kind").Default("Deployment").Enum("Deployment", "StatefulSet")
|
||||
strategy := defkit.Struct("strategy").Required().Fields(
|
||||
defkit.Field("type", defkit.ParamTypeString).Default("RollingUpdate"),
|
||||
)
|
||||
|
||||
trait := defkit.NewTrait("cond-annotation-test").
|
||||
Description("Test patchStrategy in conditional block").
|
||||
AppliesTo("deployments.apps").
|
||||
Params(kind, strategy).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.Patch().
|
||||
If(defkit.Eq(kind, defkit.Lit("Deployment"))).
|
||||
PatchStrategyAnnotation("spec.strategy", "retainKeys").
|
||||
Set("spec.strategy.type", defkit.ParameterField("strategy.type")).
|
||||
EndIf()
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Should contain the annotation inside the if block
|
||||
Expect(cue).To(ContainSubstring("// +patchStrategy=retainKeys"))
|
||||
Expect(cue).To(ContainSubstring(`parameter.kind == "Deployment"`))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Condition hoisting", func() {
|
||||
It("should hoist matching conditions from children to parent", func() {
|
||||
enabled := defkit.Bool("enabled")
|
||||
replicas := defkit.Int("replicas")
|
||||
image := defkit.String("image")
|
||||
|
||||
trait := defkit.NewTrait("hoist-test").
|
||||
Description("Test condition hoisting").
|
||||
AppliesTo("deployments.apps").
|
||||
Params(enabled, replicas, image).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
cond := defkit.Eq(enabled, defkit.Lit(true))
|
||||
tpl.Patch().
|
||||
If(cond).
|
||||
Set("spec.replicas", replicas).
|
||||
Set("spec.template.spec.image", image).
|
||||
EndIf()
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// The If condition should appear
|
||||
Expect(cue).To(ContainSubstring(`parameter.enabled == true`))
|
||||
// Both fields should be set inside the condition
|
||||
Expect(cue).To(ContainSubstring("replicas: parameter.replicas"))
|
||||
Expect(cue).To(ContainSubstring("image: parameter.image"))
|
||||
})
|
||||
|
||||
It("should hoist common condition from AND combinations", func() {
|
||||
kind := defkit.String("kind").Default("Deployment")
|
||||
replicas := defkit.Int("replicas")
|
||||
image := defkit.String("image")
|
||||
|
||||
trait := defkit.NewTrait("hoist-and-test").
|
||||
Description("Test AND condition hoisting").
|
||||
AppliesTo("deployments.apps").
|
||||
Params(kind, replicas, image).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
isDeployment := defkit.Eq(kind, defkit.Lit("Deployment"))
|
||||
replicasSet := replicas.IsSet()
|
||||
imageSet := image.IsSet()
|
||||
tpl.Patch().
|
||||
If(isDeployment).
|
||||
SetIf(replicasSet, "spec.replicas", replicas).
|
||||
SetIf(imageSet, "spec.template.spec.image", image).
|
||||
EndIf()
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// The outer condition should appear at the parent level
|
||||
Expect(cue).To(ContainSubstring(`parameter.kind == "Deployment"`))
|
||||
// The inner conditions should remain on the children
|
||||
Expect(cue).To(ContainSubstring(`parameter["replicas"] != _|_`))
|
||||
Expect(cue).To(ContainSubstring(`parameter["image"] != _|_`))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Multiple IfBlocks with overlapping paths", func() {
|
||||
It("should render multiple IfBlocks as separate conditional blocks", func() {
|
||||
kind := defkit.String("kind").Default("Deployment").Enum("Deployment", "StatefulSet", "DaemonSet")
|
||||
strategyType := defkit.String("strategyType").Default("RollingUpdate")
|
||||
maxSurge := defkit.String("maxSurge").Default("25%")
|
||||
partition := defkit.Int("partition").Default(0)
|
||||
|
||||
trait := defkit.NewTrait("multi-ifblock-test").
|
||||
Description("Test multiple IfBlocks").
|
||||
AppliesTo("deployments.apps", "statefulsets.apps", "daemonsets.apps").
|
||||
Params(kind, strategyType, maxSurge, partition).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
isDeployment := defkit.Eq(kind, defkit.Lit("Deployment"))
|
||||
isStatefulSet := defkit.Eq(kind, defkit.Lit("StatefulSet"))
|
||||
|
||||
tpl.Patch().
|
||||
If(isDeployment).
|
||||
PatchStrategyAnnotation("spec.strategy", "retainKeys").
|
||||
Set("spec.strategy.type", strategyType).
|
||||
Set("spec.strategy.rollingUpdate.maxSurge", maxSurge).
|
||||
EndIf().
|
||||
If(isStatefulSet).
|
||||
PatchStrategyAnnotation("spec.updateStrategy", "retainKeys").
|
||||
Set("spec.updateStrategy.type", strategyType).
|
||||
Set("spec.updateStrategy.rollingUpdate.partition", partition).
|
||||
EndIf()
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Should have two separate if blocks (not merged)
|
||||
Expect(cue).To(ContainSubstring(`parameter.kind == "Deployment"`))
|
||||
Expect(cue).To(ContainSubstring(`parameter.kind == "StatefulSet"`))
|
||||
// Each should have its own patchStrategy annotation
|
||||
Expect(strings.Count(cue, "// +patchStrategy=retainKeys")).To(Equal(2))
|
||||
// Deployment uses "strategy", StatefulSet uses "updateStrategy"
|
||||
Expect(cue).To(ContainSubstring("strategy: {"))
|
||||
Expect(cue).To(ContainSubstring("updateStrategy: {"))
|
||||
// Fields should be present
|
||||
Expect(cue).To(ContainSubstring("maxSurge: parameter.maxSurge"))
|
||||
Expect(cue).To(ContainSubstring("partition: parameter.partition"))
|
||||
})
|
||||
|
||||
It("should produce correct k8s-update-strategy-like pattern", func() {
|
||||
targetKind := defkit.String("targetKind").Default("Deployment").Enum("Deployment", "StatefulSet", "DaemonSet")
|
||||
strategy := defkit.Struct("strategy").Required().Fields(
|
||||
defkit.Field("type", defkit.ParamTypeString).Default("RollingUpdate").Enum("RollingUpdate", "Recreate", "OnDelete"),
|
||||
defkit.Field("rollingStrategy", defkit.ParamTypeStruct).
|
||||
Nested(defkit.Struct("rollingStrategy").Fields(
|
||||
defkit.Field("maxSurge", defkit.ParamTypeString).Default("25%"),
|
||||
defkit.Field("maxUnavailable", defkit.ParamTypeString).Default("25%"),
|
||||
defkit.Field("partition", defkit.ParamTypeInt).Default(0),
|
||||
)),
|
||||
)
|
||||
|
||||
trait := defkit.NewTrait("k8s-update-strategy-test").
|
||||
Description("Test k8s-update-strategy pattern").
|
||||
AppliesTo("deployments.apps", "statefulsets.apps", "daemonsets.apps").
|
||||
PodDisruptive(false).
|
||||
Params(targetKind, strategy).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
strategyType := defkit.ParameterField("strategy.type")
|
||||
maxSurge := defkit.ParameterField("strategy.rollingStrategy.maxSurge")
|
||||
maxUnavailable := defkit.ParameterField("strategy.rollingStrategy.maxUnavailable")
|
||||
partition := defkit.ParameterField("strategy.rollingStrategy.partition")
|
||||
|
||||
isDeployment := defkit.Eq(defkit.ParameterField("targetKind"), defkit.Lit("Deployment"))
|
||||
isStatefulSet := defkit.Eq(defkit.ParameterField("targetKind"), defkit.Lit("StatefulSet"))
|
||||
isDaemonSet := defkit.Eq(defkit.ParameterField("targetKind"), defkit.Lit("DaemonSet"))
|
||||
isNotOnDelete := defkit.Ne(strategyType, defkit.Lit("OnDelete"))
|
||||
isNotRecreate := defkit.Ne(strategyType, defkit.Lit("Recreate"))
|
||||
isRollingUpdate := defkit.Eq(strategyType, defkit.Lit("RollingUpdate"))
|
||||
|
||||
tpl.Patch().
|
||||
If(defkit.And(isDeployment, isNotOnDelete)).
|
||||
PatchStrategyAnnotation("spec.strategy", "retainKeys").
|
||||
Set("spec.strategy.type", strategyType).
|
||||
SetIf(isRollingUpdate, "spec.strategy.rollingUpdate.maxSurge", maxSurge).
|
||||
SetIf(isRollingUpdate, "spec.strategy.rollingUpdate.maxUnavailable", maxUnavailable).
|
||||
EndIf().
|
||||
If(defkit.And(isStatefulSet, isNotRecreate)).
|
||||
PatchStrategyAnnotation("spec.updateStrategy", "retainKeys").
|
||||
Set("spec.updateStrategy.type", strategyType).
|
||||
SetIf(isRollingUpdate, "spec.updateStrategy.rollingUpdate.partition", partition).
|
||||
EndIf().
|
||||
If(defkit.And(isDaemonSet, isNotRecreate)).
|
||||
PatchStrategyAnnotation("spec.updateStrategy", "retainKeys").
|
||||
Set("spec.updateStrategy.type", strategyType).
|
||||
SetIf(isRollingUpdate, "spec.updateStrategy.rollingUpdate.maxSurge", maxSurge).
|
||||
SetIf(isRollingUpdate, "spec.updateStrategy.rollingUpdate.maxUnavailable", maxUnavailable).
|
||||
EndIf()
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Three separate if blocks
|
||||
Expect(cue).To(ContainSubstring(`parameter.targetKind == "Deployment" && parameter.strategy.type != "OnDelete"`))
|
||||
Expect(cue).To(ContainSubstring(`parameter.targetKind == "StatefulSet" && parameter.strategy.type != "Recreate"`))
|
||||
Expect(cue).To(ContainSubstring(`parameter.targetKind == "DaemonSet" && parameter.strategy.type != "Recreate"`))
|
||||
// Three patchStrategy annotations
|
||||
Expect(strings.Count(cue, "// +patchStrategy=retainKeys")).To(Equal(3))
|
||||
// RollingUpdate inner conditions
|
||||
Expect(cue).To(ContainSubstring(`parameter.strategy.type == "RollingUpdate"`))
|
||||
// Deployment uses "strategy", others use "updateStrategy"
|
||||
Expect(cue).To(ContainSubstring("strategy: {"))
|
||||
Expect(cue).To(ContainSubstring("updateStrategy: {"))
|
||||
// Correct field assignments
|
||||
Expect(cue).To(ContainSubstring("maxSurge: parameter.strategy.rollingStrategy.maxSurge"))
|
||||
Expect(cue).To(ContainSubstring("maxUnavailable: parameter.strategy.rollingStrategy.maxUnavailable"))
|
||||
Expect(cue).To(ContainSubstring("partition: parameter.strategy.rollingStrategy.partition"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Raw patch block with fluent params and helpers", func() {
|
||||
It("should render raw patch block followed by fluent parameter and helper definitions", func() {
|
||||
postStart := defkit.Map("postStart").WithSchemaRef("Handler")
|
||||
preStop := defkit.Map("preStop").WithSchemaRef("Handler")
|
||||
|
||||
trait := defkit.NewTrait("lifecycle").
|
||||
Description("test").
|
||||
AppliesTo("deployments.apps").
|
||||
PodDisruptive(true).
|
||||
Params(postStart, preStop).
|
||||
Helper("Handler", defkit.Struct("Handler").Fields(
|
||||
defkit.Field("exec", defkit.ParamTypeStruct).
|
||||
Nested(defkit.Struct("exec").Fields(
|
||||
defkit.Field("command", defkit.ParamTypeArray).ArrayOf(defkit.ParamTypeString).Required(),
|
||||
)),
|
||||
)).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.SetRawPatchBlock(`patch: spec: containers: [...{
|
||||
lifecycle: {
|
||||
if parameter.postStart != _|_ {
|
||||
postStart: parameter.postStart
|
||||
}
|
||||
}
|
||||
}]`)
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Raw patch block is rendered
|
||||
Expect(cue).To(ContainSubstring("containers: [...{"))
|
||||
Expect(cue).To(ContainSubstring("lifecycle: {"))
|
||||
Expect(cue).To(ContainSubstring("if parameter.postStart != _|_"))
|
||||
|
||||
// Fluent parameter block is rendered (not skipped)
|
||||
Expect(cue).To(ContainSubstring("parameter: {"))
|
||||
Expect(cue).To(ContainSubstring("postStart?: #Handler"))
|
||||
// CUE formatter may add alignment spaces: preStop?: #Handler
|
||||
Expect(cue).To(ContainSubstring("preStop?:"))
|
||||
Expect(cue).To(MatchRegexp(`preStop\?:\s+#Handler`))
|
||||
|
||||
// Helper definition is rendered (not skipped)
|
||||
// CUE formatter collapses single-field struct to inline form
|
||||
Expect(cue).To(ContainSubstring("#Handler:"))
|
||||
Expect(cue).To(ContainSubstring("command: [...string]"))
|
||||
})
|
||||
|
||||
It("should still use full raw mode when raw parameter block is set", func() {
|
||||
trait := defkit.NewTrait("raw-all").
|
||||
Description("test").
|
||||
AppliesTo("deployments.apps").
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.SetRawPatchBlock(`patch: spec: replicas: parameter.replicas`)
|
||||
tpl.SetRawParameterBlock(`parameter: {
|
||||
replicas: *1 | int
|
||||
}`)
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
Expect(cue).To(ContainSubstring("replicas: parameter.replicas"))
|
||||
Expect(cue).To(ContainSubstring("replicas: *1 | int"))
|
||||
// Should NOT have a duplicate parameter block
|
||||
Expect(strings.Count(cue, "parameter:")).To(Equal(2)) // one in patch ref, one in param block
|
||||
})
|
||||
})
|
||||
|
||||
Context("IntParam helper definition rendering", func() {
|
||||
It("should render constrained int helper with min and max", func() {
|
||||
trait := defkit.NewTrait("int-helper-test").
|
||||
Description("test").
|
||||
AppliesTo("deployments.apps").
|
||||
Helper("Port", defkit.Int("Port").Min(1).Max(65535)).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.SetRawPatchBlock(`patch: spec: port: parameter.port`)
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
Expect(cue).To(ContainSubstring("#Port: int & >=1 & <=65535"))
|
||||
})
|
||||
|
||||
It("should render int helper with only min constraint", func() {
|
||||
trait := defkit.NewTrait("int-min-test").
|
||||
Description("test").
|
||||
AppliesTo("deployments.apps").
|
||||
Helper("Positive", defkit.Int("Positive").Min(0)).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.SetRawPatchBlock(`patch: spec: count: parameter.count`)
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
Expect(cue).To(ContainSubstring("#Positive: int & >=0"))
|
||||
Expect(cue).NotTo(ContainSubstring("<="))
|
||||
})
|
||||
|
||||
It("should render int helper without constraints", func() {
|
||||
trait := defkit.NewTrait("int-bare-test").
|
||||
Description("test").
|
||||
AppliesTo("deployments.apps").
|
||||
Helper("Count", defkit.Int("Count")).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.SetRawPatchBlock(`patch: spec: count: parameter.count`)
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
Expect(cue).To(ContainSubstring("#Count: int"))
|
||||
Expect(cue).NotTo(ContainSubstring(">="))
|
||||
Expect(cue).NotTo(ContainSubstring("<="))
|
||||
})
|
||||
})
|
||||
|
||||
Context("SpreadAll operation", func() {
|
||||
It("should record SpreadAll ops on PatchResource", func() {
|
||||
p := defkit.NewPatchResource()
|
||||
elem := defkit.NewArrayElement().Set("name", defkit.Lit("test"))
|
||||
p.SpreadAll("spec.containers", elem)
|
||||
|
||||
ops := p.Ops()
|
||||
Expect(ops).To(HaveLen(1))
|
||||
sa, ok := ops[0].(*defkit.SpreadAllOp)
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(sa.Path()).To(Equal("spec.containers"))
|
||||
Expect(sa.Elements()).To(HaveLen(1))
|
||||
})
|
||||
|
||||
It("should record SpreadAll inside If block", func() {
|
||||
p := defkit.NewPatchResource()
|
||||
cond := defkit.Eq(defkit.ParameterField("enabled"), defkit.Lit(true))
|
||||
elem := defkit.NewArrayElement().Set("name", defkit.Lit("test"))
|
||||
p.If(cond).
|
||||
SpreadAll("spec.containers", elem).
|
||||
EndIf()
|
||||
|
||||
ops := p.Ops()
|
||||
Expect(ops).To(HaveLen(1))
|
||||
ifBlock, ok := ops[0].(*defkit.IfBlock)
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(ifBlock.Ops()).To(HaveLen(1))
|
||||
_, ok = ifBlock.Ops()[0].(*defkit.SpreadAllOp)
|
||||
Expect(ok).To(BeTrue())
|
||||
})
|
||||
|
||||
It("should render unconditional SpreadAll with simple value", func() {
|
||||
image := defkit.String("image").Required()
|
||||
|
||||
trait := defkit.NewTrait("spreadall-simple-test").
|
||||
Description("Test SpreadAll with simple value").
|
||||
AppliesTo("deployments.apps").
|
||||
Params(image).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
elem := defkit.NewArrayElement().
|
||||
Set("image", image)
|
||||
tpl.Patch().SpreadAll("spec.template.spec.containers", elem)
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
Expect(cue).To(ContainSubstring("containers: [...{"))
|
||||
Expect(cue).To(ContainSubstring("image: parameter.image"))
|
||||
})
|
||||
|
||||
It("should render conditional SpreadAll with ArrayElement SetIf", func() {
|
||||
postStart := defkit.String("postStart")
|
||||
preStop := defkit.String("preStop")
|
||||
|
||||
trait := defkit.NewTrait("spreadall-conditional-test").
|
||||
Description("Test SpreadAll with conditional fields").
|
||||
AppliesTo("deployments.apps").
|
||||
Params(postStart, preStop).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
elem := defkit.NewArrayElement().
|
||||
SetIf(postStart.IsSet(), "lifecycle.postStart.exec.command", postStart).
|
||||
SetIf(preStop.IsSet(), "lifecycle.preStop.exec.command", preStop)
|
||||
tpl.Patch().SpreadAll("spec.template.spec.containers", elem)
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
Expect(cue).To(ContainSubstring("containers: [...{"))
|
||||
Expect(cue).To(ContainSubstring(`parameter["postStart"] != _|_`))
|
||||
Expect(cue).To(ContainSubstring(`parameter["preStop"] != _|_`))
|
||||
})
|
||||
|
||||
It("should render SpreadAll inside an IfBlock", func() {
|
||||
enabled := defkit.Bool("enabled").Default(false)
|
||||
image := defkit.String("image").Required()
|
||||
|
||||
trait := defkit.NewTrait("spreadall-ifblock-test").
|
||||
Description("Test SpreadAll inside IfBlock").
|
||||
AppliesTo("deployments.apps").
|
||||
Params(enabled, image).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
elem := defkit.NewArrayElement().
|
||||
Set("image", image)
|
||||
tpl.Patch().
|
||||
If(defkit.Eq(enabled, defkit.Lit(true))).
|
||||
SpreadAll("spec.template.spec.containers", elem).
|
||||
EndIf()
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
Expect(cue).To(ContainSubstring("parameter.enabled == true"))
|
||||
Expect(cue).To(ContainSubstring("containers: [...{"))
|
||||
Expect(cue).To(ContainSubstring("image: parameter.image"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Multiple IfBlocks with bare ops", func() {
|
||||
It("should render bare ops before conditional IfBlocks", func() {
|
||||
kind := defkit.String("kind").Default("Deployment").Enum("Deployment", "StatefulSet")
|
||||
replicas := defkit.Int("replicas").Default(1)
|
||||
strategyType := defkit.String("strategyType").Default("RollingUpdate")
|
||||
|
||||
trait := defkit.NewTrait("bare-ops-ifblock-test").
|
||||
Description("Test bare ops with multiple IfBlocks").
|
||||
AppliesTo("deployments.apps", "statefulsets.apps").
|
||||
Params(kind, replicas, strategyType).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
isDeployment := defkit.Eq(kind, defkit.Lit("Deployment"))
|
||||
isStatefulSet := defkit.Eq(kind, defkit.Lit("StatefulSet"))
|
||||
|
||||
tpl.Patch().
|
||||
Set("spec.replicas", replicas).
|
||||
If(isDeployment).
|
||||
Set("spec.strategy.type", strategyType).
|
||||
EndIf().
|
||||
If(isStatefulSet).
|
||||
Set("spec.updateStrategy.type", strategyType).
|
||||
EndIf()
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Bare op should be present
|
||||
Expect(cue).To(ContainSubstring("replicas: parameter.replicas"))
|
||||
// Both conditional blocks
|
||||
Expect(cue).To(ContainSubstring(`parameter.kind == "Deployment"`))
|
||||
Expect(cue).To(ContainSubstring(`parameter.kind == "StatefulSet"`))
|
||||
Expect(cue).To(ContainSubstring("strategy: type:"))
|
||||
Expect(cue).To(ContainSubstring("updateStrategy: type:"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("findIfBlockCommonPrefix edge cases", func() {
|
||||
It("should handle IfBlocks with no common prefix", func() {
|
||||
kind := defkit.String("kind").Default("a").Enum("a", "b")
|
||||
|
||||
trait := defkit.NewTrait("no-common-prefix-test").
|
||||
Description("Test no common prefix").
|
||||
AppliesTo("deployments.apps").
|
||||
Params(kind).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.Patch().
|
||||
If(defkit.Eq(kind, defkit.Lit("a"))).
|
||||
Set("spec.strategy.type", defkit.Lit("RollingUpdate")).
|
||||
EndIf().
|
||||
If(defkit.Eq(kind, defkit.Lit("b"))).
|
||||
Set("metadata.labels.version", defkit.Lit("v2")).
|
||||
EndIf()
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// Both conditions should be rendered
|
||||
Expect(cue).To(ContainSubstring(`parameter.kind == "a"`))
|
||||
Expect(cue).To(ContainSubstring(`parameter.kind == "b"`))
|
||||
Expect(cue).To(ContainSubstring("strategy: type:"))
|
||||
Expect(cue).To(ContainSubstring("labels: version:"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Nested array struct in helper definitions", func() {
|
||||
It("should render array field with nested struct as [...{fields}]", func() {
|
||||
trait := defkit.NewTrait("nested-array-test").
|
||||
Description("test").
|
||||
AppliesTo("deployments.apps").
|
||||
Helper("Config", defkit.Struct("Config").Fields(
|
||||
defkit.Field("headers", defkit.ParamTypeArray).
|
||||
Nested(defkit.Struct("headers").Fields(
|
||||
defkit.Field("name", defkit.ParamTypeString).Required(),
|
||||
defkit.Field("value", defkit.ParamTypeString).Required(),
|
||||
)),
|
||||
)).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.SetRawPatchBlock(`patch: spec: config: parameter.config`)
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
// CUE formatter collapses single-field struct to inline form
|
||||
Expect(cue).To(ContainSubstring("#Config: headers?: [...{"))
|
||||
Expect(cue).To(ContainSubstring("name: string"))
|
||||
Expect(cue).To(ContainSubstring("value: string"))
|
||||
})
|
||||
|
||||
It("should render schema ref on fields within helper structs", func() {
|
||||
trait := defkit.NewTrait("schema-ref-test").
|
||||
Description("test").
|
||||
AppliesTo("deployments.apps").
|
||||
Helper("Port", defkit.Int("Port").Min(1).Max(65535)).
|
||||
Helper("Endpoint", defkit.Struct("Endpoint").Fields(
|
||||
defkit.Field("port", defkit.ParamTypeInt).WithSchemaRef("Port").Required(),
|
||||
defkit.Field("host", defkit.ParamTypeString),
|
||||
)).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
tpl.SetRawPatchBlock(`patch: spec: endpoint: parameter.endpoint`)
|
||||
})
|
||||
|
||||
cue := trait.ToCue()
|
||||
|
||||
Expect(cue).To(ContainSubstring("#Port: int & >=1 & <=65535"))
|
||||
Expect(cue).To(ContainSubstring("#Endpoint: {"))
|
||||
Expect(cue).To(ContainSubstring("port: #Port"))
|
||||
Expect(cue).To(ContainSubstring("host?: string"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@ module github.com/kubevela/vela-go-sdk
|
||||
go 1.23.8
|
||||
|
||||
require (
|
||||
github.com/oam-dev/kubevela-core-api v1.7.8-0.20250930174210-fb3adce5e9f6
|
||||
github.com/oam-dev/kubevela-core-api v1.7.8-0.20260116042113-3461775e5e3f
|
||||
|
||||
// for main module
|
||||
github.com/pkg/errors v0.9.1
|
||||
@@ -16,10 +16,13 @@ require (
|
||||
// for sub-module
|
||||
// require github.com/kubevela/vela-go-sdk v0.0.0-20230309022604-cd431bb25a9a
|
||||
|
||||
require github.com/kubevela/workflow v0.6.3-0.20250717221743-56b80cee4121
|
||||
require (
|
||||
github.com/kubevela/pkg v1.9.3-0.20251028181209-ef6824214171
|
||||
github.com/kubevela/workflow v0.6.3-0.20251125110424-924e73add777 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cuelang.org/go v0.9.2 // indirect
|
||||
cuelang.org/go v0.14.1 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||
@@ -58,7 +61,6 @@ require (
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.10 // indirect
|
||||
github.com/kubevela/pkg v1.9.3-0.20250625225831-a2894a62a307 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
@@ -67,14 +69,14 @@ require (
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/oam-dev/cluster-gateway v1.9.2-0.20250629203450-2b04dd452b7a // indirect
|
||||
github.com/oam-dev/terraform-controller v0.8.0 // indirect
|
||||
github.com/oam-dev/terraform-controller v0.8.1-0.20250707044258-c0557127de25 // indirect
|
||||
github.com/openshift/library-go v0.0.0-20230327085348-8477ec72b725 // indirect
|
||||
github.com/prometheus/client_golang v1.20.5 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/spf13/cobra v1.8.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/cobra v1.9.1 // indirect
|
||||
github.com/spf13/pflag v1.0.7 // indirect
|
||||
github.com/stoewer/go-strcase v1.2.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.16 // indirect
|
||||
@@ -92,14 +94,14 @@ require (
|
||||
go.uber.org/automaxprocs v1.5.3 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.26.0 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/oauth2 v0.29.0 // indirect
|
||||
golang.org/x/sync v0.13.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/term v0.31.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/term v0.33.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/time v0.10.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||
|
||||
@@ -20,7 +20,7 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/crossplane/crossplane-runtime v1.16.0 h1:lz+l0wEB3qowdTmN7t0PZkfuNSvfOoEhQrEYFbYqMow=
|
||||
github.com/crossplane/crossplane-runtime v1.16.0/go.mod h1:Pz2tdGVMF6KDGzHZOkvKro0nKc8EzK0sb/nSA7pH4Dc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -31,8 +31,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk=
|
||||
github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emicklei/proto v1.10.0 h1:pDGyFRVV5RvV+nkBK9iy3q67FBy9Xa7vwrOTE+g5aGw=
|
||||
github.com/emicklei/proto v1.10.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
|
||||
github.com/emicklei/proto v1.14.2 h1:wJPxPy2Xifja9cEMrcA/g08art5+7CGJNFNk35iXC1I=
|
||||
github.com/emicklei/proto v1.14.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
|
||||
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
|
||||
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
@@ -62,8 +62,6 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY=
|
||||
github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
@@ -114,10 +112,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kubevela/cue v0.4.4-0.20221107123854-a976b0e340be h1:0xj/Rh4yVy54mUD2nLmAuN1AYgBkkHxBh4PoLGbIg5g=
|
||||
github.com/kubevela/cue v0.4.4-0.20221107123854-a976b0e340be/go.mod h1:Ya12qn7FZc+LSN0qgEhzEpnzQsvnGHVgoDrqe9i3eNg=
|
||||
github.com/kubevela/pkg v1.9.3-0.20250625225831-a2894a62a307 h1:6vebFO0h5vU/0gSol3l/9KlgZeuZzYhl3/DlDr0jI6E=
|
||||
github.com/kubevela/pkg v1.9.3-0.20250625225831-a2894a62a307/go.mod h1:P1yK32LmSs+NRjGu3Wu45VeCeKgIXiRg4qItN1MbgA8=
|
||||
github.com/kubevela/workflow v0.6.3-0.20250717221743-56b80cee4121 h1:clU2P7FyrhLm1l/xviiLO1Cen00ZI01oOfPxAOoMi0w=
|
||||
github.com/kubevela/workflow v0.6.3-0.20250717221743-56b80cee4121/go.mod h1:79KSLzfgBnJboWgxy5P/1GCc2ZUOLEYlF+vS4xQ3FNo=
|
||||
github.com/kubevela/pkg v1.9.3-0.20251028181209-ef6824214171 h1:Ts3UWI0GNxuGtLlIVy/VHtkzKqSM8JC7seuJs58OzjI=
|
||||
github.com/kubevela/pkg v1.9.3-0.20251028181209-ef6824214171/go.mod h1:EmM4VIyU7KxDmPBq9hG4GpSZbGwiM76/W/8paLBk8wY=
|
||||
github.com/kubevela/workflow v0.6.3-0.20251125110424-924e73add777 h1:WM97lR7pW+ZMdlOxaEq/dhJfKTh65EXvsYq8pYsGnFo=
|
||||
github.com/kubevela/workflow v0.6.3-0.20251125110424-924e73add777/go.mod h1:sBIs7uzPGZgJDMdWpFAYhMFW6mPaxIPd1jW6VWB5QRw=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
@@ -139,10 +137,10 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/oam-dev/cluster-gateway v1.9.2-0.20250629203450-2b04dd452b7a h1:DRcSDrLv1en8j5ESR+LR0feGLuyT9M15HJgiMn3sFZs=
|
||||
github.com/oam-dev/cluster-gateway v1.9.2-0.20250629203450-2b04dd452b7a/go.mod h1:ZIYRoiy4He22db8XiWTAh7QecIt5QWiQNacCZg1zbbY=
|
||||
github.com/oam-dev/kubevela-core-api v1.7.8-0.20250930174210-fb3adce5e9f6 h1:nZVplulZS9P7v6FyELWfLMZv9v6sMWu9+80DHixq7jg=
|
||||
github.com/oam-dev/kubevela-core-api v1.7.8-0.20250930174210-fb3adce5e9f6/go.mod h1:PaR1ddEgEx7gYh3pzlyuIXWOKDEp11fjb+JYvyyg6mQ=
|
||||
github.com/oam-dev/terraform-controller v0.8.0 h1:/881bAELCsceSj+Zh3Nsu8Ym5N7D5Ho0HsWzO+TDc1w=
|
||||
github.com/oam-dev/terraform-controller v0.8.0/go.mod h1:ydc9iHjgLzwuWB+MlE2vRA8gOZsif0T4E2FL34K8CZ4=
|
||||
github.com/oam-dev/kubevela-core-api v1.7.8-0.20260116042113-3461775e5e3f h1:6FT3HZf2x6EOHUnrCb7d3TcOcS1+Ea8wPYA7rW2deDw=
|
||||
github.com/oam-dev/kubevela-core-api v1.7.8-0.20260116042113-3461775e5e3f/go.mod h1:tKPhhyuCK7EADtf4f8GGAB4mtxZYSxgWmdMD6a0p7d8=
|
||||
github.com/oam-dev/terraform-controller v0.8.1-0.20250707044258-c0557127de25 h1:J5byd7UtaHZWDMcFA1VoMos+kbEpe3LcitWVmpcJmdI=
|
||||
github.com/oam-dev/terraform-controller v0.8.1-0.20250707044258-c0557127de25/go.mod h1:+QfZ/EBzdrGZcxGIt/I9OTDwq4kH/+aCD6Mh0yrrGr8=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0=
|
||||
github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
|
||||
@@ -166,19 +164,20 @@ github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20220428173112-74888fd59c2b h1:zd/2RNzIRkoGGMjE+YIsZ85CnDIz672JK2F3Zl4vux4=
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20220428173112-74888fd59c2b/go.mod h1:KjY0wibdYKc4DYkerHSbguaf3JeIPGhNJBp2BNiFH78=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20250627152318-f293424e46b5 h1:WWs1ZFnGobK5ZXNu+N9If+8PDNVB9xAqrib/stUXsV4=
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20250627152318-f293424e46b5/go.mod h1:BnHogPTyzYAReeQLZrOxyxzS739DaTNtTvohVdbENmA=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
|
||||
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -239,8 +238,8 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
@@ -249,34 +248,34 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
||||
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
|
||||
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
|
||||
workflowv1alpha1 "github.com/kubevela/pkg/apis/oam/v1alpha1"
|
||||
|
||||
"github.com/oam-dev/kubevela-core-api/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela-core-api/apis/core.oam.dev/v1beta1"
|
||||
|
||||
@@ -19,7 +19,7 @@ package common
|
||||
import (
|
||||
"github.com/kubevela/vela-go-sdk/pkg/apis"
|
||||
|
||||
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
|
||||
workflowv1alpha1 "github.com/kubevela/pkg/apis/oam/v1alpha1"
|
||||
|
||||
"github.com/oam-dev/kubevela-core-api/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela-core-api/apis/core.oam.dev/v1beta1"
|
||||
|
||||
@@ -17,7 +17,7 @@ limitations under the License.
|
||||
package apis
|
||||
|
||||
import (
|
||||
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
|
||||
workflowv1alpha1 "github.com/kubevela/pkg/apis/oam/v1alpha1"
|
||||
|
||||
"github.com/oam-dev/kubevela-core-api/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela-core-api/apis/core.oam.dev/v1beta1"
|
||||
|
||||
@@ -85,7 +85,7 @@ var (
|
||||
DefinitionKindToStatement = map[string]*j.Statement{
|
||||
v1beta1.ComponentDefinitionKind: j.Qual("common", "ApplicationComponent"),
|
||||
v1beta1.TraitDefinitionKind: j.Qual("common", "ApplicationTrait"),
|
||||
v1beta1.WorkflowStepDefinitionKind: j.Qual("github.com/kubevela/workflow/api/v1alpha1", "WorkflowStep"),
|
||||
v1beta1.WorkflowStepDefinitionKind: j.Qual("github.com/kubevela/pkg/apis/oam/v1alpha1", "WorkflowStep"),
|
||||
v1beta1.PolicyDefinitionKind: j.Qual("v1beta1", "AppPolicy"),
|
||||
}
|
||||
)
|
||||
@@ -447,7 +447,7 @@ func (m *GoDefModifier) genCommonFunc() []*j.Statement {
|
||||
j.Id("Timeout"): j.Id(m.defFuncReceiver).Dot("Base").Dot("Timeout"),
|
||||
j.Id("Meta"): j.Id(m.defFuncReceiver).Dot("Base").Dot("Meta"),
|
||||
}
|
||||
builderDict[j.Id("WorkflowStepBase")] = j.Qual("github.com/kubevela/workflow/api/v1alpha1", "WorkflowStepBase").Values(workflowStepBaseDict)
|
||||
builderDict[j.Id("WorkflowStepBase")] = j.Qual("github.com/kubevela/pkg/apis/oam/v1alpha1", "WorkflowStepBase").Values(workflowStepBaseDict)
|
||||
builderDict[j.Id("SubSteps")] = j.Id("subSteps")
|
||||
} else {
|
||||
builderDict[j.Id("Type")] = j.Add(typeName)
|
||||
@@ -479,7 +479,7 @@ func (m *GoDefModifier) genCommonFunc() []*j.Statement {
|
||||
g.Add(j.For(j.List(j.Id("_"), j.Id("subStep")).Op(":=").Range().Id(m.defFuncReceiver).Dot("Base").Dot("SubSteps")).Block(
|
||||
j.Id("_subSteps").Op("=").Append(j.Id("_subSteps"), j.Id("subStep").Dot("Build").Call()),
|
||||
))
|
||||
g.Add(j.Id("subSteps").Op(":=").Make(j.Index().Qual("github.com/kubevela/workflow/api/v1alpha1", "WorkflowStepBase"), j.Lit(0)))
|
||||
g.Add(j.Id("subSteps").Op(":=").Make(j.Index().Qual("github.com/kubevela/pkg/apis/oam/v1alpha1", "WorkflowStepBase"), j.Lit(0)))
|
||||
g.Add(j.For(j.List(j.Id("_"), j.Id("_s").Op(":=").Range().Id("_subSteps"))).Block(
|
||||
j.Id("subSteps").Op("=").Append(j.Id("subSteps"), j.Id("_s").Dot("WorkflowStepBase")),
|
||||
))
|
||||
@@ -533,7 +533,7 @@ func (m *GoDefModifier) genFromFunc() []*j.Statement {
|
||||
params := DefinitionKindToStatement[kind]
|
||||
if sub {
|
||||
funcName = "FromWorkflowSubStep"
|
||||
params = j.Qual("github.com/kubevela/workflow/api/v1alpha1", "WorkflowStepBase")
|
||||
params = j.Qual("github.com/kubevela/pkg/apis/oam/v1alpha1", "WorkflowStepBase")
|
||||
}
|
||||
return j.Func().
|
||||
Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
|
||||
@@ -581,7 +581,7 @@ func (m *GoDefModifier) genFromFunc() []*j.Statement {
|
||||
j.Return(j.Id(m.defFuncReceiver).Dot("From"+DefinitionKindToPascal[kind]).Call(j.Id("from"))),
|
||||
)
|
||||
fromSubFunc := j.Func().Id("FromWorkflowSubStep").
|
||||
Params(j.Id("from").Qual("github.com/kubevela/workflow/api/v1alpha1", "WorkflowStepBase")).Params(j.Qual("apis", DefinitionKindToPascal[kind]), j.Error()).
|
||||
Params(j.Id("from").Qual("github.com/kubevela/pkg/apis/oam/v1alpha1", "WorkflowStepBase")).Params(j.Qual("apis", DefinitionKindToPascal[kind]), j.Error()).
|
||||
Block(
|
||||
j.Id(m.defFuncReceiver).Op(":=").Op("&").Id(m.defStructName).Values(j.Dict{}),
|
||||
j.Return(j.Id(m.defFuncReceiver).Dot("FromWorkflowSubStep").Call(j.Id("from"))),
|
||||
@@ -677,8 +677,8 @@ func (m *GoDefModifier) genBaseSetterFunc() []*j.Statement {
|
||||
}{
|
||||
v1beta1.ComponentDefinitionKind: {
|
||||
{funcName: "DependsOn", argName: "dependsOn", argType: j.Index().String()},
|
||||
{funcName: "Inputs", argName: "input", argType: j.Qual("github.com/kubevela/workflow/api/v1alpha1", "StepInputs")},
|
||||
{funcName: "Outputs", argName: "output", argType: j.Qual("github.com/kubevela/workflow/api/v1alpha1", "StepOutputs")},
|
||||
{funcName: "Inputs", argName: "input", argType: j.Qual("github.com/kubevela/pkg/apis/oam/v1alpha1", "StepInputs")},
|
||||
{funcName: "Outputs", argName: "output", argType: j.Qual("github.com/kubevela/pkg/apis/oam/v1alpha1", "StepOutputs")},
|
||||
{funcName: "AddDependsOn", argName: "dependsOn", argType: j.String(), isAppend: true, dst: j.Dot("DependsOn")},
|
||||
},
|
||||
v1beta1.WorkflowStepDefinitionKind: {
|
||||
@@ -686,8 +686,8 @@ func (m *GoDefModifier) genBaseSetterFunc() []*j.Statement {
|
||||
{funcName: "Alias", argName: "alias", argType: j.String(), dst: j.Dot("Meta").Dot("Alias")},
|
||||
{funcName: "Timeout", argName: "timeout", argType: j.String()},
|
||||
{funcName: "DependsOn", argName: "dependsOn", argType: j.Index().String()},
|
||||
{funcName: "Inputs", argName: "input", argType: j.Qual("github.com/kubevela/workflow/api/v1alpha1", "StepInputs")},
|
||||
{funcName: "Outputs", argName: "output", argType: j.Qual("github.com/kubevela/workflow/api/v1alpha1", "StepOutputs")},
|
||||
{funcName: "Inputs", argName: "input", argType: j.Qual("github.com/kubevela/pkg/apis/oam/v1alpha1", "StepInputs")},
|
||||
{funcName: "Outputs", argName: "output", argType: j.Qual("github.com/kubevela/pkg/apis/oam/v1alpha1", "StepOutputs")},
|
||||
},
|
||||
}
|
||||
baseFuncs := make([]*j.Statement, 0)
|
||||
|
||||
@@ -123,20 +123,21 @@ template: {
|
||||
|
||||
#podAffinityTerm: {
|
||||
labelSelector?: #labelSelector
|
||||
namespace?: string
|
||||
namespaces?: [...string]
|
||||
topologyKey: string
|
||||
namespaceSelector?: #labelSelector
|
||||
}
|
||||
|
||||
#nodeSelecor: {
|
||||
#nodeSelector: {
|
||||
key: string
|
||||
operator: *"In" | "NotIn" | "Exists" | "DoesNotExist" | "Gt" | "Lt"
|
||||
values?: [...string]
|
||||
}
|
||||
|
||||
#nodeSelectorTerm: {
|
||||
matchExpressions?: [...#nodeSelecor]
|
||||
matchFields?: [...#nodeSelecor]
|
||||
matchExpressions?: [...#nodeSelector]
|
||||
matchFields?: [...#nodeSelector]
|
||||
}
|
||||
|
||||
parameter: {
|
||||
|
||||
Reference in New Issue
Block a user