Files
kubevela/design/vela-cli/kep-defkit.md
Anoop Gopalakrishnan 9178a03cc0 Docs(KEP): Go SDK for X-Definition Authoring (defkit)
Introduces KEP proposal for defkit, a Go SDK that enables platform
  engineers to author X-Definitions using native Go code instead of CUE.

  Key proposed features:
  - Fluent builder API for Component, Trait, Policy, and WorkflowStep definitions
  - Transparent Go-to-CUE compilation
  - IDE support with autocomplete and type checking
  - Schema-agnostic resource construction
  - Collection operations (map, filter, dedupe)
  - Composable health and status expressions
  - Addon integration with godef/ folder support
  - Module dependencies for definition sharing via go get

Signed-off-by: Anoop Gopalakrishnan <agopalakrishnan@guidewire.com>
2025-12-14 18:57:44 -08:00

74 KiB

KEP: Definition Kit (defkit) - Go SDK for X-Definition Authoring

Summary

Definition Kit (defkit) is a Go SDK that enables platform engineers to author KubeVela X-Definitions using native Go code instead of CUE. The SDK compiles Go to CUE transparently, providing full IDE support while maintaining compatibility with the KubeVela controller.

Status: This document is a design proposal. While the core architecture and goals are established, specific API names, method signatures, and implementation details may evolve as we proceed with implementation and incorporate community feedback.

Motivation

Goals

  1. Author X-Definitions in Go - Write ComponentDefinition, TraitDefinition, PolicyDefinition, and WorkflowStepDefinition using fluent Go APIs
  2. Transparent CUE compilation - Go code compiles to CUE automatically; developers never see or write CUE
  3. Full IDE support - Autocomplete, type checking, and inline documentation
  4. Testable definitions - Unit test definitions using standard Go testing frameworks
  5. Schema-agnostic resource construction - Support any Kubernetes resource without coupling to K8s versions
  6. Easy distribution via Go modules - Share and version X-Definitions as standard Go packages, enabling go get for platform capabilities

Non-Goals

  1. Multi-language support in initial release (Go only)
  2. Replacing CUE as the controller's internal engine
  3. Runtime CUE evaluation in the SDK

Proposal

Why Go for X-Definitions?

1. Familiar Tooling - Platform engineers already use Go for controllers, operators, and CLI tools. Writing definitions in the same language eliminates context switching and leverages existing skills.

2. IDE Experience - Full autocomplete, type checking, go-to-definition, and inline documentation. No special CUE plugins required.

3. Standard Distribution - Definitions become Go packages that can be versioned, shared via go get, and composed like any other library. No custom registries or tooling needed.

4. Testability - Use standard Go testing frameworks (go test, testify, gomega) to unit test definitions before deployment. Mock contexts enable testing without a cluster.

5. Compile-Time Safety - Catch errors at compile time rather than at deployment time. Invalid field names, type mismatches, and missing required parameters are caught immediately.

Fluent API Design

defkit provides a fluent builder API where parameters are defined inline and bound to local variables. All types are in a single defkit package for simplicity:

func WebserviceDefinition() *defkit.ComponentDefinition {
    // Parameters defined as local variables within the function
    image := defkit.String("image").Required()
    replicas := defkit.Int("replicas").Default(3).Min(1).Max(100)
    cpu := defkit.String("cpu")

    return defkit.NewComponent("webservice").
        Description("A production-ready web service").
        Workload("apps/v1", "Deployment").
        Params(image, replicas, cpu).
        Template(func(tpl *defkit.Template) {
            vela := defkit.VelaCtx()

            deploy := defkit.NewResource("apps/v1", "Deployment").
                Set("spec.replicas", replicas).
                Set("spec.selector.matchLabels[app.oam.dev/component]", vela.Name()).
                Set("spec.template.spec.containers[0].name", vela.Name()).
                Set("spec.template.spec.containers[0].image", image).
                // Optional parameters use fluent conditional
                SetIf(cpu.IsSet(), "spec.template.spec.containers[0].resources.limits.cpu", cpu)

            tpl.Output(deploy)
        })
}

Key design insight: Parameters are Go variables that can be used directly in both definition and template. No string lookups, no dual references. The variable image carries both its schema definition AND serves as the accessor.

This reads naturally: "Define image, replicas, and cpu as parameters. Create a webservice component using them."

