Skip to main content
Version: v1.11-alpha

Complex Parameter Types

Complex parameter types compose multiple fields or variant shapes into a single parameter. Use Struct when you need a named top-level sub-object with a typed, fixed set of fields (and access via .Field() refs in templates); Object as the fluent alias for the same concept when building nested configs; OneOf with Variant for discriminated unions where a selector field determines which set of sibling fields is active; ClosedUnion with ClosedStruct when the union shape must be fully closed (no extra fields permitted) and there is no single discriminator key; Field with ParamType* constants to declare typed fields inside Struct.WithFields() programmatically. All these types participate in the standard parameter chain (Required(), Optional(), Description()) and integrate with SetIf conditions through .IsSet(), .Eq(), and .Field() field references.

defkit.Struct() and defkit.Field()

MethodDescription
defkit.Struct(name string) *StructParamCreates a named struct parameter with typed, fixed fields. Generates a nested CUE struct name?: { ... }. Chain .WithFields(...) to declare each field.
*StructParam.WithFields(fields ...*StructField) *StructParamAdds field definitions to the struct. Fields are created with defkit.Field(name, paramType) and its modifier chain.
*StructParam.Required() *StructParamMarks the struct as required (name!:).
*StructParam.Optional() *StructParamMarks the struct as optional (name?:). Without either modifier, the parameter is required by default.
*StructParam.Description(desc string) *StructParamSets the usage comment shown in vela show. Generates // +usage=desc.
*StructParam.WithSchemaRef(ref string) *StructParamReferences an external CUE type definition instead of inline fields (e.g. "#ResourceSpec"). The helper must be declared separately via tpl.Helper().
*StructParam.Field(fieldPath string) *ParamFieldRefReturns a reference to a nested field within this struct for use in .Set() / .SetIf() value positions (e.g. resources.Field("cpu")parameter.resources.cpu).

defkit.Field() and StructField methods

MethodDescription
defkit.Field(name string, fieldType ParamType) *StructFieldCreates a typed field for use inside Struct.WithFields(). fieldType is one of the ParamType* constants.
*StructField.Required() *StructFieldMarks the field as required (field!: type).
*StructField.Optional() *StructFieldMarks the field as optional (field?: type).
*StructField.Default(value any) *StructFieldSets a default value. Generates field: *value | type.
*StructField.Description(desc string) *StructFieldSets the field's usage comment.
*StructField.Values(values ...string) *StructFieldRestricts a string field to a fixed enum. Generates field: "a" | "b".
*StructField.Of(elemType ParamType) *StructFieldSets the element type when this field holds an array ([...string], [...int], etc.).
*StructField.Nested(s *StructParam) *StructFieldMakes the field's type a nested struct, creating hierarchical schemas.
*StructField.WithSchemaRef(ref string) *StructFieldReferences an external CUE type definition for this field.

ParamType* constants

ConstantCUE type
defkit.ParamTypeStringstring
defkit.ParamTypeIntint
defkit.ParamTypeBoolbool
defkit.ParamTypeFloatfloat

defkit.Object()

defkit.Object(name string) *MapParam is a convenience alias for defkit.Map(name). It is documented separately because it reads more naturally when representing a config object with a known, closed set of named fields. Use it in place of Map when the intent is a structured sub-object rather than a dictionary. It supports the full MapParam chain: .WithFields(...), .Of(valueType), .WithSchema(schema), .WithSchemaRef(ref), .Field(fieldPath), .Required(), .Optional(), .Description(), condition methods (.IsSet(), .IsNotEmpty(), .HasKey(), .LenEq(), .LenGt(), .IsEmpty()).

tip

Object and Struct both generate nested CUE structs, but their field-declaration APIs differ. Object.WithFields(...) accepts Param constructors (defkit.String(...), defkit.Int(...), etc.) while Struct.WithFields(...) accepts *StructField values created with defkit.Field(name, paramType). Use Struct when you need the full StructField modifier set (Values, Of, Nested, WithSchemaRef). Use Object when you want to reuse the scalar param constructors you already know.

defkit.OneOf() and defkit.Variant()

