Repository: kubevela/kubevela

Issue:
add mysql operator as KubeVela Custom Workload tutorial in hands on club
[Mysql Operator](https://github.com/presslabs/mysql-operator) can be easily integrated with KubeVela, let's add it as one of the tutorials in hands on club.

The tutorial should have following steps:

1. step up mysql operator
2. define mysql provision as ComponentDefinition
3. Run an Application contains a wordpress and mysql component. The wordpress should consume the resources from mysql.

Refer to the blog: https://docs.bitnami.com/tutorials/secure-wordpress-kubernetes-managed-database-ssl-upgrades/

Discussion:
@FogDong: /assign@briankane: This issue has been inactive for over 2 years and is scheduled for closure as part of our backlog maintenance.

  **If this issue is still relevant:**
  - Please comment below to keep it open
  - Provide updated context if the original description is outdated

  **Timeline:** If there's no activity by **Friday, October 24th**, this issue will be automatically closed.

  **Note:** Issues can always be reopened if they become relevant again. We encourage the community to reopen and reprioritize any incorrectly 
  closed issues.@briankane: This issue has been closed following the stale issue notice. 

If this issue is still relevant and should remain open, please:
1. Reopen the issue
2. Remove the 'stale' label
3. Provide an update on the current status or importance

Thank you for your understanding and contribution to KubeVela!

Solution patch:
diff --git a/pkg/cue/model/sets/utils.go b/pkg/cue/model/sets/utils.go
index 71f71954..0a108069 100644
--- a/pkg/cue/model/sets/utils.go
+++ b/pkg/cue/model/sets/utils.go
@@ -70,6 +70,103 @@ func lookUp(node ast.Node, paths ...string) (ast.Node, error) {
 	return nil, notFoundErr
 }
 
+func lookUpAll(node ast.Node, paths ...string) []ast.Node {
+	if len(paths) == 0 {
+		return []ast.Node{node}
+	}
+	key := paths[0]
+	var nodes []ast.Node
+	switch x := node.(type) {
+	case *ast.File:
+		for _, decl := range x.Decls {
+			nnode := lookField(decl, key)
+			if nnode != nil {
+				nodes = append(nodes, lookUpAll(nnode, paths[1:]...)...)
+			}
+		}
+
+	case *ast.StructLit:
+		for _, elt := range x.Elts {
+			nnode := lookField(elt, key)
+			if nnode != nil {
+				nodes = append(nodes, lookUpAll(nnode, paths[1:]...)...)
+			}
+		}
+	case *ast.ListLit:
+		for index, elt := range x.Elts {
+			if strconv.Itoa(index) == key {
+				return lookUpAll(elt, paths[1:]...)
+			}
+		}
+	}
+	return nodes
+}
+
+// PreprocessBuiltinFunc preprocess builtin function in cue file.
+func PreprocessBuiltinFunc(root ast.Node, name string, process func(values []ast.Node) (ast.Expr, error)) error {
+	var gerr error
+	ast.Walk(root, func(node ast.Node) bool {
+		switch v := node.(type) {
+		case *ast.EmbedDecl:
+			if fname, args := extractFuncName(v.Expr); fname == name && len(args) > 0 {
+				expr, err := doBuiltinFunc(root, args[0], process)
+				if err != nil {
+					gerr = err
+					return false
+				}
+				v.Expr = expr
+			}
+		case *ast.Field:
+			if fname, args := extractFuncName(v.Value); fname == name && len(args) > 0 {
+				expr, err := doBuiltinFunc(root, args[0], process)
+				if err != nil {
+					gerr = err
+					return false
+				}
+				v.Value = expr
+			}
+		}
+		return true
+	}, nil)
+	return gerr
+}
+
+func doBuiltinFunc(root ast.Node, pathSel ast.Expr, do func(values []ast.Node) (ast.Expr, error)) (ast.Expr, error) {
+	paths := getPaths(pathSel)
+	if len(paths) == 0 {
+		return nil, errors.New("path resolve error")
+	}
+	values := lookUpAll(root, paths...)
+	return do(values)
+}
+
+func extractFuncName(expr ast.Expr) (string, []ast.Expr) {
+	if call, ok := expr.(*ast.CallExpr); ok && len(call.Args) > 0 {
+		if ident, ok := call.Fun.(*ast.Ident); ok {
+			return ident.Name, call.Args
+		}
+	}
+	return "", nil
+}
+
+func getPaths(node ast.Expr) []string {
+	switch v := node.(type) {
+	case *ast.SelectorExpr:
+		return append(getPaths(v.X), v.Sel.Name)
+	case *ast.Ident:
+		return []string{v.Name}
+	case *ast.BasicLit:
+		s, err := literal.Unquote(v.Value)
+		if err != nil {
+			return nil
+		}
+		return []string{s}
+	case *ast.IndexExpr:
+		return append(getPaths(v.X), getPaths(v.Index)...)
+	}
+	return nil
+}
+
 func peelCloseExpr(node ast.Node) ast.Node {
 	x, ok := node.(*ast.CallExpr)
 	if !ok {
diff --git a/pkg/cue/model/value/value.go b/pkg/cue/model/value/value.go
index c4f7bfdd..d579205a 100644
--- a/pkg/cue/model/value/value.go
+++ b/pkg/cue/model/value/value.go
@@ -25,6 +25,7 @@ import (
 	"cuelang.org/go/cue"
 	"cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/build"
+	"cuelang.org/go/cue/literal"
 	"cuelang.org/go/cue/parser"
 	"github.com/pkg/errors"
 
@@ -71,7 +72,7 @@ func (val *Value) UnmarshalTo(x interface{}) error {
 }
 
 // NewValue new a value
-func NewValue(s string, pd *packages.PackageDiscover, opts ...func(*ast.File)) (*Value, error) {
+func NewValue(s string, pd *packages.PackageDiscover, opts ...func(*ast.File) error) (*Value, error) {
 	builder := &build.Instance{}
 
 	file, err := parser.ParseFile("-", s, parser.ParseComments)
@@ -79,7 +80,9 @@ func NewValue(s string, pd *packages.PackageDiscover, opts ...func(*ast.File)) (
 		return nil, err
 	}
 	for _, opt := range opts {
-		opt(file)
+		if err := opt(file); err != nil {
+			return nil, err
+		}
 	}
 	if err := builder.AddSyntax(file); err != nil {
 		return nil, err
@@ -104,7 +107,7 @@ func NewValue(s string, pd *packages.PackageDiscover, opts ...func(*ast.File)) (
 }
 
 // TagFieldOrder add step tag.
-func TagFieldOrder(root *ast.File) {
+func TagFieldOrder(root *ast.File) error {
 	i := 0
 	vs := &visitor{
 		r: map[string]struct{}{},
@@ -112,6 +115,28 @@ func TagFieldOrder(root *ast.File) {
 	for _, decl := range root.Decls {
 		vs.addAttrForExpr(decl, &i)
 	}
+	return nil
+}
+
+// ProcessScript preprocess the script builtin function.
+func ProcessScript(root *ast.File) error {
+	return sets.PreprocessBuiltinFunc(root, "script", func(values []ast.Node) (ast.Expr, error) {
+		for _, v := range values {
+			lit, ok := v.(*ast.BasicLit)
+			if ok {
+				src, err := literal.Unquote(lit.Value)
+				if err != nil {
+					return nil, errors.WithMessage(err, "unquote script value")
+				}
+				expr, err := parser.ParseExpr("-", src)
+				if err != nil {
+					return nil, errors.Errorf("script value(%s) is invalid CueLang", src)
+				}
+				return expr, nil
+			}
+		}
+		return nil, errors.New("script parameter error")
+	})
 }
 
 type visitor struct {
diff --git a/pkg/workflow/tasks/custom/task.go b/pkg/workflow/tasks/custom/task.go
index 9d43ddb2..bba120ed 100644
--- a/pkg/workflow/tasks/custom/task.go
+++ b/pkg/workflow/tasks/custom/task.go
@@ -105,7 +105,7 @@ func (t *TaskLoader) makeTaskGenerator(templ string) (wfTypes.TaskGenerator, err
 			}
 
 			for _, input := range inputs {
-				inputValue, err := ctx.GetVar(input.From)
+				inputValue, err := ctx.GetVar(strings.Split(input.From, ".")...)
 				if err != nil {
 					return common.WorkflowStepStatus{}, nil, errors.WithMessagef(err, "get input from [%s]", input.From)
 				}
@@ -141,7 +141,7 @@ func (t *TaskLoader) makeTaskGenerator(templ string) (wfTypes.TaskGenerator, err
 
 			if exec.status().Phase == common.WorkflowStepPhaseSucceeded {
 				for _, output := range outputs {
-					v, err := taskv.LookupValue(output.ExportKey)
+					v, err := taskv.LookupValue(strings.Split(output.ExportKey, ".")...)
 					if err != nil {
 						exec.err(err, StatusReasonOutput)
 						return exec.status(), exec.operation(), nil
@@ -168,7 +168,7 @@ func (t *TaskLoader) makeValue(ctx wfContext.Context, templ string) (*value.Valu
 		}
 		templ += fmt.Sprintf("\ncontext: {%s}", ms)
 	}
-	return value.NewValue(templ, t.pd, value.TagFieldOrder)
+	return value.NewValue(templ, t.pd, value.ProcessScript, value.TagFieldOrder)
 }
 
 type executor struct {
diff --git a/pkg/workflow/tasks/discover.go b/pkg/workflow/tasks/discover.go
index 362eba27..db594b90 100644
--- a/pkg/workflow/tasks/discover.go
+++ b/pkg/workflow/tasks/discover.go
@@ -26,6 +26,7 @@ import (
 	"github.com/oam-dev/kubevela/pkg/cue/packages"
 	wfContext "github.com/oam-dev/kubevela/pkg/workflow/context"
 	"github.com/oam-dev/kubevela/pkg/workflow/providers"
+	"github.com/oam-dev/kubevela/pkg/workflow/providers/http"
 	"github.com/oam-dev/kubevela/pkg/workflow/providers/workspace"
 	"github.com/oam-dev/kubevela/pkg/workflow/tasks/custom"
 	"github.com/oam-dev/kubevela/pkg/workflow/types"
@@ -69,7 +70,7 @@ func suspend(step v1beta1.WorkflowStep) (types.TaskRunner, error) {
 func NewTaskDiscover(providerHandlers providers.Providers, pd *packages.PackageDiscover, loadTemplate custom.LoadTaskTemplate) types.TaskDiscover {
 	// install builtin provider
 	workspace.Install(providerHandlers)
-
+	http.Install(providerHandlers)
 	return &taskDiscover{
 		builtins: map[string]types.TaskGenerator{
 			"suspend": suspend,



Test patch:
diff --git a/pkg/cue/model/sets/utils_test.go b/pkg/cue/model/sets/utils_test.go
index 19b7c453..060fb2df 100644
--- a/pkg/cue/model/sets/utils_test.go
+++ b/pkg/cue/model/sets/utils_test.go
@@ -19,6 +19,10 @@ import (
 	"testing"
 
 	"cuelang.org/go/cue"
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/literal"
+	"cuelang.org/go/cue/parser"
+	"github.com/pkg/errors"
 	"gotest.tools/assert"
 )
 
@@ -131,3 +135,105 @@ lacy: """
 		assert.Equal(t, str, tcase.expected)
 	}
 }
+
+func TestPreprocessBuiltinFunc(t *testing.T) {
+
+	doScript := func(values []ast.Node) (ast.Expr, error) {
+		for _, v := range values {
+			lit, ok := v.(*ast.BasicLit)
+			if ok {
+				src, _ := literal.Unquote(lit.Value)
+				expr, err := parser.ParseExpr("-", src)
+				if err != nil {
+					return nil, errors.Errorf("script value(%s) format err", src)
+				}
+				return expr, nil
+			}
+		}
+		return nil, errors.New("script parameter")
+	}
+
+	testCases := []struct {
+		src        string
+		expectJson string
+	}{
+		{
+			src: `
+a: "a"
+b: "b"
+c: script(a)
+`,
+			expectJson: `{"a":"a","b":"b","c":"a"}`,
+		},
+		{
+			src: `
+parameter: {
+ continue: "true"
+}
+
+wait: {
+ continue: script(parameter.continue)
+}
+
+`,
+			expectJson: `{"parameter":{"continue":"true"},"wait":{"continue":true}}`,
+		},
+		{
+			src: `
+parameter: {
+ continue: "_status"
+}
+
+wait: {
+ _status: true 
+ continue: script(parameter.continue)
+}
+
+`,
+			expectJson: `{"parameter":{"continue":"_status"},"wait":{"continue":true}}`,
+		},
+		{
+			src: `
+parameter: {
+ continue: "_status"
+}
+
+wait: {
+ _status: true 
+ if parameter.continue!=_|_{
+	continue: script(parameter["continue"])
+ }
+}
+
+`,
+			expectJson: `{"parameter":{"continue":"_status"},"wait":{"continue":true}}`,
+		},
+		{
+			src: `
+parameter: {
+ continue: "_status"
+}
+
+wait: {
+ _status: {
+   x: "abc"
+ }
+ script(parameter["continue"])
+}
+`,
+			expectJson: `{"parameter":{"continue":"_status"},"wait":{"x":"abc"}}`,
+		},
+	}
+
+	var r cue.Runtime
+	for _, tCase := range testCases {
+		f, err := parser.ParseFile("-", tCase.src)
+		assert.NilError(t, err)
+		err = PreprocessBuiltinFunc(f, "script", doScript)
+		assert.NilError(t, err)
+		inst, err := r.CompileFile(f)
+		assert.NilError(t, err)
+		bt, _ := inst.Value().MarshalJSON()
+		assert.Equal(t, string(bt), tCase.expectJson)
+	}
+}
diff --git a/pkg/cue/model/value/value_test.go b/pkg/cue/model/value/value_test.go
index f20ca08a..5efd52e3 100644
--- a/pkg/cue/model/value/value_test.go
+++ b/pkg/cue/model/value/value_test.go
@@ -511,3 +511,66 @@ bottom: _|_
 	_, err = val.Field("bottom")
 	assert.Equal(t, err != nil, true)
 }
+
+func TestProcessScript(t *testing.T) {
+	testCases := []struct {
+		src    string
+		expect string
+		err    string
+	}{
+		{
+			src: `parameter: {
+ check: "status==\"ready\""
+}
+
+wait: {
+ status: "ready"
+ continue: script(parameter.check)
+}`,
+			expect: `parameter: {
+	check: "status==\"ready\""
+}
+wait: {
+	status:   "ready"
+	continue: true
+}
+`,
+		},
+		{
+			src: `parameter: {
+ check: "status==\"ready\""
+}
+
+wait: {
+ status: "ready"
+ continue: script("")
+}`,
+			expect: ``,
+			err:    "script parameter error",
+		},
+		{
+			src: `parameter: {
+ check: "status=\"ready\""
+}
+
+wait: {
+ status: "ready"
+ continue: script(parameter.check)
+}`,
+			expect: ``,
+			err:    "script value(status=\"ready\") is invalid CueLang",
+		},
+	}
+
+	for _, tCase := range testCases {
+		v, err := NewValue(tCase.src, nil, ProcessScript)
+		if tCase.err != "" {
+			assert.Equal(t, err.Error(), tCase.err)
+			continue
+		}
+		assert.NilError(t, err)
+		s, err := v.String()
+		assert.NilError(t, err)
+		assert.Equal(t, s, tCase.expect)
+	}
+}
diff --git a/pkg/workflow/tasks/custom/task_test.go b/pkg/workflow/tasks/custom/task_test.go
index e0941a37..d00dc5b7 100644
--- a/pkg/workflow/tasks/custom/task_test.go
+++ b/pkg/workflow/tasks/custom/task_test.go
@@ -43,7 +43,7 @@ func TestTaskLoader(t *testing.T) {
 	discover.Register("test", map[string]providers.Handler{
 		"output": func(ctx wfContext.Context, v *value.Value, act types.Action) error {
 			ip, _ := v.MakeValue(`
-myIP: "1.1.1.1"            
+myIP: value: "1.1.1.1"            
 `)
 			return v.FillObject(ip)
 		},
@@ -78,7 +78,7 @@ myIP: "1.1.1.1"
 			Name: "output",
 			Type: "output",
 			Outputs: v1beta1.StepOutputs{{
-				ExportKey: "myIP",
+				ExportKey: "myIP.value",
 				Name:      "podIP",
 			}},
 		},