mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-14 10:00:06 +00:00
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
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:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user