Files
kubevela/pkg/cue/upgrade
5a44b5f10c Feat: auto remediate cue issues (#7199)
* feat(cue/upgrade): auto-remediate legacy CUE syntax at render time

Transparently rewrite CUE templates that use deprecated list arithmetic
(+, *) and conflicting field names (error) so that older definitions
continue to work with CUE ≥ v0.14 (KubeVela ≥ 1.11).

- CUEUpgradeFunc registry with ID, CUE/KubeVela version guards, precheck,
  and upgrade function fields
- upgradeListConcatenation: rewrites list1+list2 → list.Concat([list1,list2])
  and list*n → list.Repeat(list, n); adds "list" import as needed
- collectAddChain + extractListConcatArgs: flatten left-associative + chains
  and existing list.Concat([...]) leaves into a single flat call, so both
  fresh chains (a+b+c+d) and partially-upgraded chains produce one
  list.Concat([a,b,c,d]) with no nesting across repeated passes
- upgradeErrorFieldLabel: rewrites unquoted `error` field labels to "error"
  to avoid conflict with the CUE 0.14 built-in; precheck uses a tighter
  \berror\s*: regex to avoid false positives on identifiers like errorMessage
- EnsureCueVersionCompatibility: single entry point used at render time;
  LRU cache with TTL eviction, Prometheus metrics, feature flag
- ParseVersion: regex anchored to reject garbage suffixes (e.g. "1.11foo")
  while accepting pre-release+build metadata (e.g. "v1.13.0-alpha.1+dev")

- template.go: call EnsureCueVersionCompatibility for every template area
  (main, health, custom status, status detail) with correct DefinitionKind
  derived from which definition pointer is non-nil
- validate.go: upgrade policy templates before compiling in
  validateNoRequiredParameters

- `vela def upgrade FILE [-o OUTPUT]`: upgrades a single .cue file
- `vela def upgrade FILE --validate [--quiet]`: exit 1 if upgrade needed
- `vela def compat definitions` / `vela def compat applications`: scan
  cluster definitions/apps for compat issues; output as table or YAML
- Cyclomatic complexity kept below threshold by extracting scanDefinitions,
  scanDefRevisions, buildDefCompatReport, scanApplications, scanAppRevision
  as standalone functions with options structs
- revisionNum() helper for numeric vN comparison (avoids lexicographic bugs)
- mergeImports() dedup helper shared by ToCUEString and formatCUEString
- ANSI escape sequences replaced with fatih/color for portability
- goconst: "yaml" → outputFormatYAML named constant throughout

- Component, trait, and policy definition validating handlers: removed
  spurious obj.Name argument from fmt.Sprintf in warning messages

- FromCUEString: only prepend importString to the stored template when
  imports are non-empty; empty importString ("\n") was causing a leading
  newline that made yaml.v3 use |2 block scalar on every generated YAML

- gen_sdk testdata: removed unused imports (vela/op, encoding/base64) from
  one_of.cue that were exposed by our importString+templateString change
- e2e test: fix flaky trait-order assertion using ContainElements instead
  of index-based equality

Upgraded all built-in .cue files that used deprecated list arithmetic:
- vela-templates/definitions/internal/component/cron-task.cue
- vela-templates/definitions/internal/trait/command.cue
- vela-templates/definitions/internal/trait/container-ports.cue
- vela-templates/definitions/internal/trait/env.cue
- vela-templates/definitions/internal/trait/init-container.cue

Removed unused stdlib imports that caused `def gen-api` to fail:
- vela-templates/definitions/internal/workflowstep/apply-deployment.cue
- vela-templates/definitions/internal/workflowstep/apply-terraform-provider.cue
- vela-templates/definitions/internal/workflowstep/build-push-image.cue

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Brian Kane <briankane1@gmail.com>

* fix(cue/upgrade): address PR review comments

- sync.atomic.Pointer for compatCache to fix data race on reinit
- SummaryVec → HistogramVec for both duration metrics (aggregatable
  across HA replicas); buckets tuned to sub-millisecond upgrade path
  and millisecond render path respectively
- errorFieldLabelRe: extend to match optional (?) and required (!)
  field constraint markers before the colon
- cue-compatibility-cache-size: clamp negative values to 0 (disabled)
  with warning log; document 0=disabled in flag help; cache put is
  no-op when capacity <= 0
- webhook: replace RequiresUpgrade+EnsureCueVersionCompatibility double
  parse with single EnsureCueVersionCompatibility call; use string
  comparison to detect upgrade and emit warning
- def compat: log warning when ApplicationRevision fetch fails instead
  of silently skipping (partial results are preserved)
- e2e: only delete definitions in DeferCleanup if this test created
  them (avoid deleting pre-existing shared resources)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Brian Kane <briankane1@gmail.com>

* fix(cue/upgrade): address further PR review comments

- EnsureCueVersionCompatibility: return (string, bool) where bool
  indicates semantic upgrades were applied (len(applied)>0), not
  string inequality — prevents false-positive warnings from
  formatting-only normalisation; update all call sites
- webhook handlers (component, trait, policy): switch from
  RequiresUpgrade+EnsureCueVersionCompatibility double-call to single
  EnsureCueVersionCompatibility call using wasUpgraded bool; remove
  now-unused strings imports
- cache: skip eviction goroutine when capacity==0 (disabled); set
  compatCacheCancel=nil on disabled path to avoid stale cancel on
  next InitCompatibilityCache call
- e2e: replace boolean ownership tracking with createAndTrack helper
  that checks pre-existence via Get before Create, eliminating both
  the ambiguous-create leak and the boilerplate booleans

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Brian Kane <briankane1@gmail.com>

* fix: address reviewer comments — cache determinism, e2e ownership race

- cache: store normalised string in compatEntry.upgraded even when no
  semantic fixes were applied, so cache-hit and cache-miss paths return
  identical output (fixes non-deterministic behaviour flagged in review)
- upgrade: return entry.upgraded on the requiresUpgrade=false cache-hit
  path instead of the raw input cueStr
- e2e: replace GET-then-CREATE ownership inference with atomic CREATE-
  first pattern; err==nil means we created it (register DeferCleanup),
  IsAlreadyExists means it pre-existed (skip cleanup), eliminating the
  GET/CREATE race window that could misattribute ownership

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Brian Kane <briankane1@gmail.com>

---------

Signed-off-by: Brian Kane <briankane1@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 05:57:23 -07:00
..

CUE Upgrade System

This package provides an extensible system for upgrading CUE templates to handle version compatibility as KubeVela evolves.

Architecture

  • upgrade.go: Core interface, registry, and main Upgrade() function
  • upgrade_1_11.go: KubeVela 1.11+ specific upgrades (list concatenation)
  • upgrade_test.go: Comprehensive test suite
  • Future: upgrade_1_12.go, upgrade_1_13.go, etc.

Usage

import "github.com/oam-dev/kubevela/pkg/cue/upgrade"

// Upgrade using current KubeVela CLI version (auto-detected)
result, err := upgrade.Upgrade(cueTemplate)
if err != nil {
    // If version detection fails, you'll get a helpful error message
    // suggesting to use the --target-version flag
    log.Fatal(err)
}

// Upgrade to specific KubeVela version
result, err := upgrade.Upgrade(cueTemplate, "1.11")

// Future: upgrade to newer versions
result, err := upgrade.Upgrade(cueTemplate, "1.12")

Adding New Version Upgrades

To add support for KubeVela 1.12 (example):

  1. Create upgrade_1_12.go:
package upgrade

import "fmt"

func init() {
    // Register your new upgrade functions
    RegisterUpgrade("1.12", upgradeSomeNewFeature)
    RegisterUpgrade("1.12", upgradeAnotherFeature)
}

// upgradeSomeNewFeature handles a breaking change in CUE 0.15
func upgradeSomeNewFeature(cueStr string) (string, error) {
    // Your upgrade logic here
    // Parse, transform, format, return
    return transformedCue, nil
}
  1. Update upgrade.go supportedVersions:
// In the Upgrade() function
supportedVersions := []string{"1.11", "1.12"}
  1. Add tests to upgrade_test.go for the new functionality

  2. Done! The system automatically applies all relevant upgrades

Current Upgrades

1.11 - List Concatenation

  • Problem: list1 + list2 syntax deprecated in underlying CUE version
  • Solution: Converts to list.Concat([list1, list2])
  • Auto-imports: Adds import "list" when needed

Features

  • Version-aware: Only applies upgrades up to target version
  • Composable: Multiple upgrades can be registered per version
  • Safe: Falls back gracefully on errors
  • Extensible: Easy to add new version support
  • Well-tested: Comprehensive test coverage

Examples

Before (Old CUE):

myList1: [1, 2, 3]
myList2: [4, 5, 6]
combined: myList1 + myList2

After (CUE 0.14+):

import "list"

myList1: [1, 2, 3]
myList2: [4, 5, 6] 
combined: list.Concat([myList1, myList2])

This system ensures KubeVela templates remain compatible as CUE continues to evolve.

CLI Usage

The upgrade functionality is available through the KubeVela CLI:

# Upgrade using current KubeVela CLI version (auto-detected)
vela def upgrade my-definition.cue

# Save upgraded definition to a file
vela def upgrade my-definition.cue -o upgraded-definition.cue

# Upgrade for specific KubeVela version
vela def upgrade my-definition.cue --target-version=v1.11

# Also works with just the version number
vela def upgrade my-definition.cue --target-version=1.11

Version Detection:

  • When no --target-version is specified, the system automatically detects the current KubeVela CLI version
  • If version detection fails (e.g., in development builds), you'll get a helpful error message suggesting to use --target-version=1.11
  • Note: Use --target-version= (with equals sign) for the version flag