Files
kubevela/pkg/definition/defkit/component.go
Ayush Kumar 012a134829 Feat: extend fluent builder API validator patterns (#7092)
* Feat: Add NotEmpty and NegativePattern constraints to StringParam; implement Closed for MapParam

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

* feat: add validation support for array and map parameters

- Introduced validators for ArrayParam and MapParam, allowing for cross-field validation within structured parameters.
- Added NonEmpty validation for ArrayParam to ensure arrays are not empty.
- Implemented ConditionalStructOp for conditional struct generation based on specified conditions.
- Created a new Validator type for defining validation rules with optional guard conditions.
- Added tests for various validation scenarios, including mutual exclusion and conditional parameters.
- Enhanced the CUE generation logic to incorporate new validation features and conditional struct handling.

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

* feat: extend fluent API with new scoped field conditions and improve validation checks

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

* feat: enhance ArrayParam with NotEmpty constraint and update ScopedField documentation

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

* feat: rename ScopedField to LocalField for improved clarity in condition building

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

* feat: refactor local field conditions to use RegexMatch and streamline condition building

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

* feat: simplify condition handling by removing unused comparison types and refactoring NotCondition usage

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

* refactor: remove unused raw CUE block handling from baseDefinition and ComponentDefinition

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

* test: update condition handling in parameter tests to use NotExpr and Cond methods

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

* refactor: remove negative pattern handling from StringParam and related tests

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

* feat: add support for emitting raw header blocks in template generation

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

* refactor: remove non-empty check from ArrayParam and update related tests

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

* refactor: convert parameter constraint tests to use Ginkgo and Gomega for improved readability and maintainability

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

* feat: extend fluent APIs for OAM with new CUE generation tests and condition evaluations

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

* refactor: clean up whitespace in component, cuegen, expr, param, and resource files

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

* feat: enhance CUE generation by adding support for new expression types and iterator references

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

* refactor: remove unnecessary whitespace in cuegen.go

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

* refactor: rename LenOf to LenOfExpr for clarity in comparison methods

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

* feat: enhance CUE generation and validation for string arrays in ArrayParam

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

* ci: retrigger checks

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

---------

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>
Signed-off-by: Ayush Kumar <aykumar@guidewire.com>
Co-authored-by: Ayush Kumar <aykumar@guidewire.com>
2026-04-09 14:25:06 +01:00

365 lines
12 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 (
"sigs.k8s.io/yaml"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/pkg/definition/defkit/placement"
)
// ComponentDefinition represents a KubeVela ComponentDefinition.
//
// ComponentDefinition embeds baseDefinition for common fields and methods shared
// with TraitDefinition and other definition types.
type ComponentDefinition struct {
baseDefinition // embedded common fields (name, description, params, template, etc.)
workload WorkloadType
omitWorkloadType bool // when true, suppresses the auto-generated workload.type field in CUE
labels map[string]string // metadata labels for the component definition
childResourceKinds []common.ChildResourceKind
podSpecPath string
}
// WorkloadType represents the workload type for a component.
type WorkloadType struct {
apiVersion string
kind string
autodetect bool // when true, uses "autodetects.core.oam.dev" without definition block
}
// NewComponent creates a new ComponentDefinition builder.
func NewComponent(name string) *ComponentDefinition {
return &ComponentDefinition{
baseDefinition: baseDefinition{
name: name,
params: make([]Param, 0),
},
}
}
// Description sets the component description.
func (c *ComponentDefinition) Description(desc string) *ComponentDefinition {
c.setDescription(desc)
return c
}
// Workload sets the workload type for this component.
func (c *ComponentDefinition) Workload(apiVersion, kind string) *ComponentDefinition {
c.workload = WorkloadType{apiVersion: apiVersion, kind: kind}
return c
}
// OmitWorkloadType suppresses the auto-generated workload.type field in the CUE output.
// Use this when the vela source CUE does not include a workload type field.
func (c *ComponentDefinition) OmitWorkloadType() *ComponentDefinition {
c.omitWorkloadType = true
return c
}
// IsOmitWorkloadType returns whether workload type should be suppressed in CUE output.
func (c *ComponentDefinition) IsOmitWorkloadType() bool {
return c.omitWorkloadType
}
// AutodetectWorkload sets the workload type to "autodetects.core.oam.dev".
// This is used for components where the workload type is auto-detected at runtime
// rather than being statically defined.
func (c *ComponentDefinition) AutodetectWorkload() *ComponentDefinition {
c.workload = WorkloadType{autodetect: true}
return c
}
// Params adds multiple parameter definitions to the component.
func (c *ComponentDefinition) Params(params ...Param) *ComponentDefinition {
c.addParams(params...)
return c
}
// Param adds a single parameter definition to the component.
// This provides a more fluent API when adding parameters one at a time.
func (c *ComponentDefinition) Param(param Param) *ComponentDefinition {
c.addParams(param)
return c
}
// Template sets the template function for the component.
func (c *ComponentDefinition) Template(fn func(tpl *Template)) *ComponentDefinition {
c.setTemplate(fn)
return c
}
// CustomStatus sets the custom status CUE expression for the component.
// This expression is used to compute a human-readable status message.
func (c *ComponentDefinition) CustomStatus(expr string) *ComponentDefinition {
c.setCustomStatus(expr)
return c
}
// HealthPolicy sets the health policy CUE expression for the component.
// This expression determines whether the component is healthy.
// For raw CUE strings, use this method directly.
// For composable health expressions, use HealthPolicyExpr with Health().
func (c *ComponentDefinition) HealthPolicy(expr string) *ComponentDefinition {
c.setHealthPolicy(expr)
return c
}
// HealthPolicyExpr sets the health policy using a composable HealthExpression.
// This allows building health checks programmatically using primitives like
// Condition, Field, Phase, Exists, And, Or, Not via Health().
//
// Example:
//
// h := defkit.Health()
// def.HealthPolicyExpr(h.Condition("Ready").IsTrue())
// def.HealthPolicyExpr(h.AllTrue("Ready", "Synced"))
// def.HealthPolicyExpr(h.And(
// h.Condition("Ready").IsTrue(),
// h.Field("status.replicas").Gt(0),
// ))
func (c *ComponentDefinition) HealthPolicyExpr(expr HealthExpression) *ComponentDefinition {
c.setHealthPolicyExpr(expr)
return c
}
// StatusDetails sets the status details CUE expression for the component.
func (c *ComponentDefinition) StatusDetails(details string) *ComponentDefinition {
c.setStatusDetails(details)
return c
}
// Annotations sets metadata annotations on the component definition.
func (c *ComponentDefinition) Annotations(annotations map[string]string) *ComponentDefinition {
c.setAnnotations(annotations)
return c
}
// Version sets the version string for the component definition.
func (c *ComponentDefinition) Version(v string) *ComponentDefinition {
c.setVersion(v)
return c
}
// DefName implements Definition.DefName - returns the definition name.
func (c *ComponentDefinition) DefName() string { return c.name }
// DefType implements Definition.DefType - returns the definition type.
func (c *ComponentDefinition) DefType() DefinitionType { return DefinitionTypeComponent }
// GetWorkload returns the workload type.
func (c *ComponentDefinition) GetWorkload() WorkloadType { return c.workload }
// Helper adds a helper type definition using fluent API.
// The param defines the schema for the helper type.
// Example:
//
// Helper("HealthProbe", defkit.Object("probe").WithFields(...))
func (c *ComponentDefinition) Helper(name string, param Param) *ComponentDefinition {
c.addHelper(name, param)
return c
}
// Labels sets metadata labels on the component definition.
// Usage: component.Labels(map[string]string{"ui-hidden": "true"})
func (c *ComponentDefinition) Labels(labels map[string]string) *ComponentDefinition {
c.labels = labels
return c
}
// GetLabels returns the component's metadata labels.
func (c *ComponentDefinition) GetLabels() map[string]string { return c.labels }
// ChildResourceKind adds a child resource kind entry to the component definition.
// Multiple calls accumulate entries.
func (c *ComponentDefinition) ChildResourceKind(apiVersion, kind string, selector map[string]string) *ComponentDefinition {
c.childResourceKinds = append(c.childResourceKinds, common.ChildResourceKind{
APIVersion: apiVersion,
Kind: kind,
Selector: selector,
})
return c
}
// GetChildResourceKinds returns all accumulated child resource kind entries.
func (c *ComponentDefinition) GetChildResourceKinds() []common.ChildResourceKind {
return c.childResourceKinds
}
// PodSpecPath sets the pod spec path for the component definition.
func (c *ComponentDefinition) PodSpecPath(path string) *ComponentDefinition {
c.podSpecPath = path
return c
}
// GetPodSpecPath returns the pod spec path.
func (c *ComponentDefinition) GetPodSpecPath() string { return c.podSpecPath }
// Validators adds top-level parameter validators to the component.
// These validators are emitted inside the parameter: { ... } block as _validate* variables.
func (c *ComponentDefinition) Validators(validators ...*Validator) *ComponentDefinition {
c.addValidators(validators...)
return c
}
// ConditionalParams adds a conditional parameter block to the component.
// This allows parameters to change shape based on a discriminator value.
func (c *ComponentDefinition) ConditionalParams(block *ConditionalParamBlock) *ComponentDefinition {
c.addConditionalParamBlock(block)
return c
}
// RawCUE sets raw CUE for complex component definitions that don't fit the builder pattern.
// When set, this bypasses all other template settings and outputs the raw CUE directly.
func (c *ComponentDefinition) RawCUE(cue string) *ComponentDefinition {
c.setRawCUE(cue)
return c
}
// WithImports adds CUE imports to the component definition.
// Usage: component.WithImports("strconv", "strings", "list")
func (c *ComponentDefinition) WithImports(imports ...string) *ComponentDefinition {
c.addImports(imports...)
return c
}
// RunOn adds placement conditions specifying which clusters this definition should run on.
// Use the placement package's fluent API to build conditions.
//
// Example:
//
// defkit.NewComponent("eks-only").
// RunOn(placement.Label("provider").Eq("aws")).
// RunOn(placement.Label("cluster-type").NotEq("vcluster"))
//
// Multiple RunOn calls are combined with AND semantics (all conditions must match).
func (c *ComponentDefinition) RunOn(conditions ...placement.Condition) *ComponentDefinition {
c.addRunOn(conditions...)
return c
}
// NotRunOn adds placement conditions specifying which clusters this definition should NOT run on.
// Use the placement package's fluent API to build conditions.
//
// Example:
//
// defkit.NewComponent("no-vclusters").
// NotRunOn(placement.Label("cluster-type").Eq("vcluster"))
//
// If any NotRunOn condition matches, the definition is ineligible for that cluster.
func (c *ComponentDefinition) NotRunOn(conditions ...placement.Condition) *ComponentDefinition {
c.addNotRunOn(conditions...)
return c
}
// ToCue generates the complete CUE definition string for this component.
// This is a convenience method that creates a CUEGenerator and calls GenerateFullDefinition.
func (c *ComponentDefinition) ToCue() string {
// If raw CUE is set, use it with the name from NewComponent() taking precedence
if c.HasRawCUE() {
return c.GetRawCUEWithName()
}
gen := NewCUEGenerator()
if len(c.GetImports()) > 0 {
gen.WithImports(c.GetImports()...)
}
return gen.GenerateFullDefinition(c)
}
// ToCueWithImports generates the CUE definition with the specified imports.
// Use this when the definition requires CUE standard library imports.
// Example: component.ToCueWithImports(CUEImports.Strconv, CUEImports.List)
func (c *ComponentDefinition) ToCueWithImports(imports ...string) string {
gen := NewCUEGenerator().WithImports(imports...)
return gen.GenerateFullDefinition(c)
}
// ToParameterSchema generates only the parameter schema block.
// This is useful for testing or comparing parameter schemas.
func (c *ComponentDefinition) ToParameterSchema() string {
gen := NewCUEGenerator()
return gen.GenerateParameterSchema(c)
}
// ToYAML generates the Kubernetes YAML representation of the ComponentDefinition.
// This produces a ComponentDefinition custom resource that can be applied to a cluster.
// Note: The CUE template is embedded in the spec.schematic.cue field.
func (c *ComponentDefinition) ToYAML() ([]byte, error) {
cueStr := c.ToCue()
// Build the ComponentDefinition CR structure
cr := map[string]any{
"apiVersion": "core.oam.dev/v1beta1",
"kind": "ComponentDefinition",
"metadata": map[string]any{
"name": c.GetName(),
"annotations": func() map[string]any {
a := map[string]any{}
for k, v := range c.GetAnnotations() {
a[k] = v
}
a["definition.oam.dev/description"] = c.GetDescription()
return a
}(),
},
"spec": map[string]any{
"workload": map[string]any{
"definition": map[string]any{
"apiVersion": c.workload.apiVersion,
"kind": c.workload.kind,
},
},
"schematic": map[string]any{
"cue": map[string]any{
"template": cueStr,
},
},
},
}
// Handle autodetect workload type
if c.workload.autodetect {
cr["spec"].(map[string]any)["workload"] = map[string]any{
"type": "autodetects.core.oam.dev",
}
}
if len(c.childResourceKinds) > 0 {
cr["spec"].(map[string]any)["childResourceKinds"] = c.childResourceKinds
}
if c.podSpecPath != "" {
cr["spec"].(map[string]any)["podSpecPath"] = c.podSpecPath
}
if c.GetVersion() != "" {
cr["spec"].(map[string]any)["version"] = c.GetVersion()
}
return yaml.Marshal(cr)
}
// APIVersion returns the workload API version.
func (w WorkloadType) APIVersion() string { return w.apiVersion }
// Kind returns the workload kind.
func (w WorkloadType) Kind() string { return w.kind }
// IsAutodetect returns true if the workload type is auto-detected at runtime.
func (w WorkloadType) IsAutodetect() bool { return w.autodetect }