mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 12:36:34 +00:00
* 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>
137 lines
4.5 KiB
Go
137 lines
4.5 KiB
Go
/*
|
|
Copyright 2021. The KubeVela Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package utils
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
velacuex "github.com/oam-dev/kubevela/pkg/cue/cuex"
|
|
"github.com/oam-dev/kubevela/pkg/cue/cuex/providers/helm"
|
|
|
|
"cuelang.org/go/cue/cuecontext"
|
|
cueErrors "cuelang.org/go/cue/errors"
|
|
"github.com/pkg/errors"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/apimachinery/pkg/util/validation"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
|
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1beta1/core"
|
|
)
|
|
|
|
// ContextRegex to match '**: reference "context" not found'
|
|
var ContextRegex = `^.+:\sreference\s\"context\"\snot\sfound$`
|
|
|
|
// ValidateDefinitionRevision validate whether definition will modify the immutable object definitionRevision
|
|
func ValidateDefinitionRevision(ctx context.Context, cli client.Client, def runtime.Object, defRevNamespacedName types.NamespacedName) error {
|
|
if errs := validation.IsQualifiedName(defRevNamespacedName.Name); len(errs) != 0 {
|
|
return errors.Errorf("invalid definitionRevision name %s:%s", defRevNamespacedName.Name, strings.Join(errs, ","))
|
|
}
|
|
defRev := new(v1beta1.DefinitionRevision)
|
|
if err := cli.Get(ctx, defRevNamespacedName, defRev); err != nil {
|
|
return client.IgnoreNotFound(err)
|
|
}
|
|
|
|
newRev, _, err := core.GatherRevisionInfo(def)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if defRev.Spec.RevisionHash != newRev.Spec.RevisionHash {
|
|
return errors.New("the definition's spec is different with existing definitionRevision's spec")
|
|
}
|
|
if !core.DeepEqualDefRevision(defRev, newRev) {
|
|
return errors.New("the definition's spec is different with existing definitionRevision's spec")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateCueTemplate validate cueTemplate
|
|
func ValidateCueTemplate(cueTemplate string) error {
|
|
val := cuecontext.New().CompileString(cueTemplate)
|
|
if e := checkError(val.Err()); e != nil {
|
|
return e
|
|
}
|
|
err := val.Validate()
|
|
return checkError(err)
|
|
}
|
|
|
|
// ValidateCuexTemplate validate cueTemplate with CueX for types utilising it.
|
|
// Uses WorkloadCompiler so that templates referencing internal provider
|
|
// packages (e.g. "vela/helm") parse during admission validation.
|
|
//
|
|
// The compile runs under helm.WithDryRun so that any provider package that
|
|
// honors the dry-run signal short-circuits to a side-effect-free path.
|
|
// Without this, a ComponentDefinition whose CUE supplies fully concrete
|
|
// arguments to helm.#Render could trigger a real chart fetch and helm
|
|
// install during admission.
|
|
func ValidateCuexTemplate(ctx context.Context, cueTemplate string) error {
|
|
ctx = helm.WithDryRun(ctx)
|
|
val, err := velacuex.WorkloadCompiler.Get().CompileStringWithOptions(ctx, cueTemplate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if e := checkError(val.Err()); e != nil {
|
|
return e
|
|
}
|
|
err = val.Validate()
|
|
return checkError(err)
|
|
}
|
|
|
|
func checkError(err error) error {
|
|
re := regexp.MustCompile(ContextRegex)
|
|
if err != nil {
|
|
// ignore context not found error
|
|
for _, e := range cueErrors.Errors(err) {
|
|
if !re.MatchString(e.Error()) {
|
|
return cueErrors.New(e.Error())
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateSemanticVersion validates if a Definition's version includes all of
|
|
// major,minor & patch version values.
|
|
func ValidateSemanticVersion(version string) error {
|
|
if version != "" {
|
|
versionParts := strings.Split(version, ".")
|
|
if len(versionParts) != 3 {
|
|
return errors.New("Not a valid version")
|
|
}
|
|
|
|
for _, versionPart := range versionParts {
|
|
if _, err := strconv.Atoi(versionPart); err != nil {
|
|
return errors.New("Not a valid version")
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateMultipleDefVersionsNotPresent validates that both Name Annotation Revision and Spec.Version are not present
|
|
func ValidateMultipleDefVersionsNotPresent(version, revisionName, objectType string) error {
|
|
if version != "" && revisionName != "" {
|
|
return fmt.Errorf("%s has both spec.version and revision name annotation. Only one can be present", objectType)
|
|
}
|
|
return nil
|
|
}
|