MethodDescription
defkit.OneOf(name string) *OneOfParamCreates a discriminated union parameter. The parameter itself becomes the enum selector field. Generates name?: "variantA" | "variantB" plus if name == "variantA" { ... } blocks for each variant's fields at the same schema level.
*OneOfParam.Variants(variants ...*OneOfVariant) *OneOfParamAdds variant definitions. Each variant is built with defkit.Variant(name).WithFields(...).
*OneOfParam.Discriminator(field string) *OneOfParamNames the field used to distinguish variants. This is for tooling and metadata only; it does not change the generated CUE — the param name is always used as the enum field name.
*OneOfParam.Default(value string) *OneOfParamSets the default variant name. Generates name: *"variantA" | "variantB".
*OneOfParam.Required() *OneOfParamMarks the union as required.
*OneOfParam.Optional() *OneOfParamMarks the union as optional.
*OneOfParam.Description(desc string) *OneOfParamSets the usage comment.
defkit.Variant(name string) *OneOfVariantStarts a variant definition. Chain .WithFields(...) to declare the fields exposed when this variant is selected.
*OneOfVariant.WithFields(fields ...*StructField) *OneOfVariantDeclares the fields active when this variant is selected. Uses the same *StructField API as Struct.WithFields().

defkit.ClosedUnion() and defkit.ClosedStruct()

MethodDescription
defkit.ClosedUnion(name string) *ClosedUnionParamCreates a closed struct disjunction. Generates name?: close({...}) | close({...}). Use when there is no single discriminator key and the parameter must match exactly one of several fully-closed shapes.
*ClosedUnionParam.Options(options ...*ClosedStructOption) *ClosedUnionParamAdds closed-struct alternatives. Each option is built with defkit.ClosedStruct().WithFields(...).
*ClosedUnionParam.Required() *ClosedUnionParamMarks the union as required.
*ClosedUnionParam.Optional() *ClosedUnionParamMarks the union as optional.
*ClosedUnionParam.Description(desc string) *ClosedUnionParamSets the usage comment.
defkit.ClosedStruct() *ClosedStructOptionCreates one option in a closed disjunction. Chain .WithFields(...) to add fields. Extra fields not listed here are rejected by CUE.
*ClosedStructOption.WithFields(fields ...*StructField) *ClosedStructOptionAdds fields to this closed struct option. Uses the same *StructField API.
caution

OneOf and ClosedUnion solve different modelling problems. OneOf requires a distinguishing enum value (the param itself) that selects which sibling fields appear — good for mutually exclusive modes ("pvc" vs "emptyDir"). ClosedUnion generates a CUE disjunction of fully-closed structs where CUE itself infers which alternative matches — good for shape-based unions where no single discriminator key is practical (e.g. ClusterIP vs NodePort service specs whose distinguishing field is also a data field).

Example

The complex-types-demo component exercises all four complex-type constructors in a single definition: Struct("resources").WithFields(Field("cpu", ParamTypeString), Field("memory", ParamTypeString), Field("gpu", ParamTypeInt)) — demonstrating Field, Default, Optional, and the ParamType* constants; Object("config").WithFields(String("logLevel"), String("outputFormat")) — showing Object with scalar param constructors and .Field() ref access in the template; OneOf("storage").Variants(Variant("emptyDir"), Variant("pvc")) — generating the enum selector plus conditional sibling fields; and ClosedUnion("endpoint").Options(ClosedStruct().WithFields(...), ClosedStruct().WithFields(...)) — emitting a closed struct disjunction. Building on the my-platform module scaffolded in Quick Start, drop the file below into my-platform/components/.

package components

import "github.com/oam-dev/kubevela/pkg/definition/defkit"

func ComplexTypesDemo() *defkit.ComponentDefinition {
resources := defkit.Struct("resources").
Optional().
Description("CPU and memory requirements for the container").
WithFields(
defkit.Field("cpu", defkit.ParamTypeString).Default("100m"),
defkit.Field("memory", defkit.ParamTypeString).Default("128Mi"),
defkit.Field("gpu", defkit.ParamTypeInt).Optional(),
)

config := defkit.Object("config").
Optional().
Description("Application configuration").
WithFields(
defkit.String("logLevel").Default("info"),
defkit.String("outputFormat").Optional(),
)

storage := defkit.OneOf("storage").
Optional().
Description("Storage backend for the workload").
Variants(
defkit.Variant("emptyDir").WithFields(
defkit.Field("sizeLimit", defkit.ParamTypeString).Optional(),
),
defkit.Variant("pvc").WithFields(
defkit.Field("size", defkit.ParamTypeString),
defkit.Field("storageClass", defkit.ParamTypeString).Default("standard"),
),
)

endpoint := defkit.ClosedUnion("endpoint").
Optional().
Description("Endpoint exposure mode: either a ClusterIP or a NodePort").
Options(
defkit.ClosedStruct().WithFields(
defkit.Field("type", defkit.ParamTypeString).Default("ClusterIP"),
defkit.Field("port", defkit.ParamTypeInt),
),
defkit.ClosedStruct().WithFields(
defkit.Field("type", defkit.ParamTypeString).Default("NodePort"),
defkit.Field("port", defkit.ParamTypeInt),
defkit.Field("nodePort", defkit.ParamTypeInt).Optional(),
),
)

return defkit.NewComponent("complex-types-demo").
Description("Demonstrates Struct, Object, OneOf/Variant, ClosedUnion/ClosedStruct, and Field/ParamType constants").
Workload("apps/v1", "Deployment").
Params(resources, config, storage, endpoint).
Template(complexTypesDemoTemplate)
}

func complexTypesDemoTemplate(tpl *defkit.Template) {
vela := defkit.VelaCtx()

resources := defkit.Struct("resources").WithFields(
defkit.Field("cpu", defkit.ParamTypeString),
defkit.Field("memory", defkit.ParamTypeString),
defkit.Field("gpu", defkit.ParamTypeInt).Optional(),
)
config := defkit.Object("config").WithFields(
defkit.String("logLevel"),
defkit.String("outputFormat").Optional(),
)
storage := defkit.OneOf("storage")

dep := defkit.NewResource("apps/v1", "Deployment").
Set("metadata.name", vela.Name()).
Set("spec.selector.matchLabels[app.oam.dev/component]", vela.Name()).
Set("spec.template.metadata.labels[app.oam.dev/component]", vela.Name()).
Set("spec.template.spec.containers[0].name", vela.Name()).
Set("spec.template.spec.containers[0].image", defkit.Lit("nginx:stable")).
SetIf(resources.IsSet(), "spec.template.spec.containers[0].resources.requests.cpu",
resources.Field("cpu")).
SetIf(resources.IsSet(), "spec.template.spec.containers[0].resources.requests.memory",
resources.Field("memory")).
SetIf(config.IsSet(), "spec.template.metadata.labels[log-level]",
config.Field("logLevel")).
SetIf(storage.IsSet(), "spec.template.metadata.labels[storage-type]",
storage)

tpl.Output(dep)
}

func init() { defkit.Register(ComplexTypesDemo()) }

Reproduce the CUE on the right with:

vela def validate-module ./my-platform
vela def gen-module ./my-platform -o ./generated-cue

Apply and verify

Apply the definition and the Application YAML above against a live cluster:

vela def apply ./generated-cue/components/complex-types-demo.cue
vela up -f complex-types-app.yaml
vela status complex-types-app --namespace default
About:

Name: complex-types-app
Namespace: default
Healthy: true
Details: running

Services:

- Name: complex-types
Cluster: local
Namespace: default
Type: complex-types-demo
Health: true
No trait applied

Once the Application reaches running, inspect the rendered Deployment to confirm that resources, config, and storage landed correctly. endpoint is a schema-only demonstration here — see the note after the captured output. Output below was captured live against a k3d cluster:

$ kubectl get deployment complex-types -n default \
-o jsonpath='{.spec.template.spec.containers[0].resources}' \
| python3 -m json.tool
{
"requests": {
"cpu": "250m",
"memory": "256Mi"
}
}

$ kubectl get deployment complex-types -n default \
-o jsonpath='{.spec.template.metadata.labels}' \
| python3 -m json.tool
{
"app.oam.dev/component": "complex-types",
"log-level": "debug",
"storage-type": "pvc"
}

$ kubectl get deployment complex-types -n default \
-o jsonpath='NAME={.metadata.name} READY={.status.readyReplicas}/{.status.replicas}'
NAME=complex-types READY=1/1

resources.Field("cpu") resolves to parameter.resources.cpu and lands 250m in the container resource requests. config.Field("logLevel") resolves to parameter.config.logLevel and writes log-level=debug as a pod label. storage: pvc activates the pvc branch of the OneOf enum, making size and storageClass required and writing storage-type=pvc as a pod label via storage.IsSet().

The endpoint ClosedUnion parameter is schema-only in this template. The Application's endpoint: { type: ClusterIP, port: 8080 } is validated against the closed disjunction (extra fields are rejected by CUE), but it is never consumed by complexTypesDemoTemplate and therefore does not appear on the rendered Deployment. This is by design of the fluent API: ClosedUnionParam exposes Options/Required/Optional/Description only — there is no .Field() accessor analogous to Struct.Field() / Object.Field(), because each option may declare a different field set. To consume an inner value from a template, drop into raw CUE with defkit.Reference("parameter.endpoint.type") (and remember that for the disjunction to resolve, each option must differ in at least one literal-valued field — *"X" | string defaults are too permissive to discriminate when the user supplies the field).

Clean up afterwards:

vela delete complex-types-app --namespace default --yes
kubectl delete componentdefinition complex-types-demo -n vela-system