Fix: Support cuex package imports in vela show/def show commands (#7017)
Some checks failed
Webhook Upgrade Validation / webhook-upgrade-check (push) Failing after 2m0s

- Added GetCUExParameterValue()
 function that uses cuex.DefaultCompiler instead
    of standard CUE compiler

 - Added GetParametersWithCuex() function with cuex support
- Updated GetBaseResourceKinds() to use cuex compiler
- Updated all callers to use cuex-aware functions

 Fixes #7012

Signed-off-by: GoGstickGo <janilution@gmail.com>
This commit is contained in:
GoGstickGo
2026-01-16 09:56:10 +00:00
committed by GitHub
parent 0b85d55e68
commit 8c85dcdbbc
9 changed files with 300 additions and 9 deletions

View File

@@ -17,11 +17,13 @@ limitations under the License.
package cue
import (
"context"
"errors"
"strings"
"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
"github.com/kubevela/pkg/cue/cuex"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/cue/process"
@@ -32,6 +34,7 @@ import (
var ErrParameterNotExist = errors.New("parameter not exist")
// GetParameters get parameter from cue template
// Deprecated: Use GetParametersWithCuex for templates with cuex imports
func GetParameters(templateStr string) ([]types.Parameter, error) {
template := cuecontext.New().CompileString(templateStr + BaseTemplate)
paramVal := template.LookupPath(cue.ParsePath(process.ParameterFieldName))
@@ -69,6 +72,51 @@ func GetParameters(templateStr string) ([]types.Parameter, error) {
return params, nil
}
// GetParametersWithCuex get parameter from cue template with cuex support for package imports
func GetParametersWithCuex(ctx context.Context, templateStr string) ([]types.Parameter, error) {
template, err := cuex.DefaultCompiler.Get().CompileStringWithOptions(
ctx,
templateStr+BaseTemplate,
cuex.DisableResolveProviderFunctions{},
)
if err != nil {
return nil, err
}
paramVal := template.LookupPath(cue.ParsePath(process.ParameterFieldName))
if !paramVal.Exists() {
return nil, ErrParameterNotExist
}
iter, err := paramVal.Fields(cue.Definitions(true), cue.Hidden(true), cue.All())
if err != nil {
return nil, err
}
// parse each fields in the parameter fields
var params []types.Parameter
for iter.Next() {
if iter.Selector().IsDefinition() {
continue
}
var param = types.Parameter{
Name: util.GetIteratorLabel(*iter),
Required: !iter.IsOptional(),
}
val := iter.Value()
param.Type = val.IncompleteKind()
if def, ok := val.Default(); ok && def.IsConcrete() {
param.Required = false
param.Type = def.Kind()
param.Default = GetDefault(def)
}
if param.Default == nil {
param.Default = getDefaultByKind(param.Type)
}
param.Short, param.Usage, param.Alias, param.Ignore = RetrieveComments(val)
params = append(params, param)
}
return params, nil
}
func getDefaultByKind(k cue.Kind) interface{} {
// nolint:exhaustive
switch k {

View File

@@ -17,11 +17,18 @@ limitations under the License.
package cue
import (
"context"
"os"
"strings"
"testing"
"cuelang.org/go/cue"
"github.com/kubevela/pkg/cue/cuex"
"github.com/kubevela/pkg/util/singleton"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
dynamicfake "k8s.io/client-go/dynamic/fake"
"github.com/oam-dev/kubevela/apis/types"
)
@@ -100,3 +107,88 @@ func TestGetParameter(t *testing.T) {
assert.True(t, foundName, "Should find 'name' parameter")
assert.True(t, foundPort, "Should find 'port' parameter")
}
func TestGetParametersWithCuex(t *testing.T) {
ctx := context.Background()
// Test 1: Basic template without imports
data, _ := os.ReadFile("testdata/workloads/metrics.cue")
params, err := GetParametersWithCuex(ctx, string(data))
assert.NoError(t, err)
assert.Equal(t, params, []types.Parameter{
{Name: "format", Required: false, Default: "prometheus", Usage: "format of the metrics, " +
"default as prometheus", Short: "f", Type: cue.StringKind},
{Name: "enabled", Required: false, Default: true, Type: cue.BoolKind},
{Name: "port", Required: false, Default: int64(8080), Type: cue.IntKind},
{Name: "selector", Required: false, Usage: "the label selector for the pods, default is the workload labels", Type: cue.StructKind},
})
// Test 2: Template with cuex package imports
// Setup test package
packageObj := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "cue.oam.dev/v1alpha1",
"kind": "Package",
"metadata": map[string]interface{}{
"name": "test-package",
"namespace": "vela-system",
},
"spec": map[string]interface{}{
"path": "test/ext",
"templates": map[string]interface{}{
"test/ext": strings.TrimSpace(`
package ext
#Config: {
host: string
port: int
}
`),
},
},
},
}
dcl := dynamicfake.NewSimpleDynamicClient(runtime.NewScheme(), packageObj)
singleton.DynamicClient.Set(dcl)
cuex.DefaultCompiler.Reload()
defer cuex.DefaultCompiler.Reload()
defer singleton.ReloadClients()
// Template with import
templateWithImport := `
import "test/ext"
parameter: {
name: string
config: ext.#Config
}
`
params, err = GetParametersWithCuex(ctx, templateWithImport)
assert.NoError(t, err, "Should compile template with cuex imports")
assert.Equal(t, 2, len(params), "Should find 2 parameters")
foundName := false
foundConfig := false
for _, p := range params {
if p.Name == "name" {
foundName = true
assert.Equal(t, cue.StringKind, p.Type)
assert.True(t, p.Required)
}
if p.Name == "config" {
foundConfig = true
assert.Equal(t, cue.StructKind, p.Type)
assert.True(t, p.Required)
}
}
assert.True(t, foundName, "Should find 'name' parameter")
assert.True(t, foundConfig, "Should find 'config' parameter")
// Test 3: Template without parameter field
data, _ = os.ReadFile("testdata/workloads/empty.cue")
params, err = GetParametersWithCuex(ctx, string(data))
assert.NoError(t, err)
var exp []types.Parameter
assert.Equal(t, exp, params)
}

View File

@@ -38,6 +38,7 @@ import (
"github.com/AlecAivazis/survey/v2"
"github.com/hashicorp/hcl/v2/hclparse"
cuexv1alpha1 "github.com/kubevela/pkg/apis/cue/v1alpha1"
"github.com/kubevela/pkg/cue/cuex"
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
clustergatewayapi "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
"github.com/oam-dev/terraform-config-inspect/tfconfig"
@@ -210,6 +211,23 @@ func GetCUEParameterValue(cueStr string) (cue.Value, error) {
return val, nil
}
// GetCUExParameterValue converts definitions with cuex imports to cue format and extracts parameter field
func GetCUExParameterValue(ctx context.Context, cueStr string) (cue.Value, error) {
template, err := cuex.DefaultCompiler.Get().CompileStringWithOptions(
ctx,
cueStr+velacue.BaseTemplate,
cuex.DisableResolveProviderFunctions{},
)
if err != nil {
return cue.Value{}, err
}
val := template.LookupPath(cue.ParsePath(process.ParameterFieldName))
if !val.Exists() {
return cue.Value{}, velacue.ErrParameterNotExist
}
return val, nil
}
// GenOpenAPI generates OpenAPI json schema from cue.Instance
func GenOpenAPI(val cue.Value) (b []byte, err error) {
defer func() {

View File

@@ -25,10 +25,16 @@ import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"cuelang.org/go/cue/cuecontext"
"github.com/kubevela/pkg/cue/cuex"
"github.com/kubevela/pkg/util/singleton"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
dynamicfake "k8s.io/client-go/dynamic/fake"
"cuelang.org/go/cue/load"
"github.com/crossplane/crossplane-runtime/pkg/test"
@@ -299,6 +305,118 @@ func TestGetCUEParameterValue4RareCases(t *testing.T) {
}
}
func TestGetCUExParameterValue(t *testing.T) {
ctx := context.Background()
type want struct {
err error
}
var validCueStr = `
parameter: {
min: int
}
`
var cueStrNotContainParameter = `
output: {
min: int
}
`
cases := map[string]struct {
reason string
cueStr string
want want
}{
"ValidCUEString": {
reason: "cue string is valid",
cueStr: validCueStr,
want: want{
err: nil,
},
},
"CUEStringNotContainParameter": {
reason: "cue string doesn't contain Parameter",
cueStr: cueStrNotContainParameter,
want: want{
err: fmt.Errorf("parameter not exist"),
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
val, err := GetCUExParameterValue(ctx, tc.cueStr)
if tc.want.err != nil {
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nGetCUExParameterValue(...): -want error, +got error:\n%s", tc.reason, diff)
}
} else {
assert.NoError(t, err)
assert.True(t, val.Exists(), "parameter value should exist")
}
})
}
}
func TestGetCUExParameterValueWithImports(t *testing.T) {
ctx := context.Background()
// Setup a test package similar to webhook/utils test
packageObj := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "cue.oam.dev/v1alpha1",
"kind": "Package",
"metadata": map[string]interface{}{
"name": "test-package",
"namespace": "vela-system",
},
"spec": map[string]interface{}{
"path": "test/builtin",
"templates": map[string]interface{}{
"test/builtin": strings.TrimSpace(`
package builtin
#Config: {
name: string
value: string
}
`),
},
},
},
}
dcl := dynamicfake.NewSimpleDynamicClient(runtime.NewScheme(), packageObj)
singleton.DynamicClient.Set(dcl)
cuex.DefaultCompiler.Reload()
defer cuex.DefaultCompiler.Reload()
defer singleton.ReloadClients()
var cueStrWithImport = `
import "test/builtin"
parameter: {
name: string
config: builtin.#Config
}
`
val, err := GetCUExParameterValue(ctx, cueStrWithImport)
assert.NoError(t, err, "should compile CUE with cuex import")
assert.True(t, val.Exists(), "parameter value should exist")
// Verify we can access the parameter fields
iter, err := val.Fields()
assert.NoError(t, err)
fieldCount := 0
for iter.Next() {
fieldCount++
}
assert.Equal(t, 2, fieldCount, "should have 2 parameter fields: name and config")
}
func TestGenOpenAPI(t *testing.T) {
type want struct {
targetSchemaFile string

View File

@@ -371,7 +371,9 @@ func HandleTemplate(in *runtime.RawExtension, schematic *commontypes.Schematic,
if tmp.CueTemplate == "" {
return types.Capability{}, errors.New("template not exist in definition")
}
tmp.Parameters, err = cue.GetParameters(tmp.CueTemplate)
// TODO: Accept context parameter for proper cancellation/timeout support
// Currently using Background() to avoid breaking changes to function
tmp.Parameters, err = cue.GetParametersWithCuex(context.Background(), tmp.CueTemplate)
if err != nil && !errors.Is(err, cue.ErrParameterNotExist) {
return types.Capability{}, err
}

View File

@@ -95,7 +95,9 @@ func (ref *ConsoleReference) GenerateCUETemplateProperties(capability *types.Cap
ref.DisplayFormat = "console"
capName := capability.Name
cueValue, err := common.GetCUEParameterValue(capability.CueTemplate)
// TODO: Accept context parameter for proper cancellation/timeout support
// Currently using Background() to avoid breaking changes to function
cueValue, err := common.GetCUExParameterValue(context.Background(), capability.CueTemplate)
if err != nil {
return "", nil, fmt.Errorf("failed to retrieve `parameters` value from %s with err: %w", capName, err)
}

View File

@@ -145,7 +145,10 @@ func (ref *MarkdownReference) GenerateMarkdownForCap(_ context.Context, c types.
capNameInTitle := ref.makeReadableTitle(capName)
switch c.Category {
case types.CUECategory:
cueValue, err := common.GetCUEParameterValue(c.CueTemplate)
// TODO: Use context from caller for proper cancellation/timeout support
// Currently using Background() to avoid breaking changes to function
ctx := context.Background()
cueValue, err := common.GetCUExParameterValue(ctx, c.CueTemplate)
if err != nil && !errors.Is(err, cue.ErrParameterNotExist) {
return "", fmt.Errorf("failed to retrieve `parameters` value from %s with err: %w", c.Name, err)
}
@@ -156,7 +159,7 @@ func (ref *MarkdownReference) GenerateMarkdownForCap(_ context.Context, c types.
}
if c.Type == types.TypeComponentDefinition {
var warnErr error
baseDoc, warnErr = GetBaseResourceKinds(c.CueTemplate, ref.Client.RESTMapper())
baseDoc, warnErr = GetBaseResourceKinds(ctx, c.CueTemplate, ref.Client.RESTMapper())
if warnErr != nil {
klog.Warningf("failed to get base resource kinds for %s: %v", c.Name, warnErr)
}

View File

@@ -29,8 +29,8 @@ import (
"cuelang.org/go/cue"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/cuecontext"
"github.com/getkin/kin-openapi/openapi3"
"github.com/kubevela/pkg/cue/cuex"
"github.com/olekukonko/tablewriter"
"github.com/pkg/errors"
"golang.org/x/mod/modfile"
@@ -699,8 +699,15 @@ func WalkParameterSchema(parameters *openapi3.Schema, name string, depth int) er
}
// GetBaseResourceKinds helps get resource.group string of components' base resource
func GetBaseResourceKinds(cueStr string, mapper meta.RESTMapper) (string, error) {
tmpl := cuecontext.New().CompileString(cueStr + velacue.BaseTemplate)
func GetBaseResourceKinds(ctx context.Context, cueStr string, mapper meta.RESTMapper) (string, error) {
tmpl, err := cuex.DefaultCompiler.Get().CompileStringWithOptions(
ctx,
cueStr+velacue.BaseTemplate,
cuex.DisableResolveProviderFunctions{},
)
if err != nil {
return "", err
}
kindValue := tmpl.LookupPath(cue.ParsePath("output.kind"))
kind, err := kindValue.String()
if err != nil {

View File

@@ -17,6 +17,7 @@ limitations under the License.
package docgen
import (
"context"
"encoding/json"
"fmt"
"os"
@@ -622,7 +623,7 @@ func TestExtractParameter(t *testing.T) {
ref := &MarkdownReference{}
for key, ca := range testcases {
cueValue, _ := common.GetCUEParameterValue(ca.cueTemplate)
cueValue, _ := common.GetCUExParameterValue(context.Background(), ca.cueTemplate)
out, _, err := ref.parseParameters("", cueValue, key, 0, false)
assert.NoError(t, err, key)
assert.Contains(t, out, ca.contains, key)
@@ -728,7 +729,7 @@ func TestExtractParameterFromFiles(t *testing.T) {
for key, ca := range testcases {
content, err := os.ReadFile(ca.path)
assert.NoError(t, err, ca.path)
cueValue, _ := common.GetCUEParameterValue(string(content))
cueValue, _ := common.GetCUExParameterValue(context.Background(), string(content))
out, _, err := ref.parseParameters("", cueValue, key, 0, false)
assert.NoError(t, err, key)
assert.Contains(t, out, ca.contains, key)