mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-27 16:17:34 +00:00
Feat: support validate properties with CueX (#5894)
Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>
This commit is contained in:
@@ -24,11 +24,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
velacuex "github.com/oam-dev/kubevela/pkg/cue/cuex"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"github.com/kubevela/pkg/cue/cuex"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -49,6 +45,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
icontext "github.com/oam-dev/kubevela/pkg/config/context"
|
||||
"github.com/oam-dev/kubevela/pkg/config/writer"
|
||||
velacue "github.com/oam-dev/kubevela/pkg/cue"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/script"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
@@ -474,11 +471,9 @@ func (k *kubeConfigFactory) ParseConfig(ctx context.Context,
|
||||
Namespace: meta.Namespace,
|
||||
}
|
||||
// Compile the config template
|
||||
contextOption := cuex.WithExtraData("context", contextValue)
|
||||
parameterOption := cuex.WithExtraData(TemplateParameter, meta.Properties)
|
||||
val, err := velacuex.KubeVelaDefaultCompiler.Get().CompileStringWithOptions(ctx, string(template.Template), contextOption, parameterOption)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compile config template: %w", err)
|
||||
val, err := template.Template.RunAndOutputWithCueX(ctx, contextValue, meta.Properties)
|
||||
if err != nil && !velacue.IsFieldNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
// Render the validation response and check validation result
|
||||
valid := val.LookupPath(cue.ParsePath(TemplateValidationReturns))
|
||||
|
||||
@@ -22,12 +22,14 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/kubevela/pkg/cue/cuex"
|
||||
|
||||
cuelang "cuelang.org/go/cue"
|
||||
"cuelang.org/go/cue/errors"
|
||||
"github.com/kubevela/workflow/pkg/cue/model/value"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/cue"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/cuex"
|
||||
velacuex "github.com/oam-dev/kubevela/pkg/cue/cuex"
|
||||
)
|
||||
|
||||
// CUE the cue script with the template format
|
||||
@@ -63,7 +65,7 @@ func (c CUE) ParseToValue() (*value.Value, error) {
|
||||
func (c CUE) ParseToValueWithCueX() (cuelang.Value, error) {
|
||||
// the cue script must be first, it could include the imports
|
||||
template := string(c) + "\n" + cue.BaseTemplate
|
||||
val, err := cuex.KubeVelaDefaultCompiler.Get().CompileString(context.Background(), template)
|
||||
val, err := velacuex.KubeVelaDefaultCompiler.Get().CompileString(context.Background(), template)
|
||||
if err != nil {
|
||||
return cuelang.Value{}, fmt.Errorf("failed to compile config template: %w", err)
|
||||
}
|
||||
@@ -168,6 +170,35 @@ func (c CUE) RunAndOutput(context interface{}, properties map[string]interface{}
|
||||
return render.LookupValue(outputField...)
|
||||
}
|
||||
|
||||
// RunAndOutputWithCueX run the cue script and return the values of the specified field.
|
||||
// The output field must be under the template field.
|
||||
func (c CUE) RunAndOutputWithCueX(ctx context.Context, context interface{}, properties map[string]interface{}, outputField ...string) (cuelang.Value, error) {
|
||||
// Validate the properties
|
||||
if err := c.ValidatePropertiesWithCueX(properties); err != nil {
|
||||
return cuelang.Value{}, err
|
||||
}
|
||||
contextOption := cuex.WithExtraData("context", context)
|
||||
parameterOption := cuex.WithExtraData("template.parameter", properties)
|
||||
val, err := velacuex.KubeVelaDefaultCompiler.Get().CompileStringWithOptions(ctx, string(c), contextOption, parameterOption)
|
||||
if !val.Exists() {
|
||||
return cuelang.Value{}, fmt.Errorf("failed to compile config template")
|
||||
}
|
||||
if err != nil {
|
||||
return cuelang.Value{}, fmt.Errorf("failed to compile config template: %w", err)
|
||||
}
|
||||
if Error(val) != nil {
|
||||
return cuelang.Value{}, fmt.Errorf("failed to compile config template: %w", Error(val))
|
||||
}
|
||||
if len(outputField) == 0 {
|
||||
return val, nil
|
||||
}
|
||||
outputFieldVal := val.LookupPath(cuelang.ParsePath(strings.Join(outputField, ".")))
|
||||
if !outputFieldVal.Exists() {
|
||||
return cuelang.Value{}, fmt.Errorf("failed to lookup value: var(path=%s) not exist", strings.Join(outputField, "."))
|
||||
}
|
||||
return outputFieldVal, nil
|
||||
}
|
||||
|
||||
// ValidateProperties validate the input properties by the template
|
||||
func (c CUE) ValidateProperties(properties map[string]interface{}) error {
|
||||
template, err := c.ParseToTemplateValue()
|
||||
@@ -203,6 +234,31 @@ func (c CUE) ValidateProperties(properties map[string]interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatePropertiesWithCueX validate the input properties by the template
|
||||
func (c CUE) ValidatePropertiesWithCueX(properties map[string]interface{}) error {
|
||||
template, err := c.ParseToTemplateValueWithCueX()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
paramPath := cuelang.ParsePath("template.parameter")
|
||||
parameter := template.LookupPath(paramPath)
|
||||
if !parameter.Exists() {
|
||||
return fmt.Errorf("failed to lookup value: var(path=template.parameter) not exist")
|
||||
}
|
||||
props := parameter.FillPath(cuelang.ParsePath(""), properties)
|
||||
if props.Err() != nil {
|
||||
return ConvertFieldError(props.Err())
|
||||
}
|
||||
if err := props.Validate(); err != nil {
|
||||
return ConvertFieldError(err)
|
||||
}
|
||||
_, err = props.MarshalJSON()
|
||||
if err != nil {
|
||||
return ConvertFieldError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParameterError the error report of the parameter field validation
|
||||
type ParameterError struct {
|
||||
Name string
|
||||
@@ -232,3 +288,22 @@ func ConvertFieldError(err error) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Error return value's error information.
|
||||
func Error(val cuelang.Value) error {
|
||||
if !val.Exists() {
|
||||
return errors.New("empty value")
|
||||
}
|
||||
if err := val.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
var gerr error
|
||||
val.Walk(func(value cuelang.Value) bool {
|
||||
if err := value.Eval().Err(); err != nil {
|
||||
gerr = err
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, nil)
|
||||
return gerr
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package script
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -196,6 +197,26 @@ func TestRunAndOutput(t *testing.T) {
|
||||
assert.Equal(t, data["url"], "hub.docker.com")
|
||||
}
|
||||
|
||||
func TestRunAndOutputWithCueX(t *testing.T) {
|
||||
var cueScript = BuildCUEScriptWithDefaultContext([]byte("context:{namespace:string \n name:string}"), []byte(templateWithContextScript))
|
||||
output, err := cueScript.RunAndOutputWithCueX(context.Background(), map[string]interface{}{
|
||||
"name": "nnn",
|
||||
"namespace": "ns",
|
||||
}, map[string]interface{}{
|
||||
"url": "hub.docker.com",
|
||||
"username": "test",
|
||||
"password": "test",
|
||||
"caFile": "test ca",
|
||||
}, "template", "output")
|
||||
assert.Equal(t, err, nil)
|
||||
var data = map[string]interface{}{}
|
||||
err = output.Decode(&data)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, data["name"], "nnn")
|
||||
assert.Equal(t, data["namespace"], "ns")
|
||||
assert.Equal(t, data["url"], "hub.docker.com")
|
||||
}
|
||||
|
||||
func TestValidateProperties(t *testing.T) {
|
||||
var cueScript = CUE(templateScript)
|
||||
// miss the required parameter
|
||||
@@ -231,6 +252,41 @@ func TestValidateProperties(t *testing.T) {
|
||||
assert.Equal(t, strings.Contains(err.(*ParameterError).Message, "2 errors in empty disjunction"), true)
|
||||
}
|
||||
|
||||
func TestValidatePropertiesWithCueX(t *testing.T) {
|
||||
var cueScript = CUE(templateScript)
|
||||
// miss the required parameter
|
||||
err := cueScript.ValidatePropertiesWithCueX(map[string]interface{}{
|
||||
"url": "hub.docker.com",
|
||||
})
|
||||
assert.Equal(t, err.(*ParameterError).Message, "This parameter is required")
|
||||
|
||||
// wrong the parameter value type
|
||||
err = cueScript.ValidatePropertiesWithCueX(map[string]interface{}{
|
||||
"url": 1,
|
||||
"username": "ddd",
|
||||
})
|
||||
assert.Equal(t, strings.Contains(err.(*ParameterError).Message, "conflicting values"), true)
|
||||
assert.Equal(t, strings.Contains(err.(*ParameterError).Name, "url"), true)
|
||||
|
||||
// wrong the parameter value
|
||||
err = cueScript.ValidatePropertiesWithCueX(map[string]interface{}{
|
||||
"url": "ddd",
|
||||
"username": "ddd",
|
||||
})
|
||||
assert.Equal(t, strings.Contains(err.(*ParameterError).Message, "This parameter is required"), true)
|
||||
assert.Equal(t, strings.Contains(err.(*ParameterError).Name, "options"), true)
|
||||
|
||||
// wrong the parameter value and no required value
|
||||
err = cueScript.ValidatePropertiesWithCueX(map[string]interface{}{
|
||||
"url": "ddd",
|
||||
"username": "ddd",
|
||||
"options": "o3",
|
||||
})
|
||||
fmt.Println(err.(*ParameterError).Message)
|
||||
assert.Equal(t, strings.Contains(err.(*ParameterError).Name, "options"), true)
|
||||
assert.Equal(t, strings.Contains(err.(*ParameterError).Message, "2 errors in empty disjunction"), true)
|
||||
}
|
||||
|
||||
func TestParsePropertiesToSchema(t *testing.T) {
|
||||
cue := CUE([]byte(withPackage))
|
||||
schema, err := cue.ParsePropertiesToSchema()
|
||||
|
||||
Reference in New Issue
Block a user