mirror of
https://github.com/kubevela/kubevela.git
synced 2026-05-19 07:46:51 +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>
988 lines
31 KiB
Go
988 lines
31 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_test
|
|
|
|
import (
|
|
"strings"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
|
|
"github.com/oam-dev/kubevela/pkg/definition/defkit"
|
|
)
|
|
|
|
var _ = Describe("ComponentDefinition", func() {
|
|
|
|
Context("NewComponent", func() {
|
|
It("should create a component with name", func() {
|
|
c := defkit.NewComponent("webservice")
|
|
Expect(c.GetName()).To(Equal("webservice"))
|
|
Expect(c.GetParams()).To(BeEmpty())
|
|
})
|
|
})
|
|
|
|
Context("Component Configuration", func() {
|
|
It("should set description", func() {
|
|
c := defkit.NewComponent("webservice").
|
|
Description("Describes long-running, scalable, containerized services")
|
|
Expect(c.GetDescription()).To(Equal("Describes long-running, scalable, containerized services"))
|
|
})
|
|
|
|
It("should set workload type", func() {
|
|
c := defkit.NewComponent("webservice").
|
|
Workload("apps/v1", "Deployment")
|
|
Expect(c.GetWorkload().APIVersion()).To(Equal("apps/v1"))
|
|
Expect(c.GetWorkload().Kind()).To(Equal("Deployment"))
|
|
})
|
|
|
|
It("should add parameters", func() {
|
|
image := defkit.String("image")
|
|
replicas := defkit.Int("replicas").Default(1)
|
|
c := defkit.NewComponent("webservice").
|
|
Params(image, replicas)
|
|
Expect(c.GetParams()).To(HaveLen(2))
|
|
Expect(c.GetParams()[0].Name()).To(Equal("image"))
|
|
Expect(c.GetParams()[1].Name()).To(Equal("replicas"))
|
|
})
|
|
|
|
It("should set template function", func() {
|
|
var templateCalled bool
|
|
c := defkit.NewComponent("webservice").
|
|
Template(func(tpl *defkit.Template) {
|
|
templateCalled = true
|
|
})
|
|
Expect(c.GetTemplate()).NotTo(BeNil())
|
|
c.GetTemplate()(defkit.NewTemplate())
|
|
Expect(templateCalled).To(BeTrue())
|
|
})
|
|
|
|
It("should set autodetect workload mode", func() {
|
|
c := defkit.NewComponent("test").
|
|
AutodetectWorkload()
|
|
Expect(c.GetWorkload().IsAutodetect()).To(BeTrue())
|
|
})
|
|
})
|
|
|
|
Context("Template Operations", func() {
|
|
It("should create a new template", func() {
|
|
tpl := defkit.NewTemplate()
|
|
Expect(tpl).NotTo(BeNil())
|
|
Expect(tpl.GetOutput()).To(BeNil())
|
|
Expect(tpl.GetOutputs()).To(BeEmpty())
|
|
})
|
|
|
|
It("should set primary output", func() {
|
|
tpl := defkit.NewTemplate()
|
|
r := defkit.NewResource("apps/v1", "Deployment")
|
|
tpl.Output(r)
|
|
Expect(tpl.GetOutput()).To(Equal(r))
|
|
})
|
|
|
|
It("should return existing output when called without args", func() {
|
|
tpl := defkit.NewTemplate()
|
|
r := defkit.NewResource("apps/v1", "Deployment")
|
|
tpl.Output(r)
|
|
Expect(tpl.Output()).To(Equal(r))
|
|
})
|
|
|
|
It("should set auxiliary outputs", func() {
|
|
tpl := defkit.NewTemplate()
|
|
svc := defkit.NewResource("v1", "Service")
|
|
cm := defkit.NewResource("v1", "ConfigMap")
|
|
tpl.Outputs("service", svc)
|
|
tpl.Outputs("config", cm)
|
|
Expect(tpl.GetOutputs()).To(HaveLen(2))
|
|
Expect(tpl.Outputs("service")).To(Equal(svc))
|
|
Expect(tpl.Outputs("config")).To(Equal(cm))
|
|
})
|
|
|
|
It("should support patch with strategy", func() {
|
|
tpl := defkit.NewTemplate()
|
|
tpl.PatchStrategy("retainKeys").
|
|
Patch().Set("spec.replicas", defkit.Lit(1))
|
|
|
|
Expect(tpl.HasPatch()).To(BeTrue())
|
|
Expect(tpl.GetPatchStrategy()).To(Equal("retainKeys"))
|
|
Expect(tpl.GetPatch()).NotTo(BeNil())
|
|
})
|
|
|
|
It("should support patch without strategy", func() {
|
|
tpl := defkit.NewTemplate()
|
|
tpl.Patch().Set("spec.replicas", defkit.Lit(1))
|
|
|
|
Expect(tpl.HasPatch()).To(BeTrue())
|
|
Expect(tpl.GetPatchStrategy()).To(BeEmpty())
|
|
})
|
|
})
|
|
|
|
Context("Template Helper Registration", func() {
|
|
It("should register struct array helpers", func() {
|
|
tpl := defkit.NewTemplate()
|
|
volumeMounts := defkit.Object("volumeMounts")
|
|
tpl.StructArrayHelper("volumesArray", volumeMounts).
|
|
Field("pvc", defkit.FieldMap{"name": defkit.FieldRef("name")}).
|
|
Build()
|
|
|
|
Expect(tpl.GetStructArrayHelpers()).To(HaveLen(1))
|
|
Expect(tpl.GetStructArrayHelpers()[0].HelperName()).To(Equal("volumesArray"))
|
|
})
|
|
|
|
It("should register concat helpers", func() {
|
|
tpl := defkit.NewTemplate()
|
|
volumeMounts := defkit.Object("volumeMounts")
|
|
structHelper := tpl.StructArrayHelper("volumesArray", volumeMounts).
|
|
Field("pvc", defkit.FieldMap{"name": defkit.FieldRef("name")}).
|
|
Field("configMap", defkit.FieldMap{"name": defkit.FieldRef("name")}).
|
|
Build()
|
|
|
|
tpl.ConcatHelper("volumesList", structHelper).
|
|
Fields("pvc", "configMap").
|
|
Build()
|
|
|
|
Expect(tpl.GetConcatHelpers()).To(HaveLen(1))
|
|
Expect(tpl.GetConcatHelpers()[0].HelperName()).To(Equal("volumesList"))
|
|
})
|
|
|
|
It("should register dedupe helpers", func() {
|
|
tpl := defkit.NewTemplate()
|
|
volumeMounts := defkit.Object("volumeMounts")
|
|
structHelper := tpl.StructArrayHelper("volumesArray", volumeMounts).
|
|
Field("pvc", defkit.FieldMap{"name": defkit.FieldRef("name")}).
|
|
Build()
|
|
|
|
concatHelper := tpl.ConcatHelper("volumesList", structHelper).
|
|
Fields("pvc").
|
|
Build()
|
|
|
|
tpl.DedupeHelper("uniqueVolumes", concatHelper).
|
|
ByKey("name").
|
|
Build()
|
|
|
|
Expect(tpl.GetDedupeHelpers()).To(HaveLen(1))
|
|
Expect(tpl.GetDedupeHelpers()[0].HelperName()).To(Equal("uniqueVolumes"))
|
|
})
|
|
})
|
|
|
|
Context("PatchResource", func() {
|
|
It("should create patch resource with Set operations", func() {
|
|
patch := defkit.NewPatchResource()
|
|
patch.Set("spec.replicas", defkit.Lit(3))
|
|
patch.Set("metadata.labels.app", defkit.Lit("myapp"))
|
|
|
|
Expect(patch.Ops()).To(HaveLen(2))
|
|
})
|
|
|
|
It("should add SetIf operation", func() {
|
|
cpu := defkit.String("cpu")
|
|
patch := defkit.NewPatchResource()
|
|
patch.SetIf(cpu.IsSet(), "spec.resources.limits.cpu", cpu)
|
|
|
|
Expect(patch.Ops()).To(HaveLen(1))
|
|
setIfOp, ok := patch.Ops()[0].(*defkit.SetIfOp)
|
|
Expect(ok).To(BeTrue())
|
|
Expect(setIfOp.Path()).To(Equal("spec.resources.limits.cpu"))
|
|
})
|
|
|
|
It("should add SpreadIf operation", func() {
|
|
labels := defkit.Object("labels")
|
|
patch := defkit.NewPatchResource()
|
|
patch.SpreadIf(labels.IsSet(), "metadata.labels", labels)
|
|
|
|
Expect(patch.Ops()).To(HaveLen(1))
|
|
spreadIfOp, ok := patch.Ops()[0].(*defkit.SpreadIfOp)
|
|
Expect(ok).To(BeTrue())
|
|
Expect(spreadIfOp.Path()).To(Equal("metadata.labels"))
|
|
})
|
|
|
|
It("should add ForEach operation", func() {
|
|
labels := defkit.Object("labels")
|
|
patch := defkit.NewPatchResource()
|
|
patch.ForEach(labels, "metadata.labels")
|
|
|
|
Expect(patch.Ops()).To(HaveLen(1))
|
|
forEachOp, ok := patch.Ops()[0].(*defkit.ForEachOp)
|
|
Expect(ok).To(BeTrue())
|
|
Expect(forEachOp.Path()).To(Equal("metadata.labels"))
|
|
Expect(forEachOp.Source()).To(Equal(labels))
|
|
})
|
|
|
|
It("should handle If/EndIf blocks", func() {
|
|
enabled := defkit.Bool("enabled")
|
|
replicas := defkit.Int("replicas")
|
|
cond := defkit.Eq(enabled, defkit.Lit(true))
|
|
|
|
patch := defkit.NewPatchResource()
|
|
patch.If(cond).
|
|
Set("spec.replicas", replicas).
|
|
EndIf()
|
|
|
|
Expect(patch.Ops()).To(HaveLen(1))
|
|
ifBlock, ok := patch.Ops()[0].(*defkit.IfBlock)
|
|
Expect(ok).To(BeTrue())
|
|
Expect(ifBlock.Cond()).To(Equal(cond))
|
|
Expect(ifBlock.Ops()).To(HaveLen(1))
|
|
})
|
|
|
|
It("should handle SetIf within If block", func() {
|
|
enabled := defkit.Bool("enabled")
|
|
cpu := defkit.String("cpu")
|
|
outerCond := defkit.Eq(enabled, defkit.Lit(true))
|
|
|
|
patch := defkit.NewPatchResource()
|
|
patch.If(outerCond).
|
|
SetIf(cpu.IsSet(), "spec.resources.limits.cpu", cpu).
|
|
EndIf()
|
|
|
|
Expect(patch.Ops()).To(HaveLen(1))
|
|
ifBlock := patch.Ops()[0].(*defkit.IfBlock)
|
|
Expect(ifBlock.Ops()).To(HaveLen(1))
|
|
})
|
|
|
|
It("should handle SpreadIf within If block", func() {
|
|
enabled := defkit.Bool("enabled")
|
|
labels := defkit.Object("labels")
|
|
outerCond := defkit.Eq(enabled, defkit.Lit(true))
|
|
|
|
patch := defkit.NewPatchResource()
|
|
patch.If(outerCond).
|
|
SpreadIf(labels.IsSet(), "metadata.labels", labels).
|
|
EndIf()
|
|
|
|
Expect(patch.Ops()).To(HaveLen(1))
|
|
ifBlock := patch.Ops()[0].(*defkit.IfBlock)
|
|
Expect(ifBlock.Ops()).To(HaveLen(1))
|
|
})
|
|
|
|
It("should handle ForEach within If block", func() {
|
|
enabled := defkit.Bool("enabled")
|
|
labels := defkit.Object("labels")
|
|
outerCond := defkit.Eq(enabled, defkit.Lit(true))
|
|
|
|
patch := defkit.NewPatchResource()
|
|
patch.If(outerCond).
|
|
ForEach(labels, "metadata.labels").
|
|
EndIf()
|
|
|
|
Expect(patch.Ops()).To(HaveLen(1))
|
|
ifBlock := patch.Ops()[0].(*defkit.IfBlock)
|
|
Expect(ifBlock.Ops()).To(HaveLen(1))
|
|
})
|
|
|
|
It("should add PatchKey operation", func() {
|
|
container := defkit.NewArrayElement().
|
|
Set("name", defkit.Lit("nginx")).
|
|
Set("image", defkit.Lit("nginx:latest"))
|
|
|
|
patch := defkit.NewPatchResource()
|
|
patch.PatchKey("spec.template.spec.containers", "name", container)
|
|
|
|
Expect(patch.Ops()).To(HaveLen(1))
|
|
patchKeyOp, ok := patch.Ops()[0].(*defkit.PatchKeyOp)
|
|
Expect(ok).To(BeTrue())
|
|
Expect(patchKeyOp.Path()).To(Equal("spec.template.spec.containers"))
|
|
Expect(patchKeyOp.Key()).To(Equal("name"))
|
|
Expect(patchKeyOp.Elements()).To(HaveLen(1))
|
|
})
|
|
|
|
It("should handle PatchKey within If block", func() {
|
|
enabled := defkit.Bool("enabled")
|
|
container := defkit.NewArrayElement().Set("name", defkit.Lit("nginx"))
|
|
outerCond := defkit.Eq(enabled, defkit.Lit(true))
|
|
|
|
patch := defkit.NewPatchResource()
|
|
patch.If(outerCond).
|
|
PatchKey("spec.containers", "name", container).
|
|
EndIf()
|
|
|
|
Expect(patch.Ops()).To(HaveLen(1))
|
|
ifBlock := patch.Ops()[0].(*defkit.IfBlock)
|
|
Expect(ifBlock.Ops()).To(HaveLen(1))
|
|
})
|
|
|
|
It("should add Passthrough operation", func() {
|
|
patch := defkit.NewPatchResource()
|
|
patch.Passthrough()
|
|
|
|
Expect(patch.Ops()).To(HaveLen(1))
|
|
_, ok := patch.Ops()[0].(*defkit.PassthroughOp)
|
|
Expect(ok).To(BeTrue())
|
|
})
|
|
})
|
|
|
|
Context("Template OutputsIf method", func() {
|
|
It("should add conditional auxiliary resource", func() {
|
|
enabled := defkit.Bool("enabled")
|
|
cond := defkit.Eq(enabled, defkit.Lit(true))
|
|
|
|
tpl := defkit.NewTemplate()
|
|
tpl.OutputsIf(cond, "service",
|
|
defkit.NewResource("v1", "Service").
|
|
Set("metadata.name", defkit.VelaCtx().Name()),
|
|
)
|
|
|
|
outputs := tpl.GetOutputs()
|
|
Expect(outputs).To(HaveKey("service"))
|
|
Expect(outputs["service"].Kind()).To(Equal("Service"))
|
|
})
|
|
})
|
|
|
|
Context("ContextOutputRef", func() {
|
|
It("should create context output reference", func() {
|
|
ref := defkit.ContextOutput()
|
|
Expect(ref).NotTo(BeNil())
|
|
Expect(ref.Path()).To(Equal("context.output"))
|
|
})
|
|
|
|
It("should access nested field", func() {
|
|
ref := defkit.ContextOutput().Field("spec.template")
|
|
Expect(ref.Path()).To(Equal("context.output.spec.template"))
|
|
})
|
|
|
|
It("should chain Field calls", func() {
|
|
ref := defkit.ContextOutput().Field("spec").Field("template")
|
|
Expect(ref.Path()).To(Equal("context.output.spec.template"))
|
|
})
|
|
|
|
It("should create HasPath condition", func() {
|
|
cond := defkit.ContextOutput().HasPath("spec.template")
|
|
Expect(cond).NotTo(BeNil())
|
|
|
|
pathCond, ok := cond.(*defkit.ContextPathExistsCondition)
|
|
Expect(ok).To(BeTrue())
|
|
Expect(pathCond.BasePath()).To(Equal("context.output"))
|
|
Expect(pathCond.FieldPath()).To(Equal("spec.template"))
|
|
Expect(pathCond.FullPath()).To(Equal("context.output.spec.template"))
|
|
})
|
|
|
|
It("should create IsSet condition", func() {
|
|
cond := defkit.ContextOutput().IsSet()
|
|
Expect(cond).NotTo(BeNil())
|
|
|
|
pathCond, ok := cond.(*defkit.ContextPathExistsCondition)
|
|
Expect(ok).To(BeTrue())
|
|
Expect(pathCond.FullPath()).To(Equal("context.output"))
|
|
})
|
|
|
|
It("should use ContextOutput in patch condition", func() {
|
|
patch := defkit.NewPatchResource()
|
|
hasTemplate := defkit.ContextOutput().HasPath("spec.template")
|
|
|
|
patch.If(hasTemplate).
|
|
Set("spec.template.metadata.labels.app", defkit.Lit("test")).
|
|
EndIf()
|
|
|
|
Expect(patch.Ops()).To(HaveLen(1))
|
|
})
|
|
})
|
|
|
|
Context("RenderedResource Data method", func() {
|
|
It("should return rendered resource data", func() {
|
|
comp := defkit.NewComponent("test").
|
|
Workload("apps/v1", "Deployment").
|
|
Template(func(tpl *defkit.Template) {
|
|
tpl.Output(
|
|
defkit.NewResource("apps/v1", "Deployment").
|
|
Set("metadata.name", defkit.VelaCtx().Name()).
|
|
Set("spec.replicas", defkit.Lit(3)),
|
|
)
|
|
})
|
|
|
|
rendered := comp.Render(
|
|
defkit.TestContext().WithName("myapp"),
|
|
)
|
|
|
|
data := rendered.Data()
|
|
Expect(data).NotTo(BeNil())
|
|
Expect(data["metadata"].(map[string]any)["name"]).To(Equal("myapp"))
|
|
})
|
|
|
|
It("should return correct APIVersion and Kind", func() {
|
|
comp := defkit.NewComponent("test").
|
|
Workload("apps/v1", "Deployment").
|
|
Template(func(tpl *defkit.Template) {
|
|
tpl.Output(
|
|
defkit.NewResource("apps/v1", "Deployment").
|
|
Set("metadata.name", defkit.VelaCtx().Name()),
|
|
)
|
|
})
|
|
|
|
rendered := comp.Render(
|
|
defkit.TestContext().WithName("myapp"),
|
|
)
|
|
|
|
Expect(rendered.APIVersion()).To(Equal("apps/v1"))
|
|
Expect(rendered.Kind()).To(Equal("Deployment"))
|
|
})
|
|
})
|
|
|
|
Context("DefName and DefType", func() {
|
|
It("should return correct definition name", func() {
|
|
c := defkit.NewComponent("mycomp")
|
|
Expect(c.DefName()).To(Equal("mycomp"))
|
|
})
|
|
|
|
It("should return component definition type", func() {
|
|
c := defkit.NewComponent("test")
|
|
Expect(c.DefType()).To(Equal(defkit.DefinitionTypeComponent))
|
|
})
|
|
})
|
|
|
|
Context("Component Param method", func() {
|
|
It("should add a single parameter using Param method", func() {
|
|
image := defkit.String("image")
|
|
c := defkit.NewComponent("test").
|
|
Param(image)
|
|
Expect(c.GetParams()).To(HaveLen(1))
|
|
Expect(c.GetParams()[0].Name()).To(Equal("image"))
|
|
})
|
|
|
|
It("should chain multiple Param calls", func() {
|
|
c := defkit.NewComponent("test").
|
|
Param(defkit.String("image")).
|
|
Param(defkit.Int("replicas")).
|
|
Param(defkit.Bool("enabled"))
|
|
Expect(c.GetParams()).To(HaveLen(3))
|
|
})
|
|
})
|
|
|
|
Context("Component Helper method", func() {
|
|
It("should add a helper definition with a param", func() {
|
|
probeParam := defkit.Struct("probe").WithFields(
|
|
defkit.Field("path", defkit.ParamTypeString),
|
|
defkit.Field("port", defkit.ParamTypeInt).Default(8080),
|
|
)
|
|
c := defkit.NewComponent("test").
|
|
Helper("HealthProbe", probeParam)
|
|
helpers := c.GetHelperDefinitions()
|
|
Expect(helpers).To(HaveLen(1))
|
|
Expect(helpers[0].GetName()).To(Equal("HealthProbe"))
|
|
Expect(helpers[0].HasParam()).To(BeTrue())
|
|
Expect(helpers[0].GetParam()).To(Equal(probeParam))
|
|
})
|
|
|
|
It("should return empty schema when using param", func() {
|
|
probeParam := defkit.Struct("probe")
|
|
c := defkit.NewComponent("test").
|
|
Helper("Probe", probeParam)
|
|
helpers := c.GetHelperDefinitions()
|
|
Expect(helpers[0].GetSchema()).To(BeEmpty())
|
|
})
|
|
})
|
|
|
|
Context("Component RawCUE method", func() {
|
|
It("should set raw CUE and bypass template generation", func() {
|
|
rawCue := `"webservice": {
|
|
type: "component"
|
|
template: {
|
|
output: { apiVersion: "apps/v1", kind: "Deployment" }
|
|
}
|
|
}`
|
|
c := defkit.NewComponent("webservice").RawCUE(rawCue)
|
|
|
|
Expect(c.GetRawCUE()).To(Equal(rawCue))
|
|
Expect(c.HasRawCUE()).To(BeTrue())
|
|
Expect(c.ToCue()).To(Equal(rawCue))
|
|
})
|
|
})
|
|
|
|
Context("Component WithImports method", func() {
|
|
It("should add imports to the component", func() {
|
|
c := defkit.NewComponent("test").
|
|
WithImports("strconv", "strings")
|
|
Expect(c.GetImports()).To(ConsistOf("strconv", "strings"))
|
|
})
|
|
|
|
It("should accumulate imports with multiple calls", func() {
|
|
c := defkit.NewComponent("test").
|
|
WithImports("strconv").
|
|
WithImports("strings", "list")
|
|
Expect(c.GetImports()).To(HaveLen(3))
|
|
})
|
|
})
|
|
|
|
Context("Component ToYAML method", func() {
|
|
It("should generate valid YAML manifest", func() {
|
|
c := defkit.NewComponent("webservice").
|
|
Description("Web service component").
|
|
Workload("apps/v1", "Deployment").
|
|
Params(defkit.String("image"))
|
|
|
|
yamlBytes, err := c.ToYAML()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
yaml := string(yamlBytes)
|
|
Expect(yaml).To(ContainSubstring("kind: ComponentDefinition"))
|
|
Expect(yaml).To(ContainSubstring("name: webservice"))
|
|
})
|
|
})
|
|
|
|
Context("Component ToCueWithImports method", func() {
|
|
It("should generate CUE with import block", func() {
|
|
c := defkit.NewComponent("test").
|
|
Description("Test").
|
|
Workload("apps/v1", "Deployment")
|
|
|
|
cue := c.ToCueWithImports("strconv", "strings")
|
|
Expect(cue).To(ContainSubstring(`import (`))
|
|
Expect(cue).To(ContainSubstring(`"strconv"`))
|
|
Expect(cue).To(ContainSubstring(`"strings"`))
|
|
})
|
|
})
|
|
|
|
Context("Component ToParameterSchema method", func() {
|
|
It("should generate parameter schema CUE", func() {
|
|
c := defkit.NewComponent("test").
|
|
Params(
|
|
defkit.String("image"),
|
|
defkit.Int("replicas").Default(1),
|
|
)
|
|
|
|
schema := c.ToParameterSchema()
|
|
Expect(schema).To(ContainSubstring("image:"))
|
|
Expect(schema).To(ContainSubstring("replicas:"))
|
|
})
|
|
})
|
|
|
|
Context("ToCue Generation", func() {
|
|
It("should generate complete CUE definition", func() {
|
|
c := defkit.NewComponent("webservice").
|
|
Description("Web service component").
|
|
Workload("apps/v1", "Deployment").
|
|
Params(
|
|
defkit.String("image").Description("Container image"),
|
|
defkit.Int("replicas").Default(1),
|
|
)
|
|
|
|
cue := c.ToCue()
|
|
|
|
Expect(cue).To(ContainSubstring(`webservice: {`))
|
|
Expect(cue).To(ContainSubstring(`type: "component"`))
|
|
Expect(cue).To(ContainSubstring(`description: "Web service component"`))
|
|
Expect(cue).To(ContainSubstring(`apiVersion: "apps/v1"`))
|
|
Expect(cue).To(ContainSubstring(`kind:`))
|
|
Expect(cue).To(ContainSubstring(`"Deployment"`))
|
|
Expect(cue).To(ContainSubstring(`parameter: {`))
|
|
Expect(cue).To(ContainSubstring(`image: string`))
|
|
Expect(cue).To(ContainSubstring(`replicas: *1 | int`))
|
|
})
|
|
|
|
It("should generate CUE with template output", func() {
|
|
c := defkit.NewComponent("test").
|
|
Description("Test component").
|
|
Workload("apps/v1", "Deployment").
|
|
Params(defkit.String("name")).
|
|
Template(func(tpl *defkit.Template) {
|
|
tpl.Output(
|
|
defkit.NewResource("apps/v1", "Deployment").
|
|
Set("metadata.name", defkit.ParamRef("name")),
|
|
)
|
|
})
|
|
|
|
cue := c.ToCue()
|
|
|
|
Expect(cue).To(ContainSubstring(`output:`))
|
|
Expect(cue).To(ContainSubstring(`metadata:`))
|
|
Expect(cue).To(ContainSubstring(`name: parameter.name`))
|
|
})
|
|
|
|
It("should generate CUE with status", func() {
|
|
c := defkit.NewComponent("stateful").
|
|
Description("Stateful component").
|
|
Workload("apps/v1", "Deployment").
|
|
CustomStatus("message: \"Running\"").
|
|
HealthPolicy("isHealth: true")
|
|
|
|
cue := c.ToCue()
|
|
|
|
Expect(cue).To(ContainSubstring(`status:`))
|
|
Expect(cue).To(ContainSubstring(`customStatus:`))
|
|
Expect(cue).To(ContainSubstring(`healthPolicy:`))
|
|
})
|
|
|
|
It("should generate CUE with statusDetails alone", func() {
|
|
c := defkit.NewComponent("foo").
|
|
Workload("apps/v1", "Deployment").
|
|
StatusDetails("message: context.output.status.phase")
|
|
|
|
cue := c.ToCue()
|
|
|
|
Expect(cue).To(ContainSubstring(`status:`))
|
|
Expect(cue).To(ContainSubstring(`details:`))
|
|
Expect(cue).NotTo(ContainSubstring(`customStatus:`))
|
|
Expect(cue).NotTo(ContainSubstring(`healthPolicy:`))
|
|
})
|
|
|
|
It("should generate CUE with all three status fields", func() {
|
|
c := defkit.NewComponent("full-status").
|
|
Workload("apps/v1", "Deployment").
|
|
CustomStatus("message: \"Running\"").
|
|
HealthPolicy("isHealth: true").
|
|
StatusDetails("message: context.output.status.phase")
|
|
|
|
cue := c.ToCue()
|
|
|
|
Expect(cue).To(ContainSubstring(`status:`))
|
|
Expect(cue).To(ContainSubstring(`customStatus:`))
|
|
Expect(cue).To(ContainSubstring(`healthPolicy:`))
|
|
Expect(cue).To(ContainSubstring(`details:`))
|
|
})
|
|
})
|
|
|
|
Context("Full Component Example", func() {
|
|
It("should build a complete webservice component", func() {
|
|
image := defkit.String("image").Description("Container image")
|
|
replicas := defkit.Int("replicas").Default(1)
|
|
port := defkit.Int("port").Default(80)
|
|
|
|
c := defkit.NewComponent("webservice").
|
|
Description("Web service component").
|
|
Workload("apps/v1", "Deployment").
|
|
Params(image, replicas, port).
|
|
Template(func(tpl *defkit.Template) {
|
|
vela := defkit.VelaCtx()
|
|
tpl.Output(
|
|
defkit.NewResource("apps/v1", "Deployment").
|
|
Set("metadata.name", vela.Name()).
|
|
Set("spec.replicas", replicas).
|
|
Set("spec.template.spec.containers[0].image", image).
|
|
Set("spec.template.spec.containers[0].ports[0].containerPort", port),
|
|
)
|
|
tpl.Outputs("service",
|
|
defkit.NewResource("v1", "Service").
|
|
Set("metadata.name", vela.Name()).
|
|
Set("spec.ports[0].port", port),
|
|
)
|
|
})
|
|
|
|
Expect(c.GetName()).To(Equal("webservice"))
|
|
Expect(c.GetParams()).To(HaveLen(3))
|
|
Expect(c.GetWorkload().Kind()).To(Equal("Deployment"))
|
|
|
|
// Execute template to verify it builds correctly
|
|
tpl := defkit.NewTemplate()
|
|
c.GetTemplate()(tpl)
|
|
Expect(tpl.GetOutput()).NotTo(BeNil())
|
|
Expect(tpl.GetOutput().Kind()).To(Equal("Deployment"))
|
|
Expect(tpl.GetOutput().Ops()).To(HaveLen(4))
|
|
Expect(tpl.GetOutputs()).To(HaveKey("service"))
|
|
})
|
|
})
|
|
|
|
Context("Set with malformed bracket paths", func() {
|
|
It("should not panic on missing closing bracket", func() {
|
|
Expect(func() {
|
|
r := defkit.NewResource("v1", "ConfigMap")
|
|
r.Set("data.items[", defkit.Lit("value"))
|
|
}).NotTo(Panic())
|
|
})
|
|
})
|
|
|
|
Context("ChildResourceKind accumulator", func() {
|
|
It("should return nil when not called", func() {
|
|
c := defkit.NewComponent("c")
|
|
Expect(c.GetChildResourceKinds()).To(BeNil())
|
|
})
|
|
|
|
It("should accumulate one entry", func() {
|
|
c := defkit.NewComponent("c").ChildResourceKind("apps/v1", "Deployment", nil)
|
|
kinds := c.GetChildResourceKinds()
|
|
Expect(kinds).To(HaveLen(1))
|
|
Expect(kinds[0].APIVersion).To(Equal("apps/v1"))
|
|
Expect(kinds[0].Kind).To(Equal("Deployment"))
|
|
})
|
|
|
|
It("should accumulate two entries with multiple calls", func() {
|
|
c := defkit.NewComponent("c").
|
|
ChildResourceKind("apps/v1", "Deployment", nil).
|
|
ChildResourceKind("v1", "Pod", nil)
|
|
Expect(c.GetChildResourceKinds()).To(HaveLen(2))
|
|
})
|
|
|
|
It("should preserve selector fields", func() {
|
|
sel := map[string]string{"app": "myapp"}
|
|
c := defkit.NewComponent("c").ChildResourceKind("v1", "Pod", sel)
|
|
Expect(c.GetChildResourceKinds()[0].Selector).To(HaveKeyWithValue("app", "myapp"))
|
|
})
|
|
|
|
It("should emit childResourceKinds in ToYAML when set", func() {
|
|
c := defkit.NewComponent("c").ChildResourceKind("apps/v1", "Deployment", nil)
|
|
yamlBytes, err := c.ToYAML()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(string(yamlBytes)).To(ContainSubstring("childResourceKinds"))
|
|
})
|
|
|
|
It("should omit childResourceKinds in ToYAML when not set", func() {
|
|
c := defkit.NewComponent("c")
|
|
yamlBytes, err := c.ToYAML()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(string(yamlBytes)).NotTo(ContainSubstring("childResourceKinds"))
|
|
})
|
|
|
|
It("should return *ComponentDefinition for chaining", func() {
|
|
c := defkit.NewComponent("c")
|
|
result := c.ChildResourceKind("apps/v1", "Deployment", nil)
|
|
Expect(result).To(Equal(c))
|
|
})
|
|
})
|
|
|
|
Context("PodSpecPath", func() {
|
|
It("should return empty string by default", func() {
|
|
c := defkit.NewComponent("c")
|
|
Expect(c.GetPodSpecPath()).To(Equal(""))
|
|
})
|
|
|
|
It("should set and return pod spec path", func() {
|
|
c := defkit.NewComponent("c").PodSpecPath("spec.template.spec")
|
|
Expect(c.GetPodSpecPath()).To(Equal("spec.template.spec"))
|
|
})
|
|
|
|
It("should emit podSpecPath in ToYAML when set", func() {
|
|
c := defkit.NewComponent("c").PodSpecPath("spec.template.spec")
|
|
yamlBytes, err := c.ToYAML()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(string(yamlBytes)).To(ContainSubstring("podSpecPath: spec.template.spec"))
|
|
})
|
|
|
|
It("should omit podSpecPath in ToYAML when not set", func() {
|
|
c := defkit.NewComponent("c")
|
|
yamlBytes, err := c.ToYAML()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(string(yamlBytes)).NotTo(ContainSubstring("podSpecPath"))
|
|
})
|
|
|
|
It("should return *ComponentDefinition for chaining", func() {
|
|
c := defkit.NewComponent("c")
|
|
result := c.PodSpecPath("spec.template.spec")
|
|
Expect(result).To(Equal(c))
|
|
})
|
|
})
|
|
|
|
Context("Annotations", func() {
|
|
It("should store and return annotations", func() {
|
|
c := defkit.NewComponent("webservice").
|
|
Annotations(map[string]string{"owner": "team-a", "env": "prod"})
|
|
Expect(c.GetAnnotations()).To(HaveKeyWithValue("owner", "team-a"))
|
|
Expect(c.GetAnnotations()).To(HaveKeyWithValue("env", "prod"))
|
|
})
|
|
|
|
It("should render sorted annotation keys in CUE", func() {
|
|
cue := defkit.NewComponent("webservice").
|
|
Annotations(map[string]string{"b": "2", "a": "1"}).
|
|
ToCue()
|
|
Expect(cue).To(ContainSubstring(`annotations: {`))
|
|
aIdx := strings.Index(cue, `"a": "1"`)
|
|
bIdx := strings.Index(cue, `"b": "2"`)
|
|
Expect(aIdx).To(BeNumerically("<", bIdx))
|
|
})
|
|
|
|
It("should keep annotations: {} in CUE when not set", func() {
|
|
cue := defkit.NewComponent("webservice").ToCue()
|
|
Expect(cue).To(ContainSubstring("annotations: {}"))
|
|
})
|
|
|
|
It("should merge user annotations in ToYAML without overriding description", func() {
|
|
c := defkit.NewComponent("webservice").
|
|
Description("My Component").
|
|
Annotations(map[string]string{
|
|
"owner": "team-a",
|
|
})
|
|
yamlBytes, err := c.ToYAML()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
yaml := string(yamlBytes)
|
|
Expect(yaml).To(ContainSubstring("owner: team-a"))
|
|
Expect(yaml).To(ContainSubstring("My Component"))
|
|
})
|
|
|
|
It("should not allow user annotation to override description in ToYAML", func() {
|
|
c := defkit.NewComponent("webservice").
|
|
Description("Actual Description").
|
|
Annotations(map[string]string{
|
|
"definition.oam.dev/description": "Not This",
|
|
})
|
|
yamlBytes, err := c.ToYAML()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
yaml := string(yamlBytes)
|
|
Expect(yaml).To(ContainSubstring("Actual Description"))
|
|
})
|
|
})
|
|
|
|
Context("Labels", func() {
|
|
It("should render label keys sorted alphabetically in CUE", func() {
|
|
cue := defkit.NewComponent("webservice").
|
|
Labels(map[string]string{"z-team": "infra", "a-env": "prod", "m-region": "us"}).
|
|
ToCue()
|
|
|
|
aIdx := strings.Index(cue, `"a-env"`)
|
|
mIdx := strings.Index(cue, `"m-region"`)
|
|
zIdx := strings.Index(cue, `"z-team"`)
|
|
Expect(aIdx).To(BeNumerically(">=", 0))
|
|
Expect(mIdx).To(BeNumerically(">=", 0))
|
|
Expect(zIdx).To(BeNumerically(">=", 0))
|
|
Expect(aIdx).To(BeNumerically("<", mIdx))
|
|
Expect(mIdx).To(BeNumerically("<", zIdx))
|
|
})
|
|
})
|
|
|
|
Context("Outputs ordering", func() {
|
|
It("should render output names sorted alphabetically in CUE", func() {
|
|
comp := defkit.NewComponent("webservice").
|
|
Workload("apps/v1", "Deployment").
|
|
Params(defkit.String("image").Required()).
|
|
Template(func(tpl *defkit.Template) {
|
|
tpl.Output(defkit.NewResource("apps/v1", "Deployment").
|
|
Set("metadata.name", defkit.VelaCtx().Name()))
|
|
tpl.Outputs("zebra-svc", defkit.NewResource("v1", "Service").
|
|
Set("metadata.name", defkit.VelaCtx().Name()))
|
|
tpl.Outputs("alpha-cm", defkit.NewResource("v1", "ConfigMap").
|
|
Set("metadata.name", defkit.VelaCtx().Name()))
|
|
tpl.Outputs("mid-secret", defkit.NewResource("v1", "Secret").
|
|
Set("metadata.name", defkit.VelaCtx().Name()))
|
|
})
|
|
|
|
cue := comp.ToCue()
|
|
|
|
alphaIdx := strings.Index(cue, "alpha-cm")
|
|
midIdx := strings.Index(cue, "mid-secret")
|
|
zebraIdx := strings.Index(cue, "zebra-svc")
|
|
Expect(alphaIdx).To(BeNumerically(">=", 0))
|
|
Expect(midIdx).To(BeNumerically(">=", 0))
|
|
Expect(zebraIdx).To(BeNumerically(">=", 0))
|
|
Expect(alphaIdx).To(BeNumerically("<", midIdx))
|
|
Expect(midIdx).To(BeNumerically("<", zebraIdx))
|
|
})
|
|
})
|
|
|
|
Context("OmitWorkloadType", func() {
|
|
It("should default to false", func() {
|
|
c := defkit.NewComponent("test").Workload("apps/v1", "Deployment")
|
|
Expect(c.IsOmitWorkloadType()).To(BeFalse())
|
|
})
|
|
|
|
It("should set omit workload type flag", func() {
|
|
c := defkit.NewComponent("test").
|
|
Workload("apps/v1", "Deployment").
|
|
OmitWorkloadType()
|
|
Expect(c.IsOmitWorkloadType()).To(BeTrue())
|
|
})
|
|
|
|
It("should suppress workload type in CUE output", func() {
|
|
comp := defkit.NewComponent("test").
|
|
Workload("apps/v1", "Deployment").
|
|
OmitWorkloadType().
|
|
Template(func(tpl *defkit.Template) {
|
|
tpl.Output(defkit.NewResource("apps/v1", "Deployment").
|
|
Set("metadata.name", defkit.Lit("test")))
|
|
})
|
|
|
|
cue := comp.ToCue()
|
|
Expect(cue).To(ContainSubstring(`kind: "Deployment"`))
|
|
Expect(cue).NotTo(ContainSubstring(`type: "deployments.apps"`))
|
|
Expect(cue).NotTo(ContainSubstring(`type: "autodetects.core.oam.dev"`))
|
|
})
|
|
|
|
It("should include workload type when not omitted", func() {
|
|
comp := defkit.NewComponent("test").
|
|
Workload("apps/v1", "Deployment").
|
|
Template(func(tpl *defkit.Template) {
|
|
tpl.Output(defkit.NewResource("apps/v1", "Deployment").
|
|
Set("metadata.name", defkit.Lit("test")))
|
|
})
|
|
|
|
cue := comp.ToCue()
|
|
Expect(cue).To(ContainSubstring(`type: "deployments.apps"`))
|
|
})
|
|
})
|
|
|
|
Context("Validators on Component", func() {
|
|
It("should store validators via Validators method", func() {
|
|
v1 := defkit.Validate("a").WithName("_a")
|
|
v2 := defkit.Validate("b").WithName("_b")
|
|
|
|
c := defkit.NewComponent("test").Validators(v1, v2)
|
|
Expect(c.GetValidators()).To(HaveLen(2))
|
|
Expect(c.GetValidators()[0]).To(Equal(v1))
|
|
Expect(c.GetValidators()[1]).To(Equal(v2))
|
|
})
|
|
|
|
It("should accumulate validators across multiple calls", func() {
|
|
c := defkit.NewComponent("test").
|
|
Validators(defkit.Validate("a").WithName("_a")).
|
|
Validators(defkit.Validate("b").WithName("_b"))
|
|
Expect(c.GetValidators()).To(HaveLen(2))
|
|
})
|
|
|
|
It("should return empty when no validators set", func() {
|
|
c := defkit.NewComponent("test")
|
|
Expect(c.GetValidators()).To(BeEmpty())
|
|
})
|
|
})
|
|
|
|
Context("ConditionalParams on Component", func() {
|
|
It("should store conditional param blocks", func() {
|
|
block := defkit.ConditionalParams(
|
|
defkit.WhenParam(defkit.Bool("x").Eq(true)).Params(defkit.String("a")),
|
|
)
|
|
c := defkit.NewComponent("test").ConditionalParams(block)
|
|
Expect(c.GetConditionalParamBlocks()).To(HaveLen(1))
|
|
Expect(c.GetConditionalParamBlocks()[0]).To(Equal(block))
|
|
})
|
|
|
|
It("should accumulate blocks across multiple calls", func() {
|
|
b1 := defkit.ConditionalParams(
|
|
defkit.WhenParam(defkit.Bool("x").Eq(true)).Params(defkit.String("a")),
|
|
)
|
|
b2 := defkit.ConditionalParams(
|
|
defkit.WhenParam(defkit.Bool("y").Eq(true)).Params(defkit.String("b")),
|
|
)
|
|
c := defkit.NewComponent("test").
|
|
ConditionalParams(b1).
|
|
ConditionalParams(b2)
|
|
Expect(c.GetConditionalParamBlocks()).To(HaveLen(2))
|
|
})
|
|
|
|
It("should return empty when no blocks set", func() {
|
|
c := defkit.NewComponent("test")
|
|
Expect(c.GetConditionalParamBlocks()).To(BeEmpty())
|
|
})
|
|
})
|
|
|
|
Context("Repeated generation stability", func() {
|
|
It("should produce identical CUE across 20 runs", func() {
|
|
build := func() string {
|
|
return defkit.NewComponent("webservice").
|
|
Labels(map[string]string{"z": "3", "a": "1", "m": "2"}).
|
|
Workload("apps/v1", "Deployment").
|
|
Params(defkit.String("image").Required()).
|
|
Template(func(tpl *defkit.Template) {
|
|
tpl.Output(defkit.NewResource("apps/v1", "Deployment").
|
|
Set("metadata.name", defkit.VelaCtx().Name()))
|
|
tpl.Outputs("z-svc", defkit.NewResource("v1", "Service").
|
|
Set("metadata.name", defkit.VelaCtx().Name()))
|
|
tpl.Outputs("a-cm", defkit.NewResource("v1", "ConfigMap").
|
|
Set("metadata.name", defkit.VelaCtx().Name()))
|
|
}).
|
|
ToCue()
|
|
}
|
|
|
|
first := build()
|
|
for i := 0; i < 20; i++ {
|
|
Expect(build()).To(Equal(first), "CUE output differed on iteration %d", i)
|
|
}
|
|
})
|
|
})
|
|
})
|