mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 12:36:34 +00:00
* docs: map existing codebase Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * docs: initialize project Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * Fix: add Short() and Ignore() methods to FloatParam for API completeness FloatParam was the only param type missing Short(string) and Ignore() fluent methods; the underlying baseParam fields already existed. Adds matching test cases following the existing BoolParam/IntParam pattern. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat(defkit): add StatusDetails() to all 4 definition types via baseDefinition Adds statusDetails string field to baseDefinition with setStatusDetails() setter and GetStatusDetails() getter. Exposes StatusDetails(string) fluent method on ComponentDefinition, TraitDefinition, WorkflowStepDefinition, and PolicyDefinition. Updates writeStatus in cuegen.go and the inline status render block in trait.go to render statusDetails as a #"""..."""# CUE block alongside customStatus and healthPolicy when set. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat(01-02): add ForceOptional() to IntParam, FloatParam, EnumParam - IntParam.ForceOptional() *IntParam - FloatParam.ForceOptional() *FloatParam - EnumParam.ForceOptional() *EnumParam - Test cases for all three types following BoolParam/StringParam patterns Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat(02-01): add Labels()/GetLabels() to PolicyDefinition and fix hardcoded labels CUE - Add labels map[string]string field to PolicyDefinition struct - Add Labels() fluent setter and GetLabels() getter - Replace hardcoded labels: {} in PolicyCUEGenerator with nil-conditional sorted output - Add sort import to policy.go Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * test(02-01): add Ginkgo tests for PolicyDefinition.Labels - Test store/return labels - Test sorted key output in CUE - Test labels block omitted when Labels() never called - Test empty labels block when Labels called with empty map Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * docs(02-01): complete Labels on PolicyDefinition plan - SUMMARY.md: plan 02-01 execution results - STATE.md: phase 2 in progress, B1 satisfied, key decisions recorded - ROADMAP.md: phase 2 progress updated (1/3 plans) - REQUIREMENTS.md: B1 marked complete Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat(02-02): add annotations field to baseDefinition with getter/setter Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat(02-03): wire status block rendering into WorkflowStep and Policy CUE generators - Add status: { customStatus, healthPolicy, statusDetails } rendering to WorkflowStepCUEGenerator.GenerateTemplate - Add same status block rendering to PolicyCUEGenerator.GenerateTemplate - Block only emitted when at least one status field is non-empty, matching trait.go pattern Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat(02-02): add Annotations() fluent method to all 4 definition types Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat(02-02): conditional sorted annotations CUE block for component, trait, policy Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * test(02-03): add Ginkgo status block CUE render tests for WorkflowStep and Policy - 5 new tests in WorkflowStepDefinition/Status Block CUE Render context - 5 new tests in PolicyDefinition/Status Block CUE Render context - Rule 3 fix: restore sort import in trait.go (used by annotations render, spuriously flagged) Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat(02-02): merge user annotations before category in workflow step CUE block Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat(02-02): add sorted annotations CUE block to WorkflowStepCUEGenerator - Wire sorted user annotations rendering into WorkflowStep.GenerateFullDefinition - Matches pattern already added to Component, Trait, and Policy generators Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat(02-02): merge user annotations into metadata.annotations in ToYAML for all 4 types Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * docs(02-03): complete Wire Status Block Rendering plan - Add 02-03-SUMMARY.md with full execution record - Update STATE.md: phase 2 complete, new decision recorded - Update ROADMAP.md progress for phase 2 - Mark B4 complete in REQUIREMENTS.md Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * test(02-02): add Annotations Ginkgo tests for all 4 definition types Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * docs(02-02): complete Annotations plan - SUMMARY, STATE, ROADMAP updated Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * docs(03): create phase 3 plans for missing CRD spec fields Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat(03-01): add Version() fluent method and GetVersion() to all 4 definition types - version string field added to baseDefinition struct - setVersion/GetVersion methods on baseDefinition following existing pattern - Version(string) fluent setter on TraitDefinition, ComponentDefinition, PolicyDefinition, WorkflowStepDefinition - TDD RED+GREEN: tests for round-trip pass; CUE/YAML render tests added (fail, to be fixed in next task) Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat(03-02): add ManageWorkload/ControlPlaneOnly/RevisionEnabled to TraitDefinition - Add manageWorkload, controlPlaneOnly, revisionEnabled bool fields to TraitDefinition struct - Add ManageWorkload(), ControlPlaneOnly(), RevisionEnabled() fluent setters - Add IsManageWorkload(), IsControlPlaneOnly(), IsRevisionEnabled() getters - Emit conditionally in ToYAML only when true; ToCue() unaffected - Add 11 round-trip tests covering defaults, setters, and YAML emission Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * test(03-03): add Ginkgo tests for ChildResourceKind accumulator on ComponentDefinition - 7 specs covering nil default, single entry, multi-entry accumulation, selector preservation, ToYAML emit/omit, and chaining Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat(03-02): add ManageHealthCheck to PolicyDefinition - Add manageHealthCheck bool field to PolicyDefinition struct - Add ManageHealthCheck() fluent setter and IsManageHealthCheck() getter - Emit conditionally in ToYAML only when true; ToCue() unaffected - Add 5 round-trip tests covering default, setter, YAML emission, and chaining Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * test(03-03): add Ginkgo tests for PodSpecPath on ComponentDefinition - 5 specs covering empty default, set/get round-trip, ToYAML emit/omit, and chaining; both fields satisfy C5 (childResourceKinds) and C6 (podSpecPath) Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat(03-01): render version in CUE output and spec.version in ToYAML for all 4 definition types - cuegen.go: conditional version emit in ComponentDefinition GenerateFullDefinition (after description) - trait.go: conditional version emit in TraitCUEGenerator.GenerateFullDefinition; spec.version in ToYAML - policy.go: conditional version emit in PolicyCUEGenerator.GenerateFullDefinition; spec.version in ToYAML - workflow_step.go: conditional version emit in WorkflowStepCUEGenerator.GenerateFullDefinition; spec.version in ToYAML - version omitted entirely when not set; TDD GREEN phase complete Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * docs(03-03): complete ChildResourceKind+PodSpecPath plan - SUMMARY, STATE, ROADMAP updated - C5 (childResourceKinds accumulator) and C6 (podSpecPath) requirements satisfied - 12 Ginkgo specs added covering both fields Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * docs(03-02): complete boolean CRD spec fields plan — manageWorkload/controlPlaneOnly/revisionEnabled/manageHealthCheck Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * docs(03-01): complete Version() plan — SUMMARY, STATE, ROADMAP updated - 03-01-SUMMARY.md created documenting Version() on all 4 definition types - STATE.md: 03-01 session log entry added; phase 3 marked complete; decision recorded - ROADMAP.md: phase 3 updated to 3/3 plans executed; status Complete - REQUIREMENTS.md: C1 marked complete Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * docs(04): add gap closure plan 04-03 for vela-go-definitions call sites Closes 17 ArrayOf→Of and 1 FilterPred→Filter call sites broken by the phase 04 renames. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat(phase-4): low-risk renames — SetField→Set, ArrayOf→Of, FilterPred→Filter A1: PolicyTemplate.SetField() renamed to Set() A3: StructField.ArrayOf() renamed to Of() A5: HelperBuilder.FilterPred(Predicate) renamed to Filter(Predicate); HelperBuilder.Filter(Condition) renamed to FilterCond(Condition) All callers within defkit updated. Verification: 894 Ginkgo specs pass, go build exits 0, zero occurrences of old names in pkg/definition/defkit/. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * docs(05-high-impact-renames): create phase plan Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat(phase-5): high-impact renames — Values→Enum, Fields→WithFields A4: EnumParam.Values() renamed to Enum() — aligns with StringParam.Enum() and StructField.Enum(); 10 call sites updated in defkit tests A2: StructParam.Fields() and OneOfVariant.Fields() renamed to WithFields() — aligns with ArrayParam and MapParam; ~50 call sites updated in defkit tests Both repos build clean. go test ./pkg/definition/defkit/... passes. Non-target .Fields() methods (InCondition, StructBuilder, ArrayElement etc.) correctly preserved. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * chore: exclude .planning/ and .claude/ from version control Local development artifacts only — not for upstream. Signed-off-by: Vishal Kumar <vishal210893@gmail.com> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * refactor: adjust formatting for consistency in component and policy definitions Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat: add FilterCond method for filtering items by Condition expression Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat: enhance filter condition handling with AND-composition for multiple filters Signed-off-by: Vishal Kumar <vishal210893@gmail.com> * feat: rename Enum method to Values for consistency in parameter definitions Signed-off-by: Jerrin Francis <jfo@> * feat: simplify labels handling in policy and trait definitions Signed-off-by: Jerrin Francis <jerrinfrancis7@gmail.com> --------- Signed-off-by: Vishal Kumar <vishal210893@gmail.com> Signed-off-by: Jerrin Francis <jfo@> Signed-off-by: Jerrin Francis <jerrinfrancis7@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
922 lines
26 KiB
Go
922 lines
26 KiB
Go
/*
|
|
Copyright 2025 The KubeVela Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package defkit
|
|
|
|
import "fmt"
|
|
|
|
// HelperVar is a type-safe reference to a template-level helper definition.
|
|
// When used in a Resource.Set() call, the CUE generator will emit a reference
|
|
// to the helper name rather than inlining the collection.
|
|
type HelperVar struct {
|
|
name string
|
|
collection Value // The collection operation (CollectionOp or MultiSource)
|
|
template *Template // Back-reference to parent template
|
|
afterOutput bool // If true, helper appears after output: block (used for expose helpers)
|
|
guard Condition // Outer guard condition for list comprehension
|
|
}
|
|
|
|
// Ensure HelperVar implements Value interface
|
|
var _ Value = (*HelperVar)(nil)
|
|
|
|
func (h *HelperVar) value() {}
|
|
func (h *HelperVar) expr() {}
|
|
|
|
// Name returns the helper name.
|
|
func (h *HelperVar) Name() string { return h.name }
|
|
|
|
// Collection returns the underlying collection for inspection and CUE generation.
|
|
func (h *HelperVar) Collection() Value { return h.collection }
|
|
|
|
// IsAfterOutput returns true if this helper should be placed after the output: block.
|
|
func (h *HelperVar) IsAfterOutput() bool { return h.afterOutput }
|
|
|
|
// Guard returns the outer guard condition, if any.
|
|
func (h *HelperVar) Guard() Condition { return h.guard }
|
|
|
|
// String implements Stringer for debugging.
|
|
func (h *HelperVar) String() string {
|
|
return fmt.Sprintf("Helper(%s)", h.name)
|
|
}
|
|
|
|
// NotEmpty returns a condition that checks if len(helper) != 0.
|
|
// This is used for conditional outputs like Service which should only
|
|
// be created when there are exposed ports.
|
|
//
|
|
// Example:
|
|
//
|
|
// exposePorts := tpl.Helper("exposePorts").
|
|
// From(ports).
|
|
// Filter(...).
|
|
// Build()
|
|
// tpl.OutputsIf(exposePorts.NotEmpty(), "service", svc)
|
|
func (h *HelperVar) NotEmpty() Condition {
|
|
return &LenNotZeroCondition{source: h}
|
|
}
|
|
|
|
// LenNotZeroCondition checks if len(value) != 0.
|
|
type LenNotZeroCondition struct {
|
|
baseCondition
|
|
source Value
|
|
}
|
|
|
|
// Source returns the value being checked for non-zero length.
|
|
func (l *LenNotZeroCondition) Source() Value { return l.source }
|
|
|
|
// HelperBuilder provides a fluent API for building template helpers.
|
|
type HelperBuilder struct {
|
|
name string
|
|
template *Template
|
|
source helperSource
|
|
ops []helperOperation
|
|
afterOutput bool // If true, helper appears after output: block
|
|
guard Condition // Outer guard condition (if param != _|_ for v in ...)
|
|
}
|
|
|
|
// helperSource represents the source for a helper (single or multi-source).
|
|
type helperSource interface {
|
|
isHelperSource()
|
|
}
|
|
|
|
// singleSource wraps a single Value source.
|
|
type singleSource struct {
|
|
source Value
|
|
}
|
|
|
|
func (s *singleSource) isHelperSource() {}
|
|
|
|
// multiSourceDef represents multiple named fields from a source.
|
|
type multiSourceDef struct {
|
|
source Value
|
|
fields []string
|
|
}
|
|
|
|
func (m *multiSourceDef) isHelperSource() {}
|
|
|
|
// helperOperation represents an operation to apply in the builder.
|
|
type helperOperation interface {
|
|
isHelperOperation()
|
|
}
|
|
|
|
// eachOp transforms each element with a function.
|
|
type eachOp struct {
|
|
fn func(Value) Value
|
|
}
|
|
|
|
func (e *eachOp) isHelperOperation() {}
|
|
|
|
// helperPickIfOp conditionally picks a field.
|
|
type helperPickIfOp struct {
|
|
cond Condition
|
|
field string
|
|
}
|
|
|
|
func (p *helperPickIfOp) isHelperOperation() {}
|
|
|
|
// mapBySourceOp applies different mappings per source type.
|
|
type mapBySourceOp struct {
|
|
mappings map[string]FieldMap
|
|
}
|
|
|
|
func (m *mapBySourceOp) isHelperOperation() {}
|
|
|
|
// filterCondOp filters items by a condition.
|
|
type filterCondOp struct {
|
|
cond Condition
|
|
}
|
|
|
|
func (f *filterCondOp) isHelperOperation() {}
|
|
|
|
// helperPickOp wraps pickOp for the builder.
|
|
type helperPickOp struct {
|
|
fields []string
|
|
}
|
|
|
|
func (h *helperPickOp) isHelperOperation() {}
|
|
|
|
// helperMapOp wraps mapOp for the builder.
|
|
type helperMapOp struct {
|
|
mappings FieldMap
|
|
}
|
|
|
|
func (h *helperMapOp) isHelperOperation() {}
|
|
|
|
// helperWrapOp wraps wrapOp for the builder.
|
|
type helperWrapOp struct {
|
|
key string
|
|
}
|
|
|
|
func (h *helperWrapOp) isHelperOperation() {}
|
|
|
|
// helperDedupeOp wraps dedupeOp for the builder.
|
|
type helperDedupeOp struct {
|
|
keyField string
|
|
}
|
|
|
|
func (h *helperDedupeOp) isHelperOperation() {}
|
|
|
|
// helperDefaultFieldOp wraps defaultFieldOp for the builder.
|
|
type helperDefaultFieldOp struct {
|
|
field string
|
|
defaultVal FieldValue
|
|
}
|
|
|
|
func (h *helperDefaultFieldOp) isHelperOperation() {}
|
|
|
|
// helperRenameOp wraps renameOp for the builder.
|
|
type helperRenameOp struct {
|
|
from, to string
|
|
}
|
|
|
|
func (h *helperRenameOp) isHelperOperation() {}
|
|
|
|
// helperFilterOp wraps filterOp for the builder.
|
|
type helperFilterOp struct {
|
|
pred Predicate
|
|
}
|
|
|
|
func (h *helperFilterOp) isHelperOperation() {}
|
|
|
|
// Helper starts building a named helper.
|
|
// The returned HelperBuilder provides a fluent API for defining the helper's
|
|
// source and operations.
|
|
//
|
|
// Example:
|
|
//
|
|
// mounts := tpl.Helper("mountsArray").
|
|
// FromFields(Param("volumeMounts"), "pvc", "configMap", "secret").
|
|
// Pick("name", "mountPath").
|
|
// PickIf(IsSet(Field("subPath")), "subPath").
|
|
// Build()
|
|
func (t *Template) Helper(name string) *HelperBuilder {
|
|
return &HelperBuilder{
|
|
name: name,
|
|
template: t,
|
|
ops: make([]helperOperation, 0),
|
|
}
|
|
}
|
|
|
|
// Guard sets an outer condition that wraps the for comprehension.
|
|
// This generates `if condition for v in source` pattern in CUE.
|
|
//
|
|
// Example:
|
|
//
|
|
// exposePorts := tpl.Helper("exposePorts").
|
|
// From(ports).
|
|
// Guard(ports.IsSet()). // generates: if parameter.ports != _|_ for v in ...
|
|
// Filter(FieldEquals("expose", true)).
|
|
// Build()
|
|
func (hb *HelperBuilder) Guard(cond Condition) *HelperBuilder {
|
|
hb.guard = cond
|
|
return hb
|
|
}
|
|
|
|
// From sets a single source for the helper.
|
|
//
|
|
// Example:
|
|
//
|
|
// ports := tpl.Helper("portsArray").
|
|
// From(Param("ports")).
|
|
// Pick("port", "name", "protocol").
|
|
// Build()
|
|
func (hb *HelperBuilder) From(source Value) *HelperBuilder {
|
|
hb.source = &singleSource{source: source}
|
|
return hb
|
|
}
|
|
|
|
// FromFields sets multiple named fields as the source.
|
|
// This is used for patterns like volumeMounts where items come from
|
|
// multiple sub-fields (pvc, configMap, secret, emptyDir, hostPath).
|
|
//
|
|
// Example:
|
|
//
|
|
// mounts := tpl.Helper("mountsArray").
|
|
// FromFields(Param("volumeMounts"), "pvc", "configMap", "secret", "emptyDir", "hostPath").
|
|
// Pick("name", "mountPath").
|
|
// Build()
|
|
func (hb *HelperBuilder) FromFields(source Value, fields ...string) *HelperBuilder {
|
|
hb.source = &multiSourceDef{source: source, fields: fields}
|
|
return hb
|
|
}
|
|
|
|
// FromArray uses a pre-built ArrayBuilder as the helper source.
|
|
// This enables complex iteration patterns (ForEachWithGuardedFiltered)
|
|
// that can't be expressed through the standard From/Filter/Map pipeline.
|
|
func (hb *HelperBuilder) FromArray(ab *ArrayBuilder) *HelperBuilder {
|
|
hb.source = &arrayBuilderSource{builder: ab}
|
|
return hb
|
|
}
|
|
|
|
// FromHelper references another helper as the source.
|
|
// This enables helper chaining for patterns like deduplication.
|
|
//
|
|
// Example:
|
|
//
|
|
// dedupedVolumes := tpl.Helper("deDupVolumesArray").
|
|
// FromHelper(volumesList).
|
|
// Dedupe("name").
|
|
// Build()
|
|
func (hb *HelperBuilder) FromHelper(helper *HelperVar) *HelperBuilder {
|
|
hb.source = &helperRefSource{helper: helper}
|
|
return hb
|
|
}
|
|
|
|
// helperRefSource references another helper.
|
|
type helperRefSource struct {
|
|
helper *HelperVar
|
|
}
|
|
|
|
func (h *helperRefSource) isHelperSource() {}
|
|
|
|
// arrayBuilderSource wraps an ArrayBuilder as a helper source.
|
|
type arrayBuilderSource struct {
|
|
builder *ArrayBuilder
|
|
}
|
|
|
|
func (a *arrayBuilderSource) isHelperSource() {}
|
|
|
|
// Each applies a transformation function to each element.
|
|
// The function receives a Value representing the current item and returns
|
|
// a transformed Value.
|
|
//
|
|
// Example:
|
|
//
|
|
// mounts := tpl.Helper("mountsArray").
|
|
// FromFields(Param("volumeMounts"), "pvc", "configMap").
|
|
// Each(func(v Value) Value {
|
|
// return Struct(
|
|
// Field("name", v.Get("name")),
|
|
// Field("mountPath", v.Get("mountPath")),
|
|
// )
|
|
// }).
|
|
// Build()
|
|
func (hb *HelperBuilder) Each(fn func(Value) Value) *HelperBuilder {
|
|
hb.ops = append(hb.ops, &eachOp{fn: fn})
|
|
return hb
|
|
}
|
|
|
|
// Pick selects only the specified fields from each element.
|
|
//
|
|
// Example:
|
|
//
|
|
// .Pick("name", "mountPath", "subPath")
|
|
func (hb *HelperBuilder) Pick(fields ...string) *HelperBuilder {
|
|
hb.ops = append(hb.ops, &helperPickOp{fields: fields})
|
|
return hb
|
|
}
|
|
|
|
// PickIf conditionally includes a field if the condition is true.
|
|
//
|
|
// Example:
|
|
//
|
|
// .PickIf(IsSet(Field("subPath")), "subPath")
|
|
func (hb *HelperBuilder) PickIf(cond Condition, field string) *HelperBuilder {
|
|
hb.ops = append(hb.ops, &helperPickIfOp{cond: cond, field: field})
|
|
return hb
|
|
}
|
|
|
|
// MapBySource applies different field mappings based on the source field name.
|
|
// This is essential for volumeMounts where pvc, configMap, secret each have
|
|
// different output structures.
|
|
//
|
|
// Example:
|
|
//
|
|
// volumes := tpl.Helper("volumesList").
|
|
// FromFields(Param("volumeMounts"), "pvc", "configMap", "secret", "emptyDir", "hostPath").
|
|
// MapBySource(map[string]FieldMap{
|
|
// "pvc": {"name": FieldRef("name"), "persistentVolumeClaim.claimName": FieldRef("claimName")},
|
|
// "configMap": {"name": FieldRef("name"), "configMap.name": FieldRef("cmName")},
|
|
// "secret": {"name": FieldRef("name"), "secret.secretName": FieldRef("secretName")},
|
|
// "emptyDir": {"name": FieldRef("name"), "emptyDir.medium": FieldRef("medium")},
|
|
// "hostPath": {"name": FieldRef("name"), "hostPath.path": FieldRef("path")},
|
|
// }).
|
|
// Build()
|
|
func (hb *HelperBuilder) MapBySource(mappings map[string]FieldMap) *HelperBuilder {
|
|
hb.ops = append(hb.ops, &mapBySourceOp{mappings: mappings})
|
|
return hb
|
|
}
|
|
|
|
// Map transforms each element using the given field mappings.
|
|
//
|
|
// Example:
|
|
//
|
|
// .Map(FieldMap{"containerPort": FieldRef("port"), "name": FieldRef("name")})
|
|
func (hb *HelperBuilder) Map(mappings FieldMap) *HelperBuilder {
|
|
hb.ops = append(hb.ops, &helperMapOp{mappings: mappings})
|
|
return hb
|
|
}
|
|
|
|
// FilterCond keeps only items matching the condition.
|
|
//
|
|
// Example:
|
|
//
|
|
// exposedPorts := tpl.Helper("exposedPorts").
|
|
// From(Param("ports")).
|
|
// FilterCond(Eq(Field("expose"), true)).
|
|
// Build()
|
|
func (hb *HelperBuilder) FilterCond(cond Condition) *HelperBuilder {
|
|
hb.ops = append(hb.ops, &filterCondOp{cond: cond})
|
|
return hb
|
|
}
|
|
|
|
// Filter keeps only items matching the predicate.
|
|
func (hb *HelperBuilder) Filter(pred Predicate) *HelperBuilder {
|
|
hb.ops = append(hb.ops, &helperFilterOp{pred: pred})
|
|
return hb
|
|
}
|
|
|
|
// Wrap wraps each item value under a new key.
|
|
//
|
|
// Example:
|
|
//
|
|
// // Transforms ["secret1", "secret2"] to [{name: "secret1"}, {name: "secret2"}]
|
|
// .Wrap("name")
|
|
func (hb *HelperBuilder) Wrap(key string) *HelperBuilder {
|
|
hb.ops = append(hb.ops, &helperWrapOp{key: key})
|
|
return hb
|
|
}
|
|
|
|
// Dedupe removes duplicate items by a key field.
|
|
//
|
|
// Example:
|
|
//
|
|
// .Dedupe("name")
|
|
func (hb *HelperBuilder) Dedupe(keyField string) *HelperBuilder {
|
|
hb.ops = append(hb.ops, &helperDedupeOp{keyField: keyField})
|
|
return hb
|
|
}
|
|
|
|
// DefaultField sets a default value for a field if not present.
|
|
//
|
|
// Example:
|
|
//
|
|
// .DefaultField("name", Format("port-%d", FieldRef("port")))
|
|
func (hb *HelperBuilder) DefaultField(field string, defaultVal FieldValue) *HelperBuilder {
|
|
hb.ops = append(hb.ops, &helperDefaultFieldOp{field: field, defaultVal: defaultVal})
|
|
return hb
|
|
}
|
|
|
|
// Rename renames a field in each item.
|
|
//
|
|
// Example:
|
|
//
|
|
// .Rename("port", "containerPort")
|
|
func (hb *HelperBuilder) Rename(from, to string) *HelperBuilder {
|
|
hb.ops = append(hb.ops, &helperRenameOp{from: from, to: to})
|
|
return hb
|
|
}
|
|
|
|
// AfterOutput marks this helper to appear after the output: block in generated CUE.
|
|
// Use this for helpers that are primarily used by outputs: (auxiliary resources)
|
|
// rather than the main output: resource.
|
|
//
|
|
// In KubeVela CUE definitions, the structure is typically:
|
|
//
|
|
// template: {
|
|
// mountsArray: [...] // Primary helpers
|
|
// volumesList: [...]
|
|
// output: {...} // Main resource
|
|
// exposePorts: [...] // Auxiliary helpers (AfterOutput)
|
|
// outputs: {...} // Auxiliary resources
|
|
// parameter: {...}
|
|
// }
|
|
//
|
|
// Example:
|
|
//
|
|
// exposePorts := tpl.Helper("exposePorts").
|
|
// From(ports).
|
|
// Filter(FieldEquals("expose", true)).
|
|
// Map(...).
|
|
// AfterOutput(). // Place after output:
|
|
// Build()
|
|
func (hb *HelperBuilder) AfterOutput() *HelperBuilder {
|
|
hb.afterOutput = true
|
|
return hb
|
|
}
|
|
|
|
// Build finalizes the helper and registers it with the template.
|
|
// Returns a type-safe HelperVar that can be used in Resource.Set() calls.
|
|
func (hb *HelperBuilder) Build() *HelperVar {
|
|
// Build the collection operation from builder state
|
|
collection := hb.buildCollection()
|
|
|
|
// Create typed reference
|
|
helper := &HelperVar{
|
|
name: hb.name,
|
|
collection: collection,
|
|
template: hb.template,
|
|
afterOutput: hb.afterOutput,
|
|
guard: hb.guard,
|
|
}
|
|
|
|
// Register with template
|
|
hb.template.registerHelper(helper)
|
|
|
|
return helper
|
|
}
|
|
|
|
// buildCollection converts builder state to the appropriate collection type.
|
|
func (hb *HelperBuilder) buildCollection() Value {
|
|
switch src := hb.source.(type) {
|
|
case *multiSourceDef:
|
|
// Create MultiSource
|
|
ms := FromFields(src.source, src.fields...)
|
|
hb.applyOpsToMultiSource(ms)
|
|
return ms
|
|
|
|
case *singleSource:
|
|
// Create CollectionOp
|
|
col := Each(src.source)
|
|
hb.applyOpsToCollection(col)
|
|
return col
|
|
|
|
case *helperRefSource:
|
|
// Create CollectionOp from helper reference
|
|
col := Each(src.helper)
|
|
hb.applyOpsToCollection(col)
|
|
return col
|
|
|
|
case *arrayBuilderSource:
|
|
return src.builder
|
|
|
|
default:
|
|
// Default: empty collection
|
|
return Each(Lit([]any{}))
|
|
}
|
|
}
|
|
|
|
// applyOpsToMultiSource applies builder operations to a MultiSource.
|
|
func (hb *HelperBuilder) applyOpsToMultiSource(ms *MultiSource) {
|
|
for _, op := range hb.ops {
|
|
switch o := op.(type) {
|
|
case *helperPickOp:
|
|
ms.Pick(o.fields...)
|
|
case *helperDedupeOp:
|
|
ms.Dedupe(o.keyField)
|
|
case *mapBySourceOp:
|
|
ms.MapBySource(o.mappings)
|
|
case *helperFilterOp:
|
|
ms.Filter(o.pred)
|
|
case *filterCondOp:
|
|
ms.FilterCond(o.cond)
|
|
case *helperPickIfOp:
|
|
// pickIf is handled specially in CUE generation
|
|
ms.ops = append(ms.ops, &pickIfCollectionOp{cond: o.cond, field: o.field})
|
|
}
|
|
}
|
|
}
|
|
|
|
// applyOpsToCollection applies builder operations to a CollectionOp.
|
|
func (hb *HelperBuilder) applyOpsToCollection(col *CollectionOp) {
|
|
for _, op := range hb.ops {
|
|
switch o := op.(type) {
|
|
case *helperPickOp:
|
|
col.Pick(o.fields...)
|
|
case *helperMapOp:
|
|
col.Map(o.mappings)
|
|
case *helperWrapOp:
|
|
col.Wrap(o.key)
|
|
case *helperDedupeOp:
|
|
col.ops = append(col.ops, &dedupeOp{keyField: o.keyField})
|
|
case *helperDefaultFieldOp:
|
|
col.DefaultField(o.field, o.defaultVal)
|
|
case *helperRenameOp:
|
|
col.Rename(o.from, o.to)
|
|
case *helperFilterOp:
|
|
col.Filter(o.pred)
|
|
case *filterCondOp:
|
|
col.FilterCond(o.cond)
|
|
case *helperPickIfOp:
|
|
// pickIf is handled specially in CUE generation
|
|
col.ops = append(col.ops, &pickIfCollectionOp{cond: o.cond, field: o.field})
|
|
case *eachOp:
|
|
// Store the transform function for CUE generation
|
|
col.ops = append(col.ops, &eachTransformOp{fn: o.fn})
|
|
}
|
|
}
|
|
}
|
|
|
|
// pickIfCollectionOp represents a conditional field pick in a collection.
|
|
type pickIfCollectionOp struct {
|
|
cond Condition
|
|
field string
|
|
}
|
|
|
|
func (p *pickIfCollectionOp) apply(items []any) []any {
|
|
// Runtime behavior: conditionally add field
|
|
result := make([]any, 0, len(items))
|
|
for _, item := range items {
|
|
if m, ok := item.(map[string]any); ok {
|
|
newItem := make(map[string]any)
|
|
for k, v := range m {
|
|
newItem[k] = v
|
|
}
|
|
// Check if field exists and condition would be met
|
|
if _, exists := m[p.field]; exists {
|
|
newItem[p.field] = m[p.field]
|
|
}
|
|
result = append(result, newItem)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Cond returns the condition for this pickIf operation.
|
|
func (p *pickIfCollectionOp) Cond() Condition { return p.cond }
|
|
|
|
// Field returns the field name for this pickIf operation.
|
|
func (p *pickIfCollectionOp) Field() string { return p.field }
|
|
|
|
// eachTransformOp stores a transform function for CUE generation.
|
|
type eachTransformOp struct {
|
|
fn func(Value) Value
|
|
}
|
|
|
|
func (e *eachTransformOp) apply(items []any) []any {
|
|
// Transform function is applied during CUE generation, not runtime
|
|
return items
|
|
}
|
|
|
|
// TransformFn returns the transform function.
|
|
func (e *eachTransformOp) TransformFn() func(Value) Value { return e.fn }
|
|
|
|
// --- Struct builder for Each transforms ---
|
|
|
|
// StructBuilder builds a struct value from fields.
|
|
type StructBuilder struct {
|
|
fields []structField
|
|
}
|
|
|
|
type structField struct {
|
|
name string
|
|
value Value
|
|
cond Condition // nil means unconditional
|
|
}
|
|
|
|
// HelperStruct creates a new struct builder with the given fields.
|
|
//
|
|
// Example:
|
|
//
|
|
// HelperStruct(
|
|
// HelperField("name", v.Get("name")),
|
|
// HelperField("mountPath", v.Get("mountPath")),
|
|
// HelperFieldIf(IsSet(v.Get("subPath")), "subPath", v.Get("subPath")),
|
|
// )
|
|
func HelperStruct(fields ...StructFieldDef) *StructBuilder {
|
|
sb := &StructBuilder{fields: make([]structField, 0, len(fields))}
|
|
for _, f := range fields {
|
|
sb.fields = append(sb.fields, structField(f))
|
|
}
|
|
return sb
|
|
}
|
|
|
|
func (s *StructBuilder) value() {}
|
|
func (s *StructBuilder) expr() {}
|
|
|
|
// Fields returns the struct fields for CUE generation.
|
|
func (s *StructBuilder) Fields() []StructFieldDef {
|
|
result := make([]StructFieldDef, len(s.fields))
|
|
for i, f := range s.fields {
|
|
result[i] = StructFieldDef(f)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// StructFieldDef defines a field in a struct.
|
|
type StructFieldDef struct {
|
|
name string
|
|
value Value
|
|
cond Condition
|
|
}
|
|
|
|
// Name returns the field name.
|
|
func (f StructFieldDef) Name() string { return f.name }
|
|
|
|
// Value returns the field value.
|
|
func (f StructFieldDef) Value() Value { return f.value }
|
|
|
|
// Cond returns the condition (nil if unconditional).
|
|
func (f StructFieldDef) Cond() Condition { return f.cond }
|
|
|
|
// HelperField creates an unconditional struct field.
|
|
func HelperField(name string, value Value) StructFieldDef {
|
|
return StructFieldDef{name: name, value: value}
|
|
}
|
|
|
|
// HelperFieldIf creates a conditional struct field.
|
|
func HelperFieldIf(cond Condition, name string, value Value) StructFieldDef {
|
|
return StructFieldDef{name: name, value: value, cond: cond}
|
|
}
|
|
|
|
// --- ItemValue for referencing fields in Each transforms ---
|
|
|
|
// ItemValue represents a reference to the current item in an Each transform.
|
|
type ItemValue struct {
|
|
field string // empty means the whole item, non-empty means a field
|
|
}
|
|
|
|
func (i *ItemValue) value() {}
|
|
func (i *ItemValue) expr() {}
|
|
|
|
// Get returns a reference to a field of this item.
|
|
func (i *ItemValue) Get(field string) *ItemValue {
|
|
return &ItemValue{field: field}
|
|
}
|
|
|
|
// Field returns the field name being accessed (empty for whole item).
|
|
func (i *ItemValue) Field() string { return i.field }
|
|
|
|
// Item returns a value representing the current item in an Each transform.
|
|
func Item() *ItemValue {
|
|
return &ItemValue{}
|
|
}
|
|
|
|
// ItemFieldIsSet returns a condition that checks if a field is set in the current iteration item.
|
|
// This generates CUE: v.fieldName != _|_
|
|
// Used with PickIf for conditionally including fields.
|
|
//
|
|
// Example:
|
|
//
|
|
// .Pick("name", "mountPath").
|
|
// .PickIf(ItemFieldIsSet("subPath"), "subPath")
|
|
func ItemFieldIsSet(field string) Condition {
|
|
return &IsSetCondition{paramName: field}
|
|
}
|
|
|
|
// StructArrayHelper represents a struct-based helper where each field is an array
|
|
// with a default empty value pattern: pvc: *[...] | []
|
|
// This pattern is used in cron-task and other components for mountsArray/volumesArray.
|
|
type StructArrayHelper struct {
|
|
name string
|
|
source Value // e.g., parameter.volumeMounts
|
|
fields []StructArrayField // each field definition
|
|
template *Template
|
|
}
|
|
|
|
// StructArrayField defines a field in a struct array helper.
|
|
type StructArrayField struct {
|
|
Name string // field name (e.g., "pvc", "configMap")
|
|
Mappings FieldMap // how to map input fields to output
|
|
}
|
|
|
|
// Ensure StructArrayHelper implements Value interface
|
|
var _ Value = (*StructArrayHelper)(nil)
|
|
|
|
func (s *StructArrayHelper) value() {}
|
|
func (s *StructArrayHelper) expr() {}
|
|
|
|
// HelperName returns the helper name.
|
|
func (s *StructArrayHelper) HelperName() string { return s.name }
|
|
|
|
// Source returns the source parameter.
|
|
func (s *StructArrayHelper) Source() Value { return s.source }
|
|
|
|
// Fields returns all field definitions.
|
|
func (s *StructArrayHelper) Fields() []StructArrayField { return s.fields }
|
|
|
|
// StructArrayBuilder provides a fluent API for building struct-based array helpers.
|
|
type StructArrayBuilder struct {
|
|
name string
|
|
source Value
|
|
fields []StructArrayField
|
|
template *Template
|
|
}
|
|
|
|
// StructArrayHelper starts building a struct-based array helper.
|
|
// This creates helpers like mountsArray or volumesArray where each source type
|
|
// is a separate field in a struct.
|
|
//
|
|
// Example:
|
|
//
|
|
// mountsArray := tpl.StructArrayHelper("mountsArray", Param("volumeMounts")).
|
|
// Field("pvc", FieldMap{"name": FieldRef("name"), "mountPath": FieldRef("mountPath")}).
|
|
// Field("configMap", FieldMap{"name": FieldRef("name"), "mountPath": FieldRef("mountPath")}).
|
|
// Build()
|
|
func (t *Template) StructArrayHelper(name string, source Value) *StructArrayBuilder {
|
|
return &StructArrayBuilder{
|
|
name: name,
|
|
source: source,
|
|
fields: make([]StructArrayField, 0),
|
|
template: t,
|
|
}
|
|
}
|
|
|
|
// Field adds a field to the struct array helper.
|
|
func (b *StructArrayBuilder) Field(name string, mappings FieldMap) *StructArrayBuilder {
|
|
b.fields = append(b.fields, StructArrayField{
|
|
Name: name,
|
|
Mappings: mappings,
|
|
})
|
|
return b
|
|
}
|
|
|
|
// Build finalizes the struct array helper and registers it with the template.
|
|
func (b *StructArrayBuilder) Build() *StructArrayHelper {
|
|
helper := &StructArrayHelper{
|
|
name: b.name,
|
|
source: b.source,
|
|
fields: b.fields,
|
|
template: b.template,
|
|
}
|
|
|
|
// Register as a special helper type
|
|
b.template.registerStructArrayHelper(helper)
|
|
|
|
return helper
|
|
}
|
|
|
|
// ConcatHelper represents a list.Concat helper that combines arrays from a struct.
|
|
type ConcatHelper struct {
|
|
name string
|
|
source *StructArrayHelper // the source struct helper
|
|
fieldRefs []string // fields to concat
|
|
template *Template
|
|
}
|
|
|
|
// Ensure ConcatHelper implements Value interface
|
|
var _ Value = (*ConcatHelper)(nil)
|
|
|
|
func (c *ConcatHelper) value() {}
|
|
func (c *ConcatHelper) expr() {}
|
|
|
|
// HelperName returns the helper name.
|
|
func (c *ConcatHelper) HelperName() string { return c.name }
|
|
|
|
// Source returns the source struct helper.
|
|
func (c *ConcatHelper) Source() *StructArrayHelper { return c.source }
|
|
|
|
// FieldRefs returns the field references to concatenate.
|
|
func (c *ConcatHelper) FieldRefs() []string { return c.fieldRefs }
|
|
|
|
// RequiredImports returns the CUE imports required by ConcatHelper.
|
|
// ConcatHelper uses list.Concat which requires the "list" import.
|
|
func (c *ConcatHelper) RequiredImports() []string {
|
|
return []string{"list"}
|
|
}
|
|
|
|
// ConcatHelperBuilder provides a fluent API for building list.Concat helpers.
|
|
type ConcatHelperBuilder struct {
|
|
name string
|
|
source *StructArrayHelper
|
|
fieldRefs []string
|
|
template *Template
|
|
}
|
|
|
|
// ConcatHelper starts building a list.Concat helper.
|
|
// This creates helpers like volumesList that concatenate arrays from a struct helper.
|
|
//
|
|
// Example:
|
|
//
|
|
// volumesList := tpl.ConcatHelper("volumesList", volumesArray).
|
|
// Fields("pvc", "configMap", "secret", "emptyDir", "hostPath").
|
|
// Build()
|
|
func (t *Template) ConcatHelper(name string, source *StructArrayHelper) *ConcatHelperBuilder {
|
|
return &ConcatHelperBuilder{
|
|
name: name,
|
|
source: source,
|
|
template: t,
|
|
}
|
|
}
|
|
|
|
// Fields adds field references to concatenate.
|
|
func (b *ConcatHelperBuilder) Fields(fields ...string) *ConcatHelperBuilder {
|
|
b.fieldRefs = append(b.fieldRefs, fields...)
|
|
return b
|
|
}
|
|
|
|
// Build finalizes the concat helper and registers it with the template.
|
|
func (b *ConcatHelperBuilder) Build() *ConcatHelper {
|
|
helper := &ConcatHelper{
|
|
name: b.name,
|
|
source: b.source,
|
|
fieldRefs: b.fieldRefs,
|
|
template: b.template,
|
|
}
|
|
|
|
// Register as a special helper type
|
|
b.template.registerConcatHelper(helper)
|
|
|
|
return helper
|
|
}
|
|
|
|
// DedupeHelper represents a deduplication helper that removes duplicates by key.
|
|
type DedupeHelper struct {
|
|
name string
|
|
source Value // source to dedupe (typically a ConcatHelper or HelperVar)
|
|
keyField string // field to dedupe by (e.g., "name")
|
|
template *Template
|
|
}
|
|
|
|
// Ensure DedupeHelper implements Value interface
|
|
var _ Value = (*DedupeHelper)(nil)
|
|
|
|
func (d *DedupeHelper) value() {}
|
|
func (d *DedupeHelper) expr() {}
|
|
|
|
// HelperName returns the helper name.
|
|
func (d *DedupeHelper) HelperName() string { return d.name }
|
|
|
|
// Source returns the source to deduplicate.
|
|
func (d *DedupeHelper) Source() Value { return d.source }
|
|
|
|
// KeyField returns the field to dedupe by.
|
|
func (d *DedupeHelper) KeyField() string { return d.keyField }
|
|
|
|
// DedupeHelperBuilder provides a fluent API for building deduplication helpers.
|
|
type DedupeHelperBuilder struct {
|
|
name string
|
|
source Value
|
|
keyField string
|
|
template *Template
|
|
}
|
|
|
|
// DedupeHelper starts building a deduplication helper.
|
|
// This creates helpers like deDupVolumesArray that remove duplicates by a key field.
|
|
//
|
|
// Example:
|
|
//
|
|
// deDupVolumes := tpl.DedupeHelper("deDupVolumesArray", volumesList).
|
|
// ByKey("name").
|
|
// Build()
|
|
func (t *Template) DedupeHelper(name string, source Value) *DedupeHelperBuilder {
|
|
return &DedupeHelperBuilder{
|
|
name: name,
|
|
source: source,
|
|
template: t,
|
|
}
|
|
}
|
|
|
|
// ByKey sets the key field to deduplicate by.
|
|
func (b *DedupeHelperBuilder) ByKey(field string) *DedupeHelperBuilder {
|
|
b.keyField = field
|
|
return b
|
|
}
|
|
|
|
// Build finalizes the dedupe helper and registers it with the template.
|
|
func (b *DedupeHelperBuilder) Build() *DedupeHelper {
|
|
helper := &DedupeHelper{
|
|
name: b.name,
|
|
source: b.source,
|
|
keyField: b.keyField,
|
|
template: b.template,
|
|
}
|
|
|
|
// Register with template
|
|
b.template.registerDedupeHelper(helper)
|
|
|
|
return helper
|
|
}
|