mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 20:46:37 +00:00
* 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>
341 lines
9.9 KiB
Go
341 lines
9.9 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
|
|
|
|
// ResourceOp represents an operation recorded during resource building.
|
|
type ResourceOp interface {
|
|
resourceOp()
|
|
}
|
|
|
|
// SetOp represents a Set operation on a resource field.
|
|
type SetOp struct {
|
|
path string
|
|
value Value
|
|
}
|
|
|
|
func (s *SetOp) resourceOp() {}
|
|
|
|
// Path returns the field path.
|
|
func (s *SetOp) Path() string { return s.path }
|
|
|
|
// Value returns the value being set.
|
|
func (s *SetOp) Value() Value { return s.value }
|
|
|
|
// SetIfOp represents a conditional Set operation.
|
|
type SetIfOp struct {
|
|
path string
|
|
value Value
|
|
cond Condition
|
|
}
|
|
|
|
func (s *SetIfOp) resourceOp() {}
|
|
|
|
// Path returns the field path.
|
|
func (s *SetIfOp) Path() string { return s.path }
|
|
|
|
// Value returns the value being set.
|
|
func (s *SetIfOp) Value() Value { return s.value }
|
|
|
|
// Cond returns the condition for the operation.
|
|
func (s *SetIfOp) Cond() Condition { return s.cond }
|
|
|
|
// IfBlock represents a conditional block of operations.
|
|
type IfBlock struct {
|
|
cond Condition
|
|
ops []ResourceOp
|
|
}
|
|
|
|
func (i *IfBlock) resourceOp() {}
|
|
|
|
// Cond returns the block condition.
|
|
func (i *IfBlock) Cond() Condition { return i.cond }
|
|
|
|
// Ops returns the operations within the block.
|
|
func (i *IfBlock) Ops() []ResourceOp { return i.ops }
|
|
|
|
// SpreadIfOp represents a conditional spread operation inside a block.
|
|
// Instead of conditionally setting a whole field, it spreads a value
|
|
// inside a block when a condition is met.
|
|
// Example: labels: { if param.labels != _|_ { parameter.labels } ... }
|
|
type SpreadIfOp struct {
|
|
path string // parent path (e.g., "spec.template.metadata.labels")
|
|
value Value // value to spread
|
|
cond Condition // condition for spreading
|
|
}
|
|
|
|
func (s *SpreadIfOp) resourceOp() {}
|
|
|
|
// Path returns the parent field path.
|
|
func (s *SpreadIfOp) Path() string { return s.path }
|
|
|
|
// Value returns the value being spread.
|
|
func (s *SpreadIfOp) Value() Value { return s.value }
|
|
|
|
// Cond returns the condition for the spread.
|
|
func (s *SpreadIfOp) Cond() Condition { return s.cond }
|
|
|
|
// VersionConditional represents a conditional apiVersion setting.
|
|
type VersionConditional struct {
|
|
Condition Condition
|
|
ApiVersion string
|
|
}
|
|
|
|
// Resource represents a Kubernetes resource being built.
|
|
type Resource struct {
|
|
apiVersion string
|
|
kind string
|
|
ops []ResourceOp
|
|
currentIf *IfBlock // tracks current If block being built
|
|
outputCondition Condition // condition for conditional output (used by OutputsIf)
|
|
versionConditionals []VersionConditional
|
|
}
|
|
|
|
// NewResource creates a new resource builder with API version and kind.
|
|
func NewResource(apiVersion, kind string) *Resource {
|
|
return &Resource{
|
|
apiVersion: apiVersion,
|
|
kind: kind,
|
|
ops: make([]ResourceOp, 0),
|
|
}
|
|
}
|
|
|
|
// NewResourceWithConditionalVersion creates a resource with conditional apiVersion.
|
|
// This is used when the apiVersion depends on runtime conditions like cluster version.
|
|
//
|
|
// Example:
|
|
//
|
|
// vela := defkit.VelaCtx()
|
|
// cronjob := defkit.NewResourceWithConditionalVersion("CronJob").
|
|
// VersionIf(Lt(vela.ClusterVersion().Minor(), 25), "batch/v1beta1").
|
|
// VersionIf(Gte(vela.ClusterVersion().Minor(), 25), "batch/v1")
|
|
func NewResourceWithConditionalVersion(kind string) *Resource {
|
|
return &Resource{
|
|
kind: kind,
|
|
ops: make([]ResourceOp, 0),
|
|
versionConditionals: make([]VersionConditional, 0),
|
|
}
|
|
}
|
|
|
|
// VersionIf adds a conditional apiVersion to the resource.
|
|
// When the condition is true, this apiVersion will be used.
|
|
func (r *Resource) VersionIf(cond Condition, apiVersion string) *Resource {
|
|
r.versionConditionals = append(r.versionConditionals, VersionConditional{
|
|
Condition: cond,
|
|
ApiVersion: apiVersion,
|
|
})
|
|
return r
|
|
}
|
|
|
|
// HasVersionConditionals returns true if the resource has conditional apiVersion.
|
|
func (r *Resource) HasVersionConditionals() bool {
|
|
return len(r.versionConditionals) > 0
|
|
}
|
|
|
|
// VersionConditionals returns all conditional apiVersion settings.
|
|
func (r *Resource) VersionConditionals() []VersionConditional {
|
|
return r.versionConditionals
|
|
}
|
|
|
|
// APIVersion returns the resource's API version.
|
|
func (r *Resource) APIVersion() string { return r.apiVersion }
|
|
|
|
// Kind returns the resource's kind.
|
|
func (r *Resource) Kind() string { return r.kind }
|
|
|
|
// Ops returns all recorded operations.
|
|
func (r *Resource) Ops() []ResourceOp { return r.ops }
|
|
|
|
// Set records a field assignment operation.
|
|
func (r *Resource) Set(path string, value Value) *Resource {
|
|
op := &SetOp{path: path, value: value}
|
|
if r.currentIf != nil {
|
|
r.currentIf.ops = append(r.currentIf.ops, op)
|
|
} else {
|
|
r.ops = append(r.ops, op)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// SetIf records a conditional field assignment.
|
|
func (r *Resource) SetIf(cond Condition, path string, value Value) *Resource {
|
|
op := &SetIfOp{path: path, value: value, cond: cond}
|
|
if r.currentIf != nil {
|
|
r.currentIf.ops = append(r.currentIf.ops, op)
|
|
} else {
|
|
r.ops = append(r.ops, op)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// SpreadIf records a conditional spread operation inside a struct block.
|
|
// Unlike SetIf which wraps the entire field in a conditional, SpreadIf spreads
|
|
// the value inside the block only when the condition is true, while the block
|
|
// itself always exists with its other fields.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// Set("spec.template.metadata.labels[app.oam.dev/name]", vela.AppName()).
|
|
// SpreadIf(labels.IsSet(), "spec.template.metadata.labels", labels)
|
|
//
|
|
// Generates:
|
|
//
|
|
// labels: {
|
|
// "app.oam.dev/name": context.appName
|
|
// if parameter.labels != _|_ {
|
|
// parameter.labels
|
|
// }
|
|
// }
|
|
func (r *Resource) SpreadIf(cond Condition, path string, value Value) *Resource {
|
|
op := &SpreadIfOp{path: path, value: value, cond: cond}
|
|
if r.currentIf != nil {
|
|
r.currentIf.ops = append(r.currentIf.ops, op)
|
|
} else {
|
|
r.ops = append(r.ops, op)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// If starts a conditional block. Operations until EndIf are conditional.
|
|
func (r *Resource) If(cond Condition) *Resource {
|
|
r.currentIf = &IfBlock{
|
|
cond: cond,
|
|
ops: make([]ResourceOp, 0),
|
|
}
|
|
return r
|
|
}
|
|
|
|
// EndIf ends the current conditional block.
|
|
func (r *Resource) EndIf() *Resource {
|
|
if r.currentIf != nil {
|
|
r.ops = append(r.ops, r.currentIf)
|
|
r.currentIf = nil
|
|
}
|
|
return r
|
|
}
|
|
|
|
// Directive records a CUE directive annotation on a field path.
|
|
// The directive string should be like "patchKey=ip" and will be rendered as // +patchKey=ip.
|
|
func (r *Resource) Directive(path string, directive string) *Resource {
|
|
op := &DirectiveOp{path: path, directive: directive}
|
|
if r.currentIf != nil {
|
|
r.currentIf.ops = append(r.currentIf.ops, op)
|
|
} else {
|
|
r.ops = append(r.ops, op)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// DirectiveOp records a CUE directive annotation on a field path.
|
|
// The directive string (e.g. "patchKey=ip") is rendered as // +patchKey=ip.
|
|
type DirectiveOp struct {
|
|
path string
|
|
directive string
|
|
}
|
|
|
|
func (d *DirectiveOp) resourceOp() {}
|
|
|
|
// Path returns the field path.
|
|
func (d *DirectiveOp) Path() string { return d.path }
|
|
|
|
// GetDirective returns the directive string.
|
|
func (d *DirectiveOp) GetDirective() string { return d.directive }
|
|
|
|
// ConditionalStructOp represents a conditional struct block emitted in the output.
|
|
// When the condition is true, the struct builder function is called to generate
|
|
// CUE fields at the given path.
|
|
//
|
|
// Example:
|
|
//
|
|
// output.ConditionalStruct(replConfig.IsSet(), "spec.replicationConfiguration", func(b *OutputStructBuilder) {
|
|
// b.Set("role", defkit.Ref("parameter.replicationConfiguration.role"))
|
|
// })
|
|
//
|
|
// Emits:
|
|
//
|
|
// if parameter["replicationConfiguration"] != _|_ {
|
|
// spec: replicationConfiguration: {
|
|
// role: parameter.replicationConfiguration.role
|
|
// }
|
|
// }
|
|
type ConditionalStructOp struct {
|
|
cond Condition
|
|
path string
|
|
builder func(b *OutputStructBuilder)
|
|
}
|
|
|
|
func (c *ConditionalStructOp) resourceOp() {}
|
|
|
|
// Cond returns the condition.
|
|
func (c *ConditionalStructOp) Cond() Condition { return c.cond }
|
|
|
|
// Path returns the output path.
|
|
func (c *ConditionalStructOp) Path() string { return c.path }
|
|
|
|
// Builder returns the struct builder function.
|
|
func (c *ConditionalStructOp) Builder() func(b *OutputStructBuilder) { return c.builder }
|
|
|
|
// ConditionalStruct records a conditional struct block in the output.
|
|
// When the condition is true, the fields built by fn are emitted at the given path.
|
|
func (r *Resource) ConditionalStruct(cond Condition, path string, fn func(b *OutputStructBuilder)) *Resource {
|
|
op := &ConditionalStructOp{cond: cond, path: path, builder: fn}
|
|
if r.currentIf != nil {
|
|
r.currentIf.ops = append(r.currentIf.ops, op)
|
|
} else {
|
|
r.ops = append(r.ops, op)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// OutputStructBuilder collects field operations for building a conditional struct.
|
|
type OutputStructBuilder struct {
|
|
ops []structBuilderOp
|
|
}
|
|
|
|
type structBuilderOp interface {
|
|
structBuilderOp()
|
|
}
|
|
|
|
type structSetOp struct {
|
|
field string
|
|
value Value
|
|
}
|
|
|
|
func (s *structSetOp) structBuilderOp() {}
|
|
|
|
type structSetIfOp struct {
|
|
cond Condition
|
|
field string
|
|
value Value
|
|
}
|
|
|
|
func (s *structSetIfOp) structBuilderOp() {}
|
|
|
|
// Set adds an unconditional field assignment to the struct.
|
|
func (b *OutputStructBuilder) Set(field string, value Value) {
|
|
b.ops = append(b.ops, &structSetOp{field: field, value: value})
|
|
}
|
|
|
|
// SetIf adds a conditional field assignment to the struct.
|
|
func (b *OutputStructBuilder) SetIf(cond Condition, field string, value Value) {
|
|
b.ops = append(b.ops, &structSetIfOp{cond: cond, field: field, value: value})
|
|
}
|
|
|
|
// Ops returns all operations recorded in the struct builder.
|
|
func (b *OutputStructBuilder) Ops() []structBuilderOp {
|
|
return b.ops
|
|
}
|