Note: Parameters are defined as local variables within the function (not package-level) to ensure proper encapsulation and test isolation. The template function receives a Template context (named tpl to avoid confusion with Go's context package). Runtime context like component name and namespace is accessed via defkit.VelaCtx().

Common Patterns

The SDK provides typed helpers for common definition patterns:

Pattern Go API Example
Required parameter defkit.String("image").Required() image must be provided
Default value defkit.Int("replicas").Default(3) replicas defaults to 3
Validation defkit.Int("replicas").Min(1).Max(100) replicas between 1-100
Enums defkit.Enum("policy", "Always", "Never") policy with allowed values
Optional object defkit.Object("persistence", ...).Optional() persistence block
Lists defkit.List("args", defkit.String()) args list

Runtime Context Access

CUE templates access runtime values (like deployment status) through well-known paths. The Go SDK provides fluent methods via defkit.VelaCtx() that generate these paths:

vela := defkit.VelaCtx()
vela.Name()                  // → context.name
vela.Namespace()             // → context.namespace
vela.AppName()               // → context.appName
vela.AppRevision()           // → context.appRevision
vela.ClusterVersion().Minor() // → context.clusterVersion.minor

Runtime context is accessed separately from template context, which focuses on output generation. Health policies and custom status use the built-in helpers:

// Health check using deployment helper
def.HealthPolicy(defkit.DeploymentHealth().Build())

// Custom status using deployment helper
def.CustomStatus(defkit.DeploymentStatus().Build())

When to Use What

┌─────────────────────────────────────────────────────────────────┐
│                     DECISION FRAMEWORK                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   Can I express it with defkit Go API?                          │
│                                                                 │
│       YES ──────────► Use Go (95% of cases)                     │
│                       • Static values: Set("image", "nginx")    │
│                       • Parameters: use the variable directly   │
│                       • Custom status: defkit.Status().Field()  │
│                       • Cluster info: vela.ClusterVersion()     │
│                                                                 │
│       NO ───────────► Use RawCUE() (rare escape hatch)          │
│                       • True unification semantics (a & b)      │
│                       • Complex nested comprehensions (3+ levels)│
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Target Developer Experience

package myplatform

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

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

func WebserviceComponent() *defkit.ComponentDefinition {
    // Parameters scoped to definition function
    image := defkit.String("image").Required()
    replicas := defkit.Int("replicas").Default(3).Min(1).Max(100)
    cpu := defkit.String("cpu").Optional()

    return defkit.NewComponent("webservice").
        Description("A production-ready web service").
        Workload("apps/v1", "Deployment").
        Params(image, replicas, cpu).
        Template(func(tpl *defkit.Template) {
            vela := defkit.VelaCtx()

            deploy := defkit.NewResource("apps/v1", "Deployment").
                Set("metadata.name", vela.Name()).
                Set("spec.replicas", replicas).
                Set("spec.selector.matchLabels[app.oam.dev/component]", vela.Name()).
                Set("spec.template.spec.containers", []map[string]any{{
                    "name":  vela.Name(),
                    "image": image,
                }}).
                SetIf(cpu.IsSet(), "spec.template.spec.containers[0].resources.limits.cpu", cpu)

            tpl.Output(deploy)
        }).
        // Health policy using deployment helper
        HealthPolicy(defkit.DeploymentHealth().Build()).
        // Custom status using deployment helper
        CustomStatus(defkit.DeploymentStatus().Build())
}

VelaContext API Reference

Runtime Context Accessors

The defkit.VelaCtx() function provides access to runtime context values:

Go Method Generated CUE Description
vela.Name() context.name Component name
vela.Namespace() context.namespace Target namespace
vela.AppName() context.appName Application name
vela.AppRevision() context.appRevision Application revision
vela.AppLabels() context.appLabels Application labels
vela.AppAnnotations() context.appAnnotations Application annotations
vela.Revision() context.revision Component revision
vela.ClusterVersion().Minor() context.clusterVersion.minor K8s minor version
vela.ClusterVersion().Major() context.clusterVersion.major K8s major version

Parameter Variables

Parameters are typed variables that generate CUE paths automatically:

Go Usage Generated CUE Description
image (in template) parameter.image Parameter value (from variable name)
cpu.IsSet() parameter.cpu != _|_ Check if optional parameter is set
persistence.Field("size") parameter.persistence.size Nested field access

How it works: Each parameter variable knows its name and type. When used in a template expression, it compiles to the corresponding parameter.X CUE path.

Comparison and Conditional Helpers

Go Method Generated CUE Description
defkit.Eq(a, b) a == b Equality comparison
defkit.Ne(a, b) a != b Inequality comparison
defkit.Lt(a, b) a < b Less than
defkit.Le(a, b) a <= b Less than or equal
defkit.Gt(a, b) a > b Greater than
defkit.Ge(a, b) a >= b Greater than or equal
defkit.And(a, b) a && b Logical AND
defkit.Or(a, b) a || b Logical OR
defkit.Not(cond) !cond Logical NOT
defkit.If(cond) if cond {...} Conditional block
defkit.Lit(value) value Literal value (for comparisons)
param.IsSet() param != _|_ Check if optional param is set

Examples

Health Policy

Composable Health Expressions

defkit provides a fully composable expression-based API for defining health policies. This enables health checks for any resource type, not just Kubernetes workloads.

Core Primitives:

All health expression methods are accessed via the Health() builder, providing a unified API:

Primitive Purpose Example
Health().Condition(type) Check status.conditions[] array Health().Condition("Ready").IsTrue()
Health().Phase(phases...) Check status.phase field Health().Phase("Running", "Succeeded")
Health().Field(path) Compare any status field Health().Field("status.state").Eq("active")
Health().Exists(path) Check field existence Health().Exists("status.endpoint")
Health().And(...) All expressions must be true Health().And(expr1, expr2)
Health().Or(...) Any expression must be true Health().Or(expr1, expr2)
Health().Not(...) Negate expression Health().Not(h.Condition("Stalled").IsTrue())
Health().AllTrue(conds...) All conditions are True Health().AllTrue("Ready", "Synced")
Health().AnyTrue(conds...) Any condition is True Health().AnyTrue("Ready", "Available")
Health().Always() Always healthy (existence) Health().Always()

Condition Expressions:

h := defkit.Health()

// Single condition check (most CRDs, Crossplane, cert-manager, etc.)
HealthPolicyExpr(h.Condition("Ready").IsTrue())

// Multiple conditions - all must be true
HealthPolicyExpr(h.AllTrue("Ready", "Synced"))

// Multiple conditions - any is sufficient
HealthPolicyExpr(h.AnyTrue("Ready", "Available"))

// Check condition exists (regardless of status)
HealthPolicyExpr(h.Condition("Initialized").Exists())

// Check condition reason
HealthPolicyExpr(h.Condition("Ready").ReasonIs("Available"))

Generated CUE for h.Condition("Ready").IsTrue():

_readyCond: [ for c in context.output.status.conditions if c.type == "Ready" { c } ]
isHealth: len(_readyCond) > 0 && _readyCond[0].status == "True"

Generated CUE for h.AllTrue("Ready", "Synced"):

_readyCond: [ for c in context.output.status.conditions if c.type == "Ready" { c } ]
_syncedCond: [ for c in context.output.status.conditions if c.type == "Synced" { c } ]
isHealth: (len(_readyCond) > 0 && _readyCond[0].status == "True") &&
          (len(_syncedCond) > 0 && _syncedCond[0].status == "True")

Phase Expressions:

h := defkit.Health()

// Pod-style phase check
HealthPolicyExpr(h.Phase("Running", "Succeeded"))

// Custom phase field path
HealthPolicyExpr(h.PhaseField("status.currentPhase", "Active", "Ready"))

Generated CUE for h.Phase("Running", "Succeeded"):

isHealth: context.output.status.phase == "Running" ||
          context.output.status.phase == "Succeeded"

Field Expressions:

h := defkit.Health()

// Field equality
HealthPolicyExpr(h.Field("status.state").Eq("active"))

// Numeric comparisons
HealthPolicyExpr(h.Field("status.replicas").Gt(0))
HealthPolicyExpr(h.Field("status.availableReplicas").Gte(1))

// Field existence
HealthPolicyExpr(h.Exists("status.loadBalancer.ingress"))

// Field in set of values
HealthPolicyExpr(h.Field("status.phase").In("Running", "Succeeded", "Complete"))

// Compare field to another field
HealthPolicyExpr(h.Field("status.readyReplicas").Eq(h.FieldRef("spec.replicas")))

Generated CUE for h.Field("status.state").Eq("active"):

isHealth: context.output.status.state == "active"

Generated CUE for h.Field("status.readyReplicas").Eq(h.FieldRef("spec.replicas")):

isHealth: context.output.status.readyReplicas == context.output.spec.replicas

Composite Expressions:

h := defkit.Health()

// Complex composition with And/Or/Not
HealthPolicyExpr(h.And(
    h.Condition("Ready").IsTrue(),
    h.Not(h.Condition("Stalled").IsTrue()),
))

// Mixed patterns
HealthPolicyExpr(h.And(
    h.Condition("Ready").IsTrue(),
    h.Or(
        h.Field("status.replicas").Gte(1),
        h.Exists("status.endpoint"),
    ),
))

// Real-world: Custom CRD with arbitrary status structure
HealthPolicyExpr(h.And(
    h.Field("status.state").Eq("active"),
    h.Exists("status.connectionString"),
    h.Field("status.lastSyncTime").Exists(),
))

Generated CUE for nested And/Or:

_readyCond: [ for c in context.output.status.conditions if c.type == "Ready" { c } ]
isHealth: (len(_readyCond) > 0 && _readyCond[0].status == "True") &&
          ((context.output.status.replicas >= 1) || (context.output.status.endpoint != _|_))

Field Expression Methods:

Method CUE Output Description
.Eq(value) field == value Equal to
.Ne(value) field != value Not equal to
.Gt(value) field > value Greater than
.Gte(value) field >= value Greater than or equal
.Lt(value) field < value Less than
.Lte(value) field <= value Less than or equal
.In(values...) field == v1 || field == v2 In set of values
.Contains(substr) String contains For string fields
.Eq(h.FieldRef(path)) field == otherField Compare to another field

Built-in Workload Helpers

For common Kubernetes workloads, defkit provides pre-configured helpers that implement the standard health patterns:

// For Deployments - use the built-in helper
HealthPolicy(defkit.DeploymentHealth().Build())

Generated CUE:

ready: {
    updatedReplicas:    *0 | int
    readyReplicas:      *0 | int
    replicas:           *0 | int
    observedGeneration: *0 | int
} & {
    if context.output.status.updatedReplicas != _|_ {
        updatedReplicas: context.output.status.updatedReplicas
    }
    if context.output.status.readyReplicas != _|_ {
        readyReplicas: context.output.status.readyReplicas
    }
    if context.output.status.replicas != _|_ {
        replicas: context.output.status.replicas
    }
    if context.output.status.observedGeneration != _|_ {
        observedGeneration: context.output.status.observedGeneration
    }
}
_isHealth: (context.output.spec.replicas == ready.readyReplicas) &&
           (context.output.spec.replicas == ready.updatedReplicas) &&
           (context.output.spec.replicas == ready.replicas) &&
           (ready.observedGeneration == context.output.metadata.generation ||
            ready.observedGeneration > context.output.metadata.generation)
isHealth: *_isHealth | bool
if context.output.metadata.annotations != _|_ {
    if context.output.metadata.annotations["app.oam.dev/disable-health-check"] != _|_ {
        isHealth: true
    }
}

Custom Status

Composable Status Expressions

defkit provides a fully composable expression-based API for defining custom status messages. This enables dynamic status messages for any resource type, not just Kubernetes workloads.

Core Primitives:

All status expression methods are accessed via the Status() builder, providing a unified API:

Primitive Purpose Example
Status().Condition(type) Check status.conditions[] array Status().Condition("Ready").StatusValue()
Status().Field(path) Extract any status field value Status().Field("status.replicas")
Status().SpecField(path) Extract spec field value Status().SpecField("spec.replicas")
Status().Exists(path) Check field existence Status().Exists("status.endpoint")
Status().Format(template, ...) Build formatted message Status().Format("Ready: %v/%v", readyExpr, desiredExpr)
Status().Concat(...) Concatenate string parts Status().Concat(part1, " - ", part2)
Status().Switch(...) Conditional message selection Status().Switch(case1, case2, default)
Status().HealthAware(...) Message based on health status Status().HealthAware(healthyMsg, unhealthyMsg)
Status().WithDetails(...) Add structured details Status().WithDetails(detail1, detail2)

Field Expressions:

s := defkit.Status()

// Extract status fields with defaults
readyReplicas := s.Field("status.readyReplicas").Default(0)
phase := s.Field("status.phase").Default("Unknown")
endpoint := s.Field("status.endpoint").Default("pending")

// Extract spec fields
desiredReplicas := s.SpecField("spec.replicas")

// Build formatted message
CustomStatusExpr(s.Format("Ready: %v/%v", readyReplicas, desiredReplicas))

Generated CUE for field extraction:

_readyReplicas: *0 | int
if context.output.status.readyReplicas != _|_ {
    _readyReplicas: context.output.status.readyReplicas
}
_desiredReplicas: context.output.spec.replicas
message: "Ready: \(_readyReplicas)/\(_desiredReplicas)"

Condition Expressions:

s := defkit.Status()

// Extract condition message
CustomStatusExpr(s.Condition("Ready").Message())

// Extract condition reason
CustomStatusExpr(s.Condition("Ready").Reason())

// Extract condition status
CustomStatusExpr(s.Condition("Ready").StatusValue())

// Combine condition info
CustomStatusExpr(s.Format("%v: %v",
    s.Condition("Ready").StatusValue(),
    s.Condition("Ready").Message(),
))

Generated CUE for condition extraction:

_readyCond: [ for c in context.output.status.conditions if c.type == "Ready" { c } ]
_readyStatus: *"Unknown" | string
_readyMessage: *"" | string
if len(_readyCond) > 0 {
    _readyStatus: _readyCond[0].status
    _readyMessage: _readyCond[0].message
}
message: "\(_readyStatus): \(_readyMessage)"

Conditional Status Messages:

s := defkit.Status()

// Switch based on field value
CustomStatusExpr(s.Switch(
    s.Case(s.Field("status.phase").Eq("Running"), "Service is running"),
    s.Case(s.Field("status.phase").Eq("Pending"), "Service is starting..."),
    s.Case(s.Field("status.phase").Eq("Failed"), s.Concat("Failed: ", s.Field("status.reason"))),
    s.Default("Unknown status"),
))

// Health-aware status (references context.status.healthy set by healthPolicy)
CustomStatusExpr(s.HealthAware(
    "All systems operational",  // when healthy
    s.Concat("Degraded: ", s.Condition("Ready").Message()),  // when unhealthy
))

Generated CUE for Switch:

message: *"Unknown status" | string
if context.output.status.phase == "Running" {
    message: "Service is running"
}
if context.output.status.phase == "Pending" {
    message: "Service is starting..."
}
if context.output.status.phase == "Failed" {
    _failReason: *"" | string
    if context.output.status.reason != _|_ {
        _failReason: context.output.status.reason
    }
    message: "Failed: \(_failReason)"
}

Composite Status Messages:

s := defkit.Status()

// Build rich status message with multiple components
CustomStatusExpr(s.Concat(
    "Replicas: ", s.Field("status.readyReplicas").Default(0),
    "/", s.SpecField("spec.replicas"),
    " | Phase: ", s.Field("status.phase").Default("Unknown"),
    " | Generation: ", s.Field("status.observedGeneration").Default(0),
))

// Real-world: Custom CRD status
CustomStatusExpr(s.Concat(
    "State: ", s.Field("status.state").Default("unknown"),
    " | Endpoint: ", s.Field("status.endpoint").Default("pending"),
    " | Connections: ", s.Field("status.activeConnections").Default(0),
))

Generated CUE for Concat:

_readyReplicas: *0 | int
if context.output.status.readyReplicas != _|_ {
    _readyReplicas: context.output.status.readyReplicas
}
_phase: *"Unknown" | string
if context.output.status.phase != _|_ {
    _phase: context.output.status.phase
}
_observedGen: *0 | int
if context.output.status.observedGeneration != _|_ {
    _observedGen: context.output.status.observedGeneration
}
message: "Replicas: \(_readyReplicas)/\(context.output.spec.replicas) | Phase: \(_phase) | Generation: \(_observedGen)"

Status Details (Structured Data):

s := defkit.Status()

// Add structured details alongside message
CustomStatusExpr(s.
    Message(s.Format("Ready: %v/%v",
        s.Field("status.readyReplicas").Default(0),
        s.SpecField("spec.replicas"),
    )).
    WithDetails(
        s.Detail("endpoint", s.Field("status.endpoint")),
        s.Detail("version", s.Field("status.version")),
        s.Detail("lastSync", s.Field("status.lastSyncTime")),
    ))

Field Expression Methods:

Method CUE Output Description
.Field(path) Extract from context.output.path Status field value
.SpecField(path) Extract from context.output.path Spec field value
.Default(value) *value | type Default if field missing
.Exists(path) path != _|_ Check field exists
.Condition(type) Array filter Access condition by type
.StatusValue() Condition status Get condition status
.Message() Condition message Get condition message
.Reason() Condition reason Get condition reason

Message Building Methods:

Method Description Example
Format(template, args...) Printf-style formatting Format("Ready: %v/%v", ready, total)
Concat(parts...) String concatenation Concat("State: ", state, " - ", msg)
Switch(cases...) Conditional selection Switch(case1, case2, default)
Case(cond, msg) Switch case Case(phase.Eq("Running"), "OK")
Default(msg) Switch default Default("Unknown")
HealthAware(ok, fail) Health-based HealthAware("OK", errMsg)

Built-in Workload Helpers

For common Kubernetes workloads, defkit provides pre-configured helpers:

// For Deployments - use the built-in helper
CustomStatus(defkit.DeploymentStatus().Build())

Generated CUE:

ready: {
    readyReplicas: *0 | int
} & {
    if context.output.status.readyReplicas != _|_ {
        readyReplicas: context.output.status.readyReplicas
    }
}
message: "Ready:\(ready.readyReplicas)/\(context.output.spec.replicas)"

Creating Custom Status Helpers

Users can create their own helpers by composing the primitives:

// Example: Crossplane-style status
func CrossplaneStatus() defkit.StatusExpression {
    s := defkit.Status()
    return s.Switch(
        s.Case(s.Condition("Ready").Is("True"),
            s.Concat("Ready: ", s.Condition("Ready").Message())),
        s.Case(s.Condition("Synced").Is("False"),
            s.Concat("Syncing: ", s.Condition("Synced").Message())),
        s.Default(s.Concat(
            "Ready: ", s.Condition("Ready").StatusValue(),
            " | Synced: ", s.Condition("Synced").StatusValue(),
        )),
    )
}

// Example: Database CRD status
func DatabaseStatus() defkit.StatusExpression {
    s := defkit.Status()
    return s.Concat(
        "State: ", s.Field("status.state").Default("initializing"),
        " | Connections: ", s.Field("status.connections").Default(0),
        " | Endpoint: ", s.Field("status.endpoint").Default("pending"),
    )
}

// Example: Certificate status
func CertificateStatus() defkit.StatusExpression {
    s := defkit.Status()
    return s.HealthAware(
        s.Concat("Valid until ", s.Field("status.notAfter")),
        s.Concat("Not ready: ", s.Condition("Ready").Message()),
    )
}

// Usage - pick ONE status per component:
CustomStatusExpr(CrossplaneStatus())
CustomStatusExpr(DatabaseStatus())

Workload Helper Reference

defkit provides pre-configured helpers for common Kubernetes workloads:

Workload Status Helper Health Helper
Deployment DeploymentStatus() DeploymentHealth()
DaemonSet DaemonSetStatus() DaemonSetHealth()
StatefulSet StatefulSetStatus() StatefulSetHealth()
Job - JobHealth()
CronJob - CronJobHealth()

Example for DaemonSet:

// DaemonSet health policy
HealthPolicy(defkit.DaemonSetHealth().Build())

Generated CUE:

ready: {
    replicas: *0 | int
} & {
    if context.output.status.numberReady != _|_ {
        replicas: context.output.status.numberReady
    }
}
desired: {
    replicas: *0 | int
} & {
    if context.output.status.desiredNumberScheduled != _|_ {
        replicas: context.output.status.desiredNumberScheduled
    }
}
// ... additional fields for current, updated, generation
isHealth: desired.replicas == ready.replicas &&
          desired.replicas == updated.replicas &&
          desired.replicas == current.replicas &&
          (generation.observed == generation.metadata || generation.observed > generation.metadata)

Creating Custom Helpers

Users can create their own helpers by composing the primitives:

// Example: Crossplane-style health check
func CrossplaneHealth() defkit.HealthExpression {
    h := defkit.Health()
    return h.AllTrue("Ready", "Synced")
}

// Example: Certificate health check
func CertificateHealth() defkit.HealthExpression {
    h := defkit.Health()
    return h.Condition("Ready").IsTrue()
}

// Example: Custom database CRD
func DatabaseHealth() defkit.HealthExpression {
    h := defkit.Health()
    return h.And(
        h.Condition("Ready").IsTrue(),
        h.Field("status.phase").Eq("Running"),
        h.Exists("status.connectionEndpoint"),
    )
}

// Usage - pick ONE health policy per component:
// For a Crossplane managed resource:
HealthPolicyExpr(CrossplaneHealth())

// For a database CRD:
HealthPolicyExpr(DatabaseHealth())

Cluster Version Check

// Go - inside template function using conditional apiVersion
Template(func(tpl *defkit.Template) {
    vela := defkit.VelaCtx()

    // Use VersionIf for cluster-version-aware resources
    cronjob := defkit.NewResourceWithConditionalVersion("batch/v1", "CronJob").
        VersionIf(vela.ClusterVersion().Minor().Lt(defkit.Lit(25)), "batch/v1beta1").
        Set("metadata.name", vela.Name()).
        Set("spec.schedule", schedule)

    tpl.Output(cronjob)
})

// Generated CUE
// if context.clusterVersion.minor < 25 {
//     apiVersion: "batch/v1beta1"
// }
// if context.clusterVersion.minor >= 25 {
//     apiVersion: "batch/v1"
// }

Conditional Fields

// Go - use SetIf for conditional field setting
Template(func(tpl *defkit.Template) {
    vela := defkit.VelaCtx()
    cpu := defkit.String("cpu")  // optional parameter

    deploy := defkit.NewResource("apps/v1", "Deployment").
        Set("metadata.name", vela.Name()).
        Set("spec.replicas", replicas).
        SetIf(cpu.IsSet(), "spec.template.spec.containers[0].resources.limits.cpu", cpu)

    tpl.Output(deploy)
})

// Generated CUE
// if parameter.cpu != _|_ {
//     spec: template: spec: containers: [{resources: limits: cpu: parameter.cpu}]
// }

Conditional Patterns

Checking if an optional parameter is set

Use param.IsSet() to conditionally set fields when an optional parameter has a value:

// Single condition - set field only if parameter was provided
deploy.SetIf(cpu.IsSet(), "spec.resources.limits.cpu", cpu)
deploy.SetIf(labels.IsSet(), "spec.template.metadata.labels", labels)

Checking if a boolean parameter's value is true

For boolean parameters, use Eq(param, Lit(true)) to check if the value is true:

// Check if boolean parameter is true
addRevisionLabel := defkit.Bool("addRevisionLabel").Default(false)

deploy.SetIf(
    defkit.Eq(addRevisionLabel, defkit.Lit(true)),
    "spec.template.metadata.labels[app.oam.dev/revision]",
    ctx.Revision(),
)

Why this pattern? Using param.IsSet() on a boolean only checks if it was provided, not its value. A boolean set to false would pass IsSet(). Use Eq(param, Lit(true)) when you need to check the actual value.

Compound Conditions

Use defkit.And() and defkit.Or() to compose conditions:

// Single condition
deploy.SetIf(cpu.IsSet(), "spec.resources.limits.cpu", cpu)

// AND - both must be true (vela := defkit.VelaCtx())
deploy.SetIf(
    defkit.And(
        vela.ClusterVersion().Minor().Gte(25),
        enableNewAPI.IsSet(),
    ),
    "apiVersion", "batch/v1",
)

// OR - either condition
deploy.SetIf(
    defkit.Or(
        vela.ClusterVersion().Minor().Lt(21),
        legacyMode.IsSet(),
    ),
    "apiVersion", "batch/v1beta1",
)

// Complex: (A && B) || C
deploy.SetIf(
    defkit.Or(
        defkit.And(isProduction, highAvailability),
        forceHA.IsSet(),
    ),
    "spec.replicas", 3,
)

Generated CUE:

if parameter.cpu != _|_ {
    spec: resources: limits: cpu: parameter.cpu
}
if context.clusterVersion.minor >= 25 && parameter.enableNewAPI != _|_ {
    apiVersion: "batch/v1"
}
if context.clusterVersion.minor < 21 || parameter.legacyMode != _|_ {
    apiVersion: "batch/v1beta1"
}
if (parameter.isProduction && parameter.highAvailability) || parameter.forceHA != _|_ {
    spec: replicas: 3
}

Multiple fields under the same condition:

// Use If().EndIf() block for multiple fields
deploy.
    If(defkit.And(persistence.IsSet(), persistence.Field("enabled"))).
        Set("spec.volumeClaimTemplates", []map[string]any{{...}}).
        Set("spec.template.spec.volumes", []map[string]any{{...}}).
    EndIf()

RawCUE() Escape Hatch

For the ~5% of patterns that cannot be expressed in Go, use RawCUE():

True Unification Semantics

// CUE's & operator has mathematical semantics Go cannot replicate
defkit.RawCUE(`
    baseConfig: {
        replicas: >=1
        image: string
    }
    prodConfig: baseConfig & {
        replicas: >=3 & <=10
    }
`)

Complex Nested Comprehensions

// Triple-nested comprehension with complex conditions
defkit.RawCUE(`
    result: [
        for ns in namespaces
        for svc in ns.services if svc.exposed
        for port in svc.ports if port.protocol == "TCP" {
            name: "\(ns.name)-\(svc.name)-\(port.port)"
        }
    ]
`)

Parameter Types

defkit Type CUE Equivalent Go Type
defkit.String("name") name: string string
defkit.Int("name") name: int int
defkit.Bool("name") name: bool bool
defkit.Float("name") name: float float64
defkit.List("name", T) name: [...T] []T
defkit.Map("name").Of(V) name: {[string]: V} map[string]V
defkit.Object("name", ...) name: {field: type} struct
defkit.Enum("name", "a", "b") name: "a" | "b" string with validation
defkit.OneOf("name", ...) name: close({...}) | close({...}) discriminated union
defkit.Struct("name") name: {...} struct with named fields
defkit.StringList("name") name: [...string] convenience alias
defkit.IntList("name") name: [...int] convenience alias
defkit.StringKeyMap("name") name: {[string]: string} string→string map

Parameter Modifiers

defkit.String("image").
    Required().                    // Must be provided
    Default("nginx:latest").       // CUE: *"nginx:latest" | string
    Optional().                    // Can be omitted
    Description("Container image")  // Schema description

defkit.Int("replicas").
    Min(1).Max(100).              // Numeric constraints
    Default(3)

defkit.String("name").
    Pattern("^[a-z][a-z0-9-]*$")  // Regex validation

Complex Parameter Types

Object parameters for nested configuration:

persistence := defkit.Object("persistence").WithFields(
    defkit.Bool("enabled").Default(false),
    defkit.String("storageClass").Required(),
    defkit.String("size").Default("10Gi"),
).Optional()

// Usage in template - fluent conditional for optional object
deploy.
    If(defkit.And(persistence.IsSet(), persistence.Field("enabled"))).
    Set("spec.volumeClaimTemplates", []map[string]any{{
        "spec": map[string]any{
            "storageClassName": persistence.Field("storageClass"),
            "resources": map[string]any{
                "requests": map[string]any{
                    "storage": persistence.Field("size"),
                },
            },
        },
    }})

Discriminated unions for variant types:

volume := defkit.OneOf("volume",
    defkit.Variant("emptyDir",
        defkit.String("medium").Default(""),
    ),
    defkit.Variant("pvc",
        defkit.String("claimName").Required(),
    ),
    defkit.Variant("configMap",
        defkit.String("name").Required(),
        defkit.Int("defaultMode").Default(420),
    ),
)

// Usage: volume.Discriminator() returns "emptyDir", "pvc", or "configMap"

Collection Operations

defkit provides comprehensive collection operations for transforming arrays and lists. These are commonly used for patterns like ports, volumeMounts, and environment variables.

Basic Collection Pipeline

// Each() creates a collection pipeline from a list parameter
ports := defkit.Array("ports")

// Filter, transform, and map fields
exposedPorts := defkit.Each(ports).
    Filter(defkit.FieldEquals("expose", true)).
    Map(defkit.FieldMap{
        "containerPort": defkit.FieldRef("port"),
        "name":          defkit.FieldRef("name"),
        "protocol":      defkit.FieldRef("protocol"),
    }).
    DefaultField("name", defkit.Format("port-%v", defkit.FieldRef("port")))

Collection Operations Reference

Operation Description Example
Each(source) Start a collection pipeline defkit.Each(ports)
.Filter(pred) Keep items matching predicate .Filter(FieldEquals("expose", true))
.Map(fieldMap) Transform item fields .Map(FieldMap{"newKey": FieldRef("oldKey")})
.Pick(fields...) Select only specified fields .Pick("name", "mountPath")
.Rename(from, to) Rename a field .Rename("port", "containerPort")
.Wrap(key) Wrap item under a key .Wrap("name"){name: value}
.DefaultField(f, v) Set default for missing field .DefaultField("name", Lit("default"))
.Flatten() Flatten nested arrays .Flatten()

Multi-Source Collections

For patterns like volumeMounts where items come from multiple sub-fields:

volumeMounts := defkit.Object("volumeMounts")

// Combine items from multiple sources (pvc, configMap, secret, etc.)
mounts := defkit.FromFields(volumeMounts, "pvc", "configMap", "secret", "emptyDir", "hostPath").
    Pick("name", "mountPath").
    Dedupe("name")
Operation Description Example
FromFields(src, fields...) Combine items from multiple object fields FromFields(volumeMounts, "pvc", "configMap")
.MapBySource(map) Apply different mappings per source type .MapBySource(map[string]FieldMap{...})
.Dedupe(keyField) Remove duplicates by key .Dedupe("name")

Field Value Helpers

Helper Description Example
FieldRef("field") Reference item field FieldRef("port")
FieldRef("f").Or(fallback) Field with fallback FieldRef("name").Or(LitField("default"))
LitField(value) Literal value LitField("TCP")
Format(fmt, args...) Formatted string Format("port-%v", FieldRef("port"))
Nested(fieldMap) Nested object Nested(FieldMap{"claimName": FieldRef("name")})
Optional("field") Include only if field exists Optional("subPath")
FieldEquals(f, v) Predicate: field equals value FieldEquals("expose", true)
FieldExists("field") Predicate: field is set FieldExists("mountPath")

Helper Builder Pattern

For complex collection transformations (like volumeMounts and volumes), defkit provides a helper builder pattern that creates named template-level helpers.

Basic Helper

Template(func(tpl *defkit.Template) {
    ports := defkit.Array("ports")

    // Create a named helper for port mapping
    portsArray := tpl.Helper("portsArray").
        From(ports).
        Pick("port", "name", "protocol").
        Rename("port", "containerPort").
        DefaultField("name", defkit.Format("port-%v", defkit.FieldRef("port"))).
        Build()

    deploy := defkit.NewResource("apps/v1", "Deployment").
        Set("spec.template.spec.containers[0].ports", portsArray)

    tpl.Output(deploy)
})

Helper Builder Methods

Method Description
tpl.Helper(name) Start building a named helper
.From(source) Set single source
.FromFields(src, fields...) Set multiple sources
.FromHelper(helper) Reference another helper
.Guard(cond) Outer condition for comprehension
.Each(fn) Transform each item with function
.Pick(fields...) Select fields
.PickIf(cond, field) Conditionally include field
.Map(fieldMap) Transform fields
.MapBySource(map) Per-source transformations
.Filter(cond) Filter by condition
.FilterPred(pred) Filter by predicate
.Wrap(key) Wrap items under key
.Dedupe(keyField) Remove duplicates
.DefaultField(f, v) Set default value
.Rename(from, to) Rename field
.AfterOutput() Place helper after output block
.Build() Finalize and register helper

Advanced Helpers

For complex patterns like volumeMounts (struct-based arrays):

// Struct array helper for mounting volumes
mountsArray := tpl.StructArrayHelper("mountsArray", volumeMounts).
    Field("pvc", defkit.FieldMap{"name": defkit.FieldRef("name"), "mountPath": defkit.FieldRef("mountPath")}).
    Field("configMap", defkit.FieldMap{"name": defkit.FieldRef("name"), "mountPath": defkit.FieldRef("mountPath")}).
    Field("secret", defkit.FieldMap{"name": defkit.FieldRef("name"), "mountPath": defkit.FieldRef("mountPath")}).
    Build()

// Volumes array with per-source mappings
volumesArray := tpl.StructArrayHelper("volumesArray", volumeMounts).
    Field("pvc", defkit.FieldMap{
        "name": defkit.FieldRef("name"),
        "persistentVolumeClaim": defkit.Nested(defkit.FieldMap{"claimName": defkit.FieldRef("claimName")}),
    }).
    Field("configMap", defkit.FieldMap{
        "name": defkit.FieldRef("name"),
        "configMap": defkit.Nested(defkit.FieldMap{"name": defkit.FieldRef("cmName")}),
    }).
    Build()

// Concat helper combines struct arrays
volumesList := tpl.ConcatHelper("volumesList", volumesArray).
    Fields("pvc", "configMap", "secret", "emptyDir", "hostPath").
    Build()

// Dedupe helper removes duplicates
deDupVolumes := tpl.DedupeHelper("deDupVolumesArray", volumesList).
    ByKey("name").
    Build()

Helper Types

Type Purpose Example
*HelperVar Reference to basic helper Returned by Helper(...).Build()
*StructArrayHelper Struct with array fields tpl.StructArrayHelper(name, src)
*ConcatHelper Concatenate arrays tpl.ConcatHelper(name, src)
*DedupeHelper Deduplicate by key tpl.DedupeHelper(name, src)

Definition Types

ComponentDefinition

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

func MyComponent() *defkit.ComponentDefinition {
    image := defkit.String("image").Required()

    return defkit.NewComponent("webservice").
        Description("...").
        Workload("apps/v1", "Deployment").
        Params(image).
        Template(func(tpl *defkit.Template) {
            vela := defkit.VelaCtx()
            deploy := defkit.NewResource("apps/v1", "Deployment").
                Set("metadata.name", vela.Name()).
                Set("spec.template.spec.containers[0].image", image)
            tpl.Output(deploy)
        }).
        HealthPolicy(defkit.DeploymentHealth().Build()).
        CustomStatus(defkit.DeploymentStatus().Build())
}

TraitDefinition

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

func RateLimitTrait() *defkit.TraitDefinition {
    rps := defkit.Int("rps").Required()

    return defkit.NewTrait("rate-limit").
        Description("...").
        AppliesTo("webservice", "microservice").
        Params(rps).
        PatchTemplate(func(tpl *defkit.Template) {
            // Trait templates patch the workload
            tpl.PatchOutput(defkit.NewResource("", "").
                Set("metadata.annotations[ratelimit.example.com/rps]", rps))
        })
}

PolicyDefinition

// Example: Topology policy for multi-cluster deployment
func Topology() *defkit.PolicyDefinition {
    clusters := defkit.StringList("clusters").Description("Specify the names of the clusters to select.")
    clusterLabelSelector := defkit.StringKeyMap("clusterLabelSelector").Description("Specify the label selector for clusters")
    allowEmpty := defkit.Bool("allowEmpty").Description("Ignore empty cluster error")
    namespace := defkit.String("namespace").Description("Specify the target namespace to deploy in the selected clusters")

    return defkit.NewPolicy("topology").
        Description("Describe the destination where components should be deployed to.").
        Params(clusters, clusterLabelSelector, allowEmpty, namespace)
}

Example policies: topology, apply-once, garbage-collect, override, read-only, replication, resource-update, shared-resource, take-over

WorkflowStepDefinition

// Example: Deploy step with multi-cluster support
func Deploy() *defkit.WorkflowStepDefinition {
    return defkit.NewWorkflowStep("deploy").
        Description("A powerful and unified deploy step for components multi-cluster delivery with policies.").
        Category("Application Delivery").
        Scope("Application").
        WithImports("vela/multicluster", "vela/builtin").
        RawCUE(`...`) // Complex CUE patterns use RawCUE() escape hatch
}

Example workflow steps: deploy, suspend, apply-component, apply-deployment, apply-object, apply-terraform-config, apply-terraform-provider, build-push-image, check-metrics, clean-jobs, collect-service-endpoints, create-config, delete-config, depends-on-app, deploy-cloud-resource, export-data, export-service, export2config, export2secret, generate-jdbc-connection, list-config, notification, print-message-in-status, read-config, read-object, request, share-cloud-resource, step-group, webhook


Schema-Agnostic Resource Construction

defkit does NOT ship typed Kubernetes helpers. Instead, it provides a universal builder that works with any resource:

// Core Kubernetes
defkit.NewResource("apps/v1", "Deployment").
    SetName("my-app").
    Set("spec.replicas", 3)

// CRDs (Crossplane, KRO, etc.)
defkit.NewResource("database.aws.crossplane.io/v1beta1", "DBInstance").
    SetName("my-db").
    Set("spec.forProvider.engine", "postgres")

Optional typed adapter for users who want compile-time type safety:

import appsv1 "k8s.io/api/apps/v1"

deployment := &appsv1.Deployment{...}
return defkit.FromTyped(deployment)

CLI Commands

The following commands extend the existing vela def command group to support Go definitions:

# Apply Go definitions (extends existing `vela def apply`)
# CUE compilation is transparent - .go files are automatically detected
vela def apply ./definitions/webservice.go

# Apply all definitions in directory (supports mixed .cue and .go)
vela def apply ./definitions/

# Dry-run to see generated resources (existing flag)
vela def apply ./definitions/ --dry-run

# Validate Go definitions without applying (existing command, extended for .go)
vela def vet ./definitions/webservice.go

# Initialize a new Go definition module with scaffolding
vela def init-module ./my-definitions --name my-definitions

# Validate all definitions in a Go module without cluster connection
vela def validate-module ./my-definitions
vela def validate-module ./my-definitions --verbose

New Subcommands

Command Description
vela def init-module Scaffold a complete Go definition module with example components
vela def validate-module Validate all Go definitions in a module without requiring a cluster
vela def gen-go Generate Go defkit code from existing CUE definitions (Phase 3 - migration)

Extended Existing Commands

Command Extension
vela def apply Accepts .go files, compiles to CUE transparently
vela def vet Validates .go definitions including Go compilation
vela def gen-api Already generates Go SDK from CUE (inverse direction)

Module Scaffolding

The init-module command creates a complete Go module structure:

$ vela def init-module ./my-platform --name my-platform

Created Go definition module at ./my-platform:
  go.mod                    - Go module definition
  components/
    webservice.go           - Example webservice component
    webservice_test.go      - Example tests
  main.go                   - Module entry point for validation

The scaffolded module can be validated immediately:

$ cd my-platform
$ vela def validate-module .
Validating Go definition module...
Found 1 definitions
✓ webservice (ComponentDefinition) - CUE validation passed
All definitions validated successfully

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                    Definition Authoring Pipeline                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐       │
│  │   defkit     │    │      IR      │    │   Compiler   │       │
│  │   Go API     │───▶│   (JSON)     │───▶│   Go → CUE   │──▶ CR │
│  │              │    │              │    │              │       │
│  │ tpl.Output() │    │ • Schema     │    │ • Validation │       │
│  │ param vars   │    │ • Template   │    │ • CUE Gen    │       │
│  └──────────────┘    └──────────────┘    └──────────────┘       │
│                                                                  │
│  (CUE compilation is transparent - developers never see it)      │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Compilation Approach

The SDK uses declarative capture rather than Go AST transformation:

  • Template functions execute with a tracing context
  • Each Set(), If(), tpl.Output() call is recorded
  • Parameter variables track their usage and generate corresponding CUE paths
  • Recorded operations form a declarative tree that maps directly to CUE

How Parameter Variables Work

Parameter variables like image := defkit.String("image").Required() are expression builders, not value holders:

// What you write:
image := defkit.String("image").Required()
deploy.Set("spec.image", image)

// What happens internally:
// 1. defkit.String("image") creates a Param{name: "image", type: "string"}
// 2. deploy.Set() receives this Param and records: SetOp{path: "spec.image", value: ParamRef("image")}
// 3. During compilation, ParamRef("image") becomes `parameter.image` in CUE

Conditional handling uses the fluent API rather than Go's if:

// For optional parameters, use SetIf or the If() fluent method:
deploy.SetIf(cpu.IsSet(), "spec.resources.limits.cpu", cpu)

// Or with fluent chaining:
deploy.If(cpu.IsSet()).Set("spec.resources.limits.cpu", cpu)

// Both compile to CUE:
// if parameter.cpu != _|_ {
//     spec: resources: limits: cpu: parameter.cpu
// }

This approach is similar to how query builders (like GORM, Squirrel) work - the Go code describes operations declaratively without executing them at definition time.


Testing

defkit provides testing support using Ginkgo and Gomega with custom matchers, enabling BDD-style test-driven development without requiring a Kubernetes cluster.

Test Levels

Test Level Framework Cluster Required What It Tests
Unit tests Ginkgo/Gomega No Parameter validation, template output, conditional logic
CUE compilation Ginkgo/Gomega No Generated CUE is syntactically valid
Integration envtest No Controller reconciliation with fake cluster
E2E Full cluster Yes Complete deployment lifecycle

Custom Matchers

defkit provides custom Gomega matchers for readable, expressive tests:

// Resource type matchers
BeDeployment(), BeService(), BeIngress(), BeConfigMap(), BeSecret()
BeResourceOfKind(kind string)

// Metadata matchers
HaveAPIVersion(version string), HaveName(name string), HaveNamespace(ns string)
HaveLabel(key, value string), HaveAnnotation(key, value string)

// Spec matchers
HaveReplicas(count int), HaveImage(image string), HaveContainerNamed(name string)
HavePort(port int), HaveEnvVar(name, value string)
HaveResourceLimit(resource, value string), HaveResourceRequest(resource, value string)

// Path-based matcher for any field
HaveFieldPath(path string, value any)

// Validation and health matchers
FailValidationWith(substring string), PassValidation()
BeHealthy(), BeUnhealthy(), HaveHealthMessage(msg string)

Example: Testing a ComponentDefinition

package webservice_test

import (
    . "github.com/onsi/ginkgo/v2"
    . "github.com/onsi/gomega"

    "github.com/oam-dev/kubevela/pkg/definition/defkit"
    . "github.com/oam-dev/kubevela/pkg/definition/defkit/testing/matchers"
)

var _ = Describe("Webservice ComponentDefinition", func() {
    var def *defkit.ComponentDefinition

    BeforeEach(func() {
        def = webservice.Webservice()
    })

    Describe("Template Rendering", func() {
        It("should render a deployment with defaults", func() {
            ctx := defkit.TestContext().
                WithName("my-app").
                WithNamespace("production").
                WithParam("image", "nginx:1.21")

            output := def.Render(ctx)

            Expect(output).To(BeDeployment())
            Expect(output).To(HaveAPIVersion("apps/v1"))
            Expect(output).To(HaveName("my-app"))
            Expect(output).To(HaveReplicas(3)) // default value
            Expect(output).To(HaveImage("nginx:1.21"))
        })

        It("should set resource limits when cpu is provided", func() {
            ctx := defkit.TestContext().
                WithName("my-app").
                WithParam("image", "nginx:1.21").
                WithParam("cpu", "500m")

            Expect(def.Render(ctx)).To(HaveResourceLimit("cpu", "500m"))
        })
    })

    Describe("Parameter Validation", func() {
        It("should fail when required image is missing", func() {
            ctx := defkit.TestContext().WithName("my-app")
            Expect(def.Validate(ctx)).To(FailValidationWith("image is required"))
        })

        It("should fail when replicas exceeds maximum", func() {
            ctx := defkit.TestContext().
                WithName("my-app").
                WithParam("image", "nginx:1.21").
                WithParam("replicas", 200)

            Expect(def.Validate(ctx)).To(FailValidationWith("replicas must be <= 100"))
        })
    })

    Describe("Health Policy", func() {
        It("should report healthy when all replicas are ready", func() {
            ctx := defkit.TestContext().
                WithName("my-app").
                WithParam("image", "nginx:1.21").
                WithParam("replicas", 3).
                WithOutputStatus(map[string]any{"readyReplicas": 3})

            Expect(def.EvaluateHealth(ctx)).To(BeHealthy())
            Expect(def.EvaluateHealth(ctx)).To(HaveHealthMessage("Ready: 3/3"))
        })
    })
})

Table-Driven Tests

DescribeTable("parameter combinations",
    func(params map[string]any, expectedField string, expectedValue any, shouldFail bool) {
        ctx := defkit.TestContext().WithName("test")
        for k, v := range params {
            ctx = ctx.WithParam(k, v)
        }

        if shouldFail {
            Expect(def.Validate(ctx)).NotTo(PassValidation())
            return
        }
        Expect(def.Render(ctx)).To(HaveFieldPath(expectedField, expectedValue))
    },
    Entry("minimal config uses default replicas",
        map[string]any{"image": "nginx:1.21"}, "spec.replicas", 3, false),
    Entry("custom replicas are respected",
        map[string]any{"image": "nginx:1.21", "replicas": 5}, "spec.replicas", 5, false),
    Entry("missing image fails validation",
        map[string]any{"replicas": 3}, "", nil, true),
)

Testing Traits

var _ = Describe("RateLimit Trait", func() {
    It("should patch workload with annotation", func() {
        workload := defkit.NewResource("apps/v1", "Deployment").SetName("my-app")

        ctx := defkit.TestContext().
            WithWorkload(workload).
            WithParam("rps", 1000)

        patches := ratelimit.New().Patch(ctx)
        Expect(patches).To(HaveAnnotation("ratelimit.example.com/rps", "1000"))
    })
})

Testing Cluster Version Conditionals

var _ = Describe("CronJob", func() {
    It("should use v1beta1 on old clusters", func() {
        ctx := defkit.TestContext().
            WithName("my-job").
            WithParam("schedule", "0 * * * *").
            WithClusterVersion(1, 24)

        Expect(cronjob.New().Render(ctx)).To(HaveAPIVersion("batch/v1beta1"))
    })

    It("should use v1 on new clusters", func() {
        ctx := defkit.TestContext().
            WithName("my-job").
            WithParam("schedule", "0 * * * *").
            WithClusterVersion(1, 25)

        Expect(cronjob.New().Render(ctx)).To(HaveAPIVersion("batch/v1"))
    })
})

What to Test vs What NOT to Test

DO Test

What Why Example
Parameter validation Catch invalid inputs early Required fields, min/max constraints
Template output structure Ensure correct K8s resources Resource kind, API version, spec fields
Conditional logic Verify branches work correctly If cpu is set, limits are added
Default values Ensure defaults are applied replicas defaults to 3
Health policy evaluation Verify health checks work Ready when replicas match
Auxiliary outputs Verify all resources generated Service, Ingress alongside Deployment
Edge cases Handle unusual inputs Empty arrays, nil values

DO NOT Test

What Why
CUE language behavior CUE is well-tested; trust it
Controller reconciliation logic Out of defkit's scope
Kubernetes API behavior Not your code
Network operations Use mocks/fakes for integration tests

Testing Anti-Patterns to Avoid

// ❌ BAD: Testing CUE internals
func TestBad(t *testing.T) {
    cue, _ := def.ToCUE()
    assert.Contains(t, cue, "replicas: parameter.replicas") // Too brittle
}

// ✅ GOOD: Test the behavior, not the implementation
func TestGood(t *testing.T) {
    ctx := defkit.TestContext().WithParam("replicas", 5)
    output := def.Render(ctx)
    assert.Equal(t, 5, output.Get("spec.replicas"))
}

// ❌ BAD: Hardcoding exact output
func TestBad2(t *testing.T) {
    output := def.Render(ctx)
    expected := `{"apiVersion":"apps/v1",...}` // Brittle, hard to maintain
    assert.Equal(t, expected, output.ToJSON())
}

// ✅ GOOD: Assert on specific fields you care about
func TestGood2(t *testing.T) {
    output := def.Render(ctx)
    assert.Equal(t, "apps/v1", output.APIVersion())
    assert.Equal(t, 3, output.Get("spec.replicas"))
}

Integration Testing with envtest

For testing controller integration without a full cluster:

package integration_test

import (
    "context"
    "testing"
    "time"

    "github.com/stretchr/testify/require"
    "sigs.k8s.io/controller-runtime/pkg/envtest"

    "myplatform/definitions/webservice"
)

func TestWebserviceIntegration(t *testing.T) {
    // Start envtest
    testEnv := &envtest.Environment{}
    cfg, err := testEnv.Start()
    require.NoError(t, err)
    defer testEnv.Stop()

    // Create client
    k8sClient, err := client.New(cfg, client.Options{})
    require.NoError(t, err)

    // Apply definition
    def := webservice.New()
    cr, err := def.BuildCR()
    require.NoError(t, err)

    err = k8sClient.Create(context.Background(), cr)
    require.NoError(t, err)

    // Verify definition was created
    var retrieved v1beta1.ComponentDefinition
    err = k8sClient.Get(context.Background(),
        types.NamespacedName{Name: "webservice", Namespace: "vela-system"},
        &retrieved)
    require.NoError(t, err)
    assert.Equal(t, "webservice", retrieved.Name)
}

Definition Deployment Workflow

Development Cycle

┌─────────────────────────────────────────────────────────────────┐
│                    Definition Development Cycle                   │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. WRITE          2. TEST           3. VALIDATE    4. APPLY    │
│  ┌──────────┐     ┌──────────┐      ┌──────────┐  ┌──────────┐  │
│  │  Go Code │────▶│go test   │─────▶│vela def  │─▶│vela def  │  │
│  │          │     │          │      │vet       │  │apply     │  │
│  └──────────┘     └──────────┘      └──────────┘  └──────────┘  │
│       │                │                  │             │        │
│       │                │                  │             │        │
│       └────────────────┴──────────────────┴─────────────┘        │
│                         ITERATE                                  │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

CI/CD Integration

# .github/workflows/definitions.yaml
name: Definition CI

on:
  push:
    paths:
      - 'definitions/**'

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.21'

      - name: Run Unit Tests
        run: go test ./definitions/... -v

      - name: Validate Definitions
        run: vela def vet ./definitions/

      - name: Generate CUE (dry-run)
        run: vela def apply ./definitions/ --dry-run

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Apply Definitions
        run: vela def apply ./definitions/
        env:
          KUBECONFIG: ${{ secrets.KUBECONFIG }}

Distribution

Go Modules

Go definitions are distributed as standard Go packages:

// go.mod for your definitions package
module github.com/myorg/platform-defs

require github.com/oam-dev/kubevela v1.10.0  // includes pkg/definition/defkit
# Consumers import your definitions
go get github.com/myorg/platform-defs@v1.0.0
// Use in application code
import "github.com/myorg/platform-defs/components/webservice"

app := application.New("my-app").
    AddComponent(webservice.New("api").Image("myapp:v1"))

This leverages Go's existing ecosystem: semantic versioning, checksums, proxy caching, and private module support.

Module Dependencies

Defkit modules could import definitions from other Go modules, enabling composition and reuse across teams and organizations:

// go.mod
module github.com/myorg/platform-defs

require (
    github.com/oam-dev/kubevela v1.10.0          // defkit SDK
    github.com/myorg/base-components v1.2.0      // shared base definitions
    github.com/anotherorg/monitoring-defs v2.0.0 // third-party definitions
)
// components/monitored_webservice.go
package components

import (
    "github.com/oam-dev/kubevela/pkg/definition/defkit"
    "github.com/myorg/base-components/components"       // import base webservice
    "github.com/anotherorg/monitoring-defs/traits"      // import monitoring traits
)

// MonitoredWebservice composes a webservice with monitoring capabilities
func MonitoredWebservice() *defkit.ComponentDefinition {
    // Extend or compose with imported definitions
    base := components.WebserviceParams()

    // Add monitoring-specific parameters
    metricsPort := defkit.Int("metricsPort").Default(9090)
    enableTracing := defkit.Bool("enableTracing").Default(true)

    return defkit.NewComponent("monitored-webservice").
        Description("Webservice with built-in monitoring").
        Workload("apps/v1", "Deployment").
        Params(append(base, metricsPort, enableTracing)...).
        Template(monitoredWebserviceTemplate)
}

This would enable:

  • Layered platforms: Base definitions from central team, extended by product teams
  • Third-party ecosystems: Community-contributed definition libraries
  • Version control: Semantic versioning for definition dependencies via Go modules

Addon Integration

KubeVela addons could include Go-based definitions alongside traditional CUE definitions. The proposed addon structure would support both:

my-addon/
├── metadata.yaml           # Addon metadata
├── resources/              # Kubernetes resources
├── definitions/            # Traditional CUE definitions (existing)
│   ├── component-a.cue
│   └── trait-b.cue
├── godef/                  # Go-based definitions (new)
│   ├── go.mod
│   ├── go.sum
│   ├── components/
│   │   ├── webservice.go
│   │   └── worker.go
│   └── traits/
│       ├── scaler.go
│       └── ingress.go
└── README.md

Key design points:

  1. Coexistence: The definitions/ folder contains CUE definitions, godef/ contains Go definitions. Both would be processed during addon installation.

  2. CLI detection: The vela addon commands would detect the godef/ folder and compile Go definitions to CUE before applying:

    # During addon enable, the CLI:
    # 1. Compiles godef/ to CUE using defkit
    # 2. Applies both definitions/ and compiled godef/ outputs
    vela addon enable my-addon
    
  3. Module dependencies in addons: The godef/go.mod could import definitions from other Go modules:

    // godef/go.mod
    module github.com/my-addon/godef
    
    require (
        github.com/oam-dev/kubevela v1.10.0
        github.com/kubevela/catalog/addons/base-defs v1.0.0  // depend on another addon's definitions
    )
    
  4. Build-time compilation: Addon maintainers could pre-compile Go definitions to CUE for distribution:

    # Pre-compile for distribution (optional, for addons that want to avoid Go dependency at install time)
    cd my-addon/godef
    vela def render . --output ../definitions-compiled/
    
  5. Validation: The addon validation process would include Go definition validation:

    vela addon validate my-addon  # validates both CUE and Go definitions
    

GitOps

# Render Go definitions to CUE/YAML for GitOps (extends `vela def render`)
vela def render ./definitions/ --output ./dist/ --format yaml

# Commit and sync with ArgoCD/Flux
git add ./dist/
git commit -m "Update definitions"
git push

Implementation Plan

Note

: This implementation plan represents the proposed design direction. API names, method signatures, and specific features may evolve during implementation as we discover edge cases, gather community feedback, or identify opportunities for improvement. The core goals and architecture will remain stable, but implementation details are subject to refinement.

Phase 1: Core Framework

  • Single defkit package with NewComponent(), NewTrait(), NewPolicy(), NewWorkflowStep() functions
  • VelaCtx() API for runtime context (Name(), Namespace(), AppName(), ClusterVersion(), etc.)
  • Parameter type system: String, Int, Bool, Float, Enum, List, Object, Map, Struct, OneOf, Variant with validation
  • Convenience types: StringList, IntList, StringKeyMap
  • Schema-agnostic resource builder with Set(), SetIf(), SpreadIf(), If()/EndIf(), VersionIf()
  • Collection operations: Each(), Filter(), Map(), Pick(), Rename(), Wrap(), DefaultField(), Flatten(), Dedupe()
  • Multi-source collections: FromFields(), MapBySource(), Nested(), Optional()
  • Helper builder pattern: tpl.Helper(), StructArrayHelper(), ConcatHelper(), DedupeHelper()
  • Transform utilities: Transform(), HasExposedPorts(), Format()
  • Expression helpers: Lit(), Eq(), Ne(), Lt(), Le(), Gt(), Ge(), And(), Or(), Not()
  • IR→CUE compiler via cuegen.go
  • CLI integration: vela def apply, vela def vet for Go files
  • CLI: vela def init-module, vela def validate-module
  • Registry pattern with defkit.Register(), All(), Components(), Clear(), Count(), ToJSON()
  • Status/Health helpers for all workload types:
    • Deployment: DeploymentStatus(), DeploymentHealth()
    • DaemonSet: DaemonSetStatus(), DaemonSetHealth()
    • StatefulSet: StatefulSetStatus(), StatefulSetHealth()
    • Job: JobHealth()
    • CronJob: CronJobHealth()
  • Composable Health Expressions via unified Health() API (generic for any resource type):
    • Condition checks: Health().Condition(), Health().AllTrue(), Health().AnyTrue()
    • Phase checks: Health().Phase(), Health().PhaseField()
    • Field comparisons: Health().Field(), Health().FieldRef(), Health().Exists(), Health().NotExists()
    • Combinators: Health().And(), Health().Or(), Health().Not()
    • Always healthy: Health().Always()
    • Policy generation: Health().Policy(expr), HealthPolicyExpr(expr)
  • Composable Status Expressions via unified Status() API (generic for any resource type):
    • Field extraction: Status().Field(), Status().SpecField(), Status().Exists()
    • Condition access: Status().Condition().StatusValue(), .Message(), .Reason()
    • Message building: Status().Format(), Status().Concat()
    • Conditional messages: Status().Switch(), Status().Case(), Status().Default()
    • Health-aware: Status().HealthAware(healthyMsg, unhealthyMsg)
    • Structured details: Status().WithDetails(), Status().Detail()
    • Status generation: Status().Build(), CustomStatusExpr(expr)
  • TestContext for unit testing: TestContext(), WithName(), WithNamespace(), WithParam(), WithClusterVersion(), etc.
  • Custom matchers for Gomega-based testing
  • Example components: webservice, worker, task, cron-task, daemon, statefulset, k8s-objects, ref-objects

Phase 2: Complete Definition Support

  • PolicyDefinition with NewPolicy()
    • Example policies: topology, apply-once, garbage-collect, override, read-only, replication, resource-update, shared-resource, take-over
  • WorkflowStepDefinition with NewWorkflowStep()
    • Example workflow steps: deploy, suspend, apply-component, apply-deployment, apply-object, apply-terraform-config, apply-terraform-provider, build-push-image, check-metrics, clean-jobs, collect-service-endpoints, create-config, delete-config, depends-on-app, deploy-cloud-resource, export-data, export-service, export2config, export2secret, generate-jdbc-connection, list-config, notification, print-message-in-status, read-config, read-object, request, share-cloud-resource, step-group, webhook
  • TraitDefinition with NewTrait()
    • Container patches: command, env, container-image, container-ports, init-container, startup-probe, resource, securitycontext
    • Pod-level: affinity, hostalias, lifecycle, podsecuritycontext, sidecar, topologyspreadconstraints
    • Scaling: scaler, cpuscaler, hpa
    • Networking: expose, gateway, pure-ingress, service-account, service-binding
    • Storage: storage
    • Metadata: labels, annotations
    • Advanced: json-merge-patch, json-patch, k8s-update-strategy, nocalhost
  • RawCUE() escape hatch for complex CUE patterns
  • Trait patterns: PatchContainer helper, SetRawPatchBlock, SetRawOutputsBlock

Phase 3: Distribution & Ecosystem

  • Addon integration: Support godef/ folder in addon structure for Go-based definitions
  • Module dependencies: Enable defkit modules to import definitions from other Go modules
  • CLI addon commands: vela addon enable detects and compiles Go definitions
  • Addon validation: vela addon validate includes Go definition validation
  • OCI registry support for distributing compiled definitions
  • Migration tooling (vela def gen-go for CUE→Go)
  • Enhanced documentation and tutorials

Phase 4: Advanced Features

  • Other languages based on community demand
  • IDE plugins
  • Definition composition

Compatibility

Coexistence with CUE

  • CUE definitions remain fully supported
  • defkit is an alternative path, not a replacement
  • Both produce valid X-Definition CRs

Migration

# Optional: Import existing CUE to Go (inverse of gen-api, NEW subcommand)
vela def gen-go ./legacy-definitions/*.cue --output ./converted/

FAQ

Q: Can I still write CUE definitions? A: Yes. CUE definitions remain fully supported. defkit is an alternative.

Q: How do I access runtime values like readyReplicas? A: Use defkit.Status().Field("status.readyReplicas") when defining custom status expressions via CustomStatus(). This generates CUE that accesses context.output.status.readyReplicas at runtime. For common workloads, use pre-built helpers like defkit.DeploymentStatus().Build().

Q: What about CUE unification (&)? A: Common patterns like defaults with runtime values are handled automatically. For complex unification, use RawCUE().

Q: Why Go first? A: Go is KubeVela's implementation language and widely used in the Kubernetes ecosystem. Other languages may follow based on community demand.

Q: How do I test definitions without a cluster? A: Use defkit.TestContext() to create mock contexts with parameters, cluster version, and output status. All testing can be done with standard Go testing frameworks.

Q: Can I see the generated CUE? A: Yes. Use vela def render ./definition.go --output cue or def.ToCUE() in tests.