Feat: support validate properties with CueX (#5894)

Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>
This commit is contained in:
JohnJan
2023-04-21 10:05:40 +08:00
committed by GitHub
parent a427c1e4c2
commit 5549619ef9
3 changed files with 137 additions and 11 deletions
+4 -9
View File
@@ -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))
+77 -2
View File
@@ -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
}
+56
View File
@@ -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()