Repository: kubevela/kubevela

Issue:
Allow specifying a Terraform component's referenced Provider in an application 
Currently, the Provider which is linked to a Configuration is expressed in ComponentlDefinition, like Azure MariaDB.

```
apiVersion: core.oam.dev/v1alpha2
kind: ComponentDefinition
metadata:
  name: azure-database-mariadb
  ...
spec:
  workload:
    definition:
      apiVersion: terraform.core.oam.dev/v1beta1
      kind: Configuration
  schematic:
    terraform:
      configuration: |
        provider "azurerm" {
          features {}
        }
        ...

      providerRef:
        name: azure
        namespace: default

```

In a Component of an application, the Provider could not be specified. For example, the specified Provider `azure-2` won't take effect.

```
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: mariadb-backend
spec:
  components:
    - name: mariadb-backend
      type: azure-database-mariadb
      properties:
        ...
        providerRef:
          name: azure-2
          namespace: default
```

We made the design based on these reasons:

- A Configuration needs a Provider
Every Configuration needs a Provider which represents a cloud provider, like Azure.

- End-users doesn't need to under the concept `Provider`
All they need to do is to enable addon `terraform/provider-azure` with Azure credentials and set the properties for the Terraform component.


But here are some drawbacks:

- The Provider name of a Configuration is hardcoded with the name of the addon `terraform/provider-xxx`
For example, when enabling addon `terraform/provider-azure`, we set the Provider name to `azure`, and in the ComponentDefinition `ComponentDefinition`, we hardcoded Provider name as `azure`.

- An Application could not use multiple Providers to group cloud resources by users
We intend to support the multiple-tenant feature, in which, cloud resources provisioned are grouped in different tenants, ie, the users behind different Providers.

![image](https://user-images.githubusercontent.com/2805315/139667718-7c117253-c366-44ca-bb35-d4fad1b9214f.png)



I am proposing the different changes:

- Support users to set the name and namespace of a Provider when enabling addon `terraform/provider-xxx`

- Support users to specify a referenced Provider in a Terraform component in an application
The Provider in the application will override the one set in the ComponentDefinition of the component.






Hints from issue comments:
@chivalryq: This make lots of sense. seems not hard to implement in addon@zzxwill: > This make lots of sense. seems not hard to implement in addon

Great. I will implement this feature after your work:)@zzxwill: >I am proposing the different changes:

>Support users to set the name and namespace of a Provider when enabling addon terraform/provider-xxx

I am working on it myself.@zzxwill: This feature is lost and we should implement it based on the new architecture of Addon.@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/Makefile b/Makefile
index 8ddeb519..f143affd 100644
--- a/Makefile
+++ b/Makefile
@@ -273,7 +273,7 @@ core-uninstall: manifests
 	kubectl delete -f charts/vela-core/crds/
 
 # Generate manifests e.g. CRD, RBAC etc.
-manifests: installcue kustomize
+manifests: installcue kustomize addon
 	go generate $(foreach t,pkg apis,./$(t)/...)
 	# TODO(yangsoon): kustomize will merge all CRD into a whole file, it may not work if we want patch more than one CRD in this way
 	$(KUSTOMIZE) build config/crd -o config/crd/base/core.oam.dev_applications.yaml
@@ -281,7 +281,6 @@ manifests: installcue kustomize
 	go run ./hack/crd/dispatch/dispatch.go config/crd/base charts/vela-core/crds charts/oam-runtime/crds runtime/ charts/vela-minimal/crds
 	rm -f config/crd/base/*
 	./vela-templates/gen_definitions.sh
-	go run ./vela-templates/gen_addons.go
 
 GOLANGCILINT_VERSION ?= v1.38.0
 HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]')
@@ -365,3 +364,7 @@ check-license-header:
 
 def-install:
 	./hack/utils/installdefinition.sh
+
+# generate addons to auto-gen and charts
+addon:
+	go run ./vela-templates/gen_addons.go
diff --git a/charts/vela-core/templates/addons/terraform-provider-alibaba.yaml b/charts/vela-core/templates/addons/terraform-provider-alibaba.yaml
index 33520c91..6bcbfe6f 100644
--- a/charts/vela-core/templates/addons/terraform-provider-alibaba.yaml
+++ b/charts/vela-core/templates/addons/terraform-provider-alibaba.yaml
@@ -11,12 +11,12 @@ data:
       namespace: vela-system
     spec:
       components:
-      - name: alibaba-account-creds
+      - name: alibaba-account-creds-[[ index .Args "providerName" ]]
         properties:
           apiVersion: v1
           kind: Secret
           metadata:
-            name: alibaba-account-creds
+            name: alibaba-account-creds-[[ index .Args "providerName" ]]
             namespace: vela-system
           stringData:
             credentials: |
@@ -25,18 +25,18 @@ data:
               securityToken: [[ index .Args "ALICLOUD_SECURITY_TOKEN" ]]
           type: Opaque
         type: raw
-      - name: default
+      - name: alibaba-[[ index .Args "providerName" ]]
         properties:
           apiVersion: terraform.core.oam.dev/v1beta1
           kind: Provider
           metadata:
-            name: default
+            name: '[[ index .Args "providerName" ]]'
             namespace: default
           spec:
             credentials:
               secretRef:
                 key: credentials
-                name: alibaba-account-creds
+                name: alibaba-account-creds-[[ index .Args "providerName" ]]
                 namespace: vela-system
               source: Secret
             provider: alibaba
diff --git a/e2e/commonContext.go b/e2e/commonContext.go
index ea9f3793..9f765c2a 100644
--- a/e2e/commonContext.go
+++ b/e2e/commonContext.go
@@ -127,7 +127,7 @@ var (
 				cli := fmt.Sprintf("vela delete %s", applicationName)
 				output, err := Exec(cli)
 				gomega.Expect(err).NotTo(gomega.HaveOccurred())
-				gomega.Expect(output).To(gomega.ContainSubstring("already deleted"))
+				gomega.Expect(output).To(gomega.ContainSubstring("deleted from namespace"))
 			})
 		})
 	}
diff --git a/go.mod b/go.mod
index ce96d954..58178793 100644
--- a/go.mod
+++ b/go.mod
@@ -32,6 +32,7 @@ require (
 	github.com/google/go-cmp v0.5.6
 	github.com/google/go-github/v32 v32.1.0
 	github.com/google/uuid v1.1.2
+	github.com/gosuri/uilive v0.0.4
 	github.com/gosuri/uitable v0.0.4
 	github.com/hashicorp/hcl/v2 v2.9.1
 	github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174
diff --git a/go.sum b/go.sum
index b30343b0..04aa6618 100644
--- a/go.sum
+++ b/go.sum
@@ -857,6 +857,8 @@ github.com/gostaticanalysis/comment v1.3.0/go.mod h1:xMicKDx7XRXYdVwY9f9wQpDJVnq
 github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado=
 github.com/gostaticanalysis/forcetypeassert v0.0.0-20200621232751-01d4955beaa5/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak=
 github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A=
+github.com/gosuri/uilive v0.0.4 h1:hUEBpQDj8D8jXgtCdBu7sWsy5sbW/5GhuO8KBwJ2jyY=
+github.com/gosuri/uilive v0.0.4/go.mod h1:V/epo5LjjlDE5RJUcqx8dbw+zc93y5Ya3yg8tfZ74VI=
 github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
 github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
 github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
diff --git a/pkg/addon/addon.go b/pkg/addon/addon.go
index 0157cba8..b3ba2a2e 100644
--- a/pkg/addon/addon.go
+++ b/pkg/addon/addon.go
@@ -23,6 +23,7 @@ import (
 	"net/url"
 	"path"
 	"path/filepath"
+	"strconv"
 	"strings"
 	"sync"
 	"time"
@@ -457,10 +458,7 @@ func cutPathUntil(path []string, end string) ([]string, error) {
 }
 
 // RenderApplication render a K8s application
-func RenderApplication(addon *types.Addon, args map[string]string) (*v1beta1.Application, []*unstructured.Unstructured, error) {
-	if args == nil {
-		args = map[string]string{}
-	}
+func RenderApplication(addon *types.Addon, args map[string]interface{}) (*v1beta1.Application, []*unstructured.Unstructured, error) {
 	app := addon.AppTemplate
 	if app == nil {
 		app = &v1beta1.Application{
@@ -582,7 +580,7 @@ func renderRawComponent(elem types.AddonElementFile) (*common2.ApplicationCompon
 }
 
 // renderCUETemplate will return a component from cue template
-func renderCUETemplate(elem types.AddonElementFile, parameters string, args map[string]string) (*common2.ApplicationComponent, error) {
+func renderCUETemplate(elem types.AddonElementFile, parameters string, args map[string]interface{}) (*common2.ApplicationComponent, error) {
 	bt, err := json.Marshal(args)
 	if err != nil {
 		return nil, err
@@ -596,7 +594,7 @@ func renderCUETemplate(elem types.AddonElementFile, parameters string, args map[
 	if err != nil {
 		return nil, err
 	}
-	out, err := v.LookupByScript(fmt.Sprintf("{%s}", elem.Data))
+	out, err := v.LookupByScript(elem.Data)
 	if err != nil {
 		return nil, err
 	}
@@ -633,14 +631,23 @@ func Convert2AddonName(name string) string {
 }
 
 // RenderArgsSecret TODO add desc
-func RenderArgsSecret(addon *types.Addon, args map[string]string) *v1.Secret {
+func RenderArgsSecret(addon *types.Addon, args map[string]interface{}) *v1.Secret {
+	data := make(map[string]string)
+	for k, v := range args {
+		switch v := v.(type) {
+		case bool:
+			data[k] = strconv.FormatBool(v)
+		default:
+			data[k] = fmt.Sprintf("%v", v)
+		}
+	}
 	sec := v1.Secret{
 		TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Secret"},
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      Convert2SecName(addon.Name),
 			Namespace: types.DefaultKubeVelaNS,
 		},
-		StringData: args,
+		StringData: data,
 		Type:       v1.SecretTypeOpaque,
 	}
 	return &sec
diff --git a/pkg/apiserver/rest/apis/v1/types.go b/pkg/apiserver/rest/apis/v1/types.go
index 1d2ce941..3590483c 100644
--- a/pkg/apiserver/rest/apis/v1/types.go
+++ b/pkg/apiserver/rest/apis/v1/types.go
@@ -83,7 +83,7 @@ type ListAddonRegistryResponse struct {
 // EnableAddonRequest defines the format for enable addon request
 type EnableAddonRequest struct {
 	// Args is the key-value environment variables, e.g. AK/SK credentials.
-	Args map[string]string `json:"args,omitempty"`
+	Args map[string]interface{} `json:"args,omitempty"`
 }
 
 // ListAddonResponse defines the format for addon list response
@@ -624,11 +624,12 @@ type DetailWorkflowRecordResponse struct {
 
 // WorkflowRecord workflow record
 type WorkflowRecord struct {
-	Name      string                      `json:"name"`
-	Namespace string                      `json:"namespace"`
-	StartTime time.Time                   `json:"startTime,omitempty"`
-	Status    string                      `json:"status"`
-	Steps     []common.WorkflowStepStatus `json:"steps,omitempty"`
+	Name         string                      `json:"name"`
+	Namespace    string                      `json:"namespace"`
+	WorkflowName string                      `json:"workflowName"`
+	StartTime    time.Time                   `json:"startTime,omitempty"`
+	Status       string                      `json:"status"`
+	Steps        []common.WorkflowStepStatus `json:"steps,omitempty"`
 }
 
 // ApplicationDeployRequest the application deploy or update event request
diff --git a/pkg/apiserver/rest/usecase/definition.go b/pkg/apiserver/rest/usecase/definition.go
index bd32c48e..1d5739b0 100644
--- a/pkg/apiserver/rest/usecase/definition.go
+++ b/pkg/apiserver/rest/usecase/definition.go
@@ -287,7 +287,7 @@ func renderUIParameter(key, label string, property *openapi3.SchemaRef, required
 	parameter.JSONKey = key
 	parameter.Description = property.Value.Description
 	parameter.Label = label
-	parameter.UIType = utils.GetDefaultUIType(property.Value.Type, len(parameter.Validate.Options) != 0, subType)
+	parameter.UIType = utils.GetDefaultUIType(property.Value.Type, len(parameter.Validate.Options) != 0, subType, len(property.Value.Properties) > 0)
 	parameter.Validate.Max = property.Value.Max
 	parameter.Validate.MaxLength = property.Value.MaxLength
 	parameter.Validate.Min = property.Value.Min
diff --git a/pkg/apiserver/rest/usecase/envbinding.go b/pkg/apiserver/rest/usecase/envbinding.go
index af70161c..37c02c5c 100644
--- a/pkg/apiserver/rest/usecase/envbinding.go
+++ b/pkg/apiserver/rest/usecase/envbinding.go
@@ -201,7 +201,7 @@ func (e *envBindingUsecaseImpl) DeleteEnvBinding(ctx context.Context, appModel *
 		return err
 	}
 	// delete env workflow
-	if err := e.deleteEnvWorkflow(ctx, appModel, envBinding.Name); err != nil {
+	if err := e.deleteEnvWorkflow(ctx, appModel, convertWorkflowName(envBinding.Name)); err != nil {
 		return err
 	}
 	return nil
@@ -218,7 +218,7 @@ func (e *envBindingUsecaseImpl) BatchDeleteEnvBinding(ctx context.Context, app *
 			return err
 		}
 		// delete env workflow
-		err := e.deleteEnvWorkflow(ctx, app, envBinding.Name)
+		err := e.deleteEnvWorkflow(ctx, app, convertWorkflowName(envBinding.Name))
 		if err != nil {
 			return err
 		}
diff --git a/pkg/apiserver/rest/usecase/workflow.go b/pkg/apiserver/rest/usecase/workflow.go
index b7ccbaa3..232e5ef8 100644
--- a/pkg/apiserver/rest/usecase/workflow.go
+++ b/pkg/apiserver/rest/usecase/workflow.go
@@ -640,11 +640,12 @@ func (w *workflowUsecaseImpl) checkRecordRunning(ctx context.Context, appModel *
 
 func convertFromRecordModel(record *model.WorkflowRecord) *apisv1.WorkflowRecord {
 	return &apisv1.WorkflowRecord{
-		Name:      record.Name,
-		Namespace: record.Namespace,
-		StartTime: record.StartTime,
-		Status:    record.Status,
-		Steps:     record.Steps,
+		Name:         record.Name,
+		Namespace:    record.Namespace,
+		WorkflowName: record.WorkflowName,
+		StartTime:    record.StartTime,
+		Status:       record.Status,
+		Steps:        record.Steps,
 	}
 }
 
diff --git a/pkg/apiserver/rest/utils/uiswagger.go b/pkg/apiserver/rest/utils/uiswagger.go
index c635382d..656c0f29 100644
--- a/pkg/apiserver/rest/utils/uiswagger.go
+++ b/pkg/apiserver/rest/utils/uiswagger.go
@@ -83,7 +83,7 @@ func FirstLower(s string) string {
 }
 
 // GetDefaultUIType Set the default mapping for API Schema Type
-func GetDefaultUIType(apiType string, haveOptions bool, subType string) string {
+func GetDefaultUIType(apiType string, haveOptions bool, subType string, haveSub bool) string {
 	switch apiType {
 	case "string":
 		if haveOptions {
@@ -103,6 +103,9 @@ func GetDefaultUIType(apiType string, haveOptions bool, subType string) string {
 		}
 		return "Structs"
 	case "object":
+		if haveSub {
+			return "Group"
+		}
 		return "KV"
 	default:
 		return "Input"
diff --git a/pkg/apiserver/rest/webservice/addon.go b/pkg/apiserver/rest/webservice/addon.go
index 506816f7..333b6ed7 100644
--- a/pkg/apiserver/rest/webservice/addon.go
+++ b/pkg/apiserver/rest/webservice/addon.go
@@ -137,7 +137,9 @@ func (s *addonWebService) detailAddon(req *restful.Request, res *restful.Respons
 
 func (s *addonWebService) enableAddon(req *restful.Request, res *restful.Response) {
 	var createReq apis.EnableAddonRequest
-	if req.Request.GetBody != nil {
+	var args []byte
+	_, err := req.Request.Body.Read(args)
+	if err == nil {
 		err := req.ReadEntity(&createReq)
 		if err != nil {
 			bcode.ReturnError(req, res, err)
@@ -150,7 +152,7 @@ func (s *addonWebService) enableAddon(req *restful.Request, res *restful.Respons
 	}
 
 	name := req.PathParameter("name")
-	err := s.addonUsecase.EnableAddon(req.Request.Context(), name, createReq)
+	err = s.addonUsecase.EnableAddon(req.Request.Context(), name, createReq)
 	if err != nil {
 		bcode.ReturnError(req, res, err)
 		return
diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/apply.go b/pkg/controller/core.oam.dev/v1alpha2/application/apply.go
index 03d4dc11..9bbdfa1c 100644
--- a/pkg/controller/core.oam.dev/v1alpha2/application/apply.go
+++ b/pkg/controller/core.oam.dev/v1alpha2/application/apply.go
@@ -173,7 +173,7 @@ func (h *AppHandler) addServiceStatus(cover bool, svcs ...common.ApplicationComp
 		found := false
 		for i := range h.services {
 			current := h.services[i]
-			if current.Name == svc.Name {
+			if current.Name == svc.Name && current.Env == svc.Env {
 				if cover {
 					h.services[i] = svc
 				}
diff --git a/pkg/controller/core.oam.dev/v1alpha2/core/scopes/healthscope/healthscope_controller.go b/pkg/controller/core.oam.dev/v1alpha2/core/scopes/healthscope/healthscope_controller.go
index f01b42f6..d2d3af00 100644
--- a/pkg/controller/core.oam.dev/v1alpha2/core/scopes/healthscope/healthscope_controller.go
+++ b/pkg/controller/core.oam.dev/v1alpha2/core/scopes/healthscope/healthscope_controller.go
@@ -188,7 +188,12 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	if err := r.patchHealthStatusToApplications(ctx, appConditions, hs); err != nil {
 		return reconcile.Result{}, errors.Wrap(err, "cannot patch health status to application")
 	}
-	return reconcile.Result{RequeueAfter: interval - elapsed}, errors.Wrap(r.UpdateStatus(ctx, hs), errUpdateHealthScopeStatus)
+
+	requeueAfter := interval - elapsed
+	if requeueAfter <= time.Second { // prevent underflow
+		requeueAfter = time.Second
+	}
+	return reconcile.Result{RequeueAfter: requeueAfter}, errors.Wrap(r.UpdateStatus(ctx, hs), errUpdateHealthScopeStatus)
 }
 
 // GetScopeHealthStatus get the status of the healthscope based on workload resources.
@@ -243,8 +248,12 @@ func (r *Reconciler) GetScopeHealthStatus(ctx context.Context, healthScope *v1al
 			)
 
 			subCtx := multicluster.ContextWithClusterName(ctxWithTimeout, resRef.clusterName)
+			ns := resRef.Namespace
+			if ns == "" {
+				ns = healthScope.GetNamespace()
+			}
 			if appfile, ok := appfiles[resRef]; ok {
-				wlHealthCondition, traitConditions = CUEBasedHealthCheck(subCtx, r.client, resRef, healthScope.GetNamespace(), appfile)
+				wlHealthCondition, traitConditions = CUEBasedHealthCheck(subCtx, r.client, resRef, ns, appfile)
 				if wlHealthCondition != nil {
 					klog.V(common.LogDebug).InfoS("Get health condition from CUE-based health check", "workload", resRef, "healthCondition", wlHealthCondition)
 					wlHealthCondition.Traits = traitConditions
@@ -257,7 +266,7 @@ func (r *Reconciler) GetScopeHealthStatus(ctx context.Context, healthScope *v1al
 				}
 			}
 
-			wlHealthCondition = r.traitChecker.Check(subCtx, r.client, resRef.ObjectReference, healthScope.GetNamespace())
+			wlHealthCondition = r.traitChecker.Check(subCtx, r.client, resRef.ObjectReference, ns)
 			if wlHealthCondition != nil {
 				klog.V(common.LogDebug).InfoS("Get health condition from health check trait ", "workload", resRef, "healthCondition", wlHealthCondition)
 				wlHealthCondition.Traits = traitConditions
@@ -270,7 +279,7 @@ func (r *Reconciler) GetScopeHealthStatus(ctx context.Context, healthScope *v1al
 			}
 
 			for _, checker := range r.checkers {
-				wlHealthCondition = checker.Check(subCtx, r.client, resRef.ObjectReference, healthScope.GetNamespace())
+				wlHealthCondition = checker.Check(subCtx, r.client, resRef.ObjectReference, ns)
 				if wlHealthCondition != nil {
 					klog.V(common.LogDebug).InfoS("Get health condition from built-in checker", "workload", resRef, "healthCondition", wlHealthCondition)
 					// found matched checker and get health condition
@@ -285,7 +294,7 @@ func (r *Reconciler) GetScopeHealthStatus(ctx context.Context, healthScope *v1al
 			}
 			// handle unknown workload
 			klog.V(common.LogDebug).InfoS("Get unknown workload", "workload", resRef)
-			wlHealthCondition = r.unknownChecker.Check(subCtx, r.client, resRef.ObjectReference, healthScope.GetNamespace())
+			wlHealthCondition = r.unknownChecker.Check(subCtx, r.client, resRef.ObjectReference, ns)
 			wlHealthCondition.Traits = traitConditions
 			wlHealthResultsC <- wlHealthResult{
 				name:    appInfos[resRef].appName,
@@ -352,8 +361,12 @@ func (r *Reconciler) CollectAppfilesAndAppNames(ctx context.Context, refs []Work
 		u := &unstructured.Unstructured{}
 		u.SetGroupVersionKind(ref.GroupVersionKind())
 
+		refNs := ref.Namespace
+		if refNs == "" {
+			refNs = ns
+		}
 		subCtx := multicluster.ContextWithClusterName(ctx, ref.clusterName)
-		if err := r.client.Get(subCtx, client.ObjectKey{Name: ref.Name, Namespace: ns}, u); err != nil {
+		if err := r.client.Get(subCtx, client.ObjectKey{Name: ref.Name, Namespace: refNs}, u); err != nil {
 			// no need to check error in this function
 			// HealthCheckFn  will handle all errors latter
 			continue
@@ -396,12 +409,7 @@ func (r *Reconciler) UpdateStatus(ctx context.Context, hs *v1alpha2.HealthScope,
 
 func (r *Reconciler) patchHealthStatusToApplications(ctx context.Context, appHealthConditions []*AppHealthCondition, hs *v1alpha2.HealthScope) error {
 	multiClusterAppCondition := make(map[string][]*AppHealthCondition)
-	for i := range appHealthConditions {
-		appHealth := appHealthConditions[i]
-		_, ok := multiClusterAppCondition[appHealth.AppName]
-		if !ok {
-			multiClusterAppCondition[appHealth.AppName] = make([]*AppHealthCondition, 0)
-		}
+	for _, appHealth := range appHealthConditions {
 		multiClusterAppCondition[appHealth.AppName] = append(multiClusterAppCondition[appHealth.AppName], appHealth)
 	}
 
@@ -414,6 +422,12 @@ func (r *Reconciler) patchHealthStatusToApplications(ctx context.Context, appHea
 		if err := r.client.Get(ctx, client.ObjectKey{Name: appName, Namespace: hs.Namespace}, app); err != nil {
 			return err
 		}
+		if app.Status.Workflow == nil {
+			continue
+		}
+		if !app.Status.Workflow.Finished && !app.Status.Workflow.Suspend {
+			continue
+		}
 		copyApp := app.DeepCopy()
 		componentPosition := make(map[string]int)
 		for i, comp := range app.Spec.Components {
@@ -435,6 +449,7 @@ func (r *Reconciler) patchHealthStatusToApplications(ctx context.Context, appHea
 				healthCondition,
 			})
 			compStatus = append(compStatus, constructAppCompStatus(healthCondition, hsRef)...)
+
 		}
 		app.Status.Services = compStatus
 		app.Status.SetConditions(condition.Condition{
@@ -544,7 +559,11 @@ func (r *Reconciler) createWorkloadRefs(ctx context.Context, appRef v1alpha2.App
 	if err == nil && policyStatus != nil {
 		for _, env := range policyStatus.Envs {
 			for _, placement := range env.Placements {
-				decisionsMap[placement.Cluster] = env.Env
+				if placement.Namespace != "" {
+					decisionsMap[placement.Cluster+"."+placement.Namespace] = env.Env
+				} else {
+					decisionsMap[placement.Cluster] = env.Env
+				}
 				decisions = append(decisions, struct {
 					Cluster string
 					Env     string
@@ -584,11 +603,17 @@ func (r *Reconciler) createWorkloadRefs(ctx context.Context, appRef v1alpha2.App
 				}
 
 				if labels := o.GetLabels(); labels != nil {
+					var envName string
+					if _envName, ok := decisionsMap[rs.Cluster+"."+rs.Namespace]; ok {
+						envName = _envName
+					} else {
+						envName = decisionsMap[rs.Cluster]
+					}
 					if labels[oam.WorkloadTypeLabel] != "" {
 						wlRefs = append(wlRefs, WorkloadReference{
 							ObjectReference: rs.ObjectReference,
 							clusterName:     rs.Cluster,
-							envName:         decisionsMap[rs.Cluster],
+							envName:         envName,
 						})
 					} else if labels[oam.TraitTypeLabel] != "" && labels[oam.LabelManageWorkloadTrait] == "true" {
 						// this means this trait is a manage-Workload trait, get workload GVK and name for trait's annotation
@@ -607,7 +632,7 @@ func (r *Reconciler) createWorkloadRefs(ctx context.Context, appRef v1alpha2.App
 						wlRefs = append(wlRefs, WorkloadReference{
 							ObjectReference: objectRef,
 							clusterName:     rs.Cluster,
-							envName:         decisionsMap[rs.Cluster],
+							envName:         envName,
 						})
 					}
 				}
diff --git a/pkg/cue/model/value/value.go b/pkg/cue/model/value/value.go
index b9d25fb0..35ee14de 100644
--- a/pkg/cue/model/value/value.go
+++ b/pkg/cue/model/value/value.go
@@ -207,6 +207,27 @@ func (val *Value) MakeValue(s string) (*Value, error) {
 	return v, nil
 }
 
+func (val *Value) makeValueWithFile(files ...*ast.File) (*Value, error) {
+	builder := &build.Instance{}
+	for _, f := range files {
+		if err := builder.AddSyntax(f); err != nil {
+			return nil, err
+		}
+	}
+	if err := val.addImports(builder); err != nil {
+		return nil, err
+	}
+	inst, err := val.r.Build(builder)
+	if err != nil {
+		return nil, err
+	}
+	v := new(Value)
+	v.r = val.r
+	v.v = inst.Value()
+	v.addImports = val.addImports
+	return v, nil
+}
+
 // FillRaw unify the value with the cue format string x at the given path.
 func (val *Value) FillRaw(x string, paths ...string) error {
 	xInst, err := val.r.Compile("-", x)
@@ -296,17 +317,63 @@ func (val *Value) LookupValue(paths ...string) (*Value, error) {
 func (val *Value) LookupByScript(script string) (*Value, error) {
 	var outputKey = "zz_output__"
 	script = strings.TrimSpace(script)
+	scriptFile, err := parser.ParseFile("-", script)
+	if err != nil {
+		return nil, errors.WithMessage(err, "parse script")
+	}
+
 	raw, err := val.String()
 	if err != nil {
 		return nil, err
 	}
-	raw += fmt.Sprintf("\n%s: %s", outputKey, script)
-	newV, err := val.MakeValue(raw)
+
+	rawFile, err := parser.ParseFile("-", raw)
+	if err != nil {
+		return nil, errors.WithMessage(err, "parse script")
+	}
+
+	behindKey(scriptFile, outputKey)
+
+	newV, err := val.makeValueWithFile(rawFile, scriptFile)
 	if err != nil {
 		return nil, err
 	}
+
 	return newV.LookupValue(outputKey)
 }
+func behindKey(file *ast.File, key string) {
+	var (
+		implDecls []ast.Decl
+		decls     []ast.Decl
+	)
+
+	for i, decl := range file.Decls {
+		if _, ok := decl.(*ast.ImportDecl); ok {
+			implDecls = append(implDecls, file.Decls[i])
+		} else {
+			decls = append(decls, file.Decls[i])
+		}
+	}
+
+	file.Decls = implDecls
+	if len(decls) == 1 {
+		target := decls[0]
+		if embed, ok := target.(*ast.EmbedDecl); ok {
+			file.Decls = append(file.Decls, &ast.Field{
+				Label: ast.NewIdent(key),
+				Value: embed.Expr,
+			})
+			return
+		}
+	}
+	file.Decls = append(file.Decls, &ast.Field{
+		Label: ast.NewIdent(key),
+		Value: &ast.StructLit{
+			Elts: decls,
+		},
+	})
+
+}
 
 type field struct {
 	Name  string
diff --git a/pkg/stdlib/pkgs/oam.cue b/pkg/stdlib/pkgs/oam.cue
index 51e9d15f..32455aea 100644
--- a/pkg/stdlib/pkgs/oam.cue
+++ b/pkg/stdlib/pkgs/oam.cue
@@ -30,3 +30,9 @@
 	value?: {...}
 	...
 }
+
+#LoadComponetsInOrder: {
+	#provider: "oam"
+	#do:       "load-comps-in-order"
+	...
+}
diff --git a/pkg/workflow/providers/oam/apply.go b/pkg/workflow/providers/oam/apply.go
index ad049476..dcbf95f2 100644
--- a/pkg/workflow/providers/oam/apply.go
+++ b/pkg/workflow/providers/oam/apply.go
@@ -172,6 +172,28 @@ func (p *provider) LoadComponent(ctx wfContext.Context, v *value.Value, act wfTy
 	return nil
 }
 
+// LoadComponentInOrder load component describe info in application output will be a list with order defined in application.
+func (p *provider) LoadComponentInOrder(ctx wfContext.Context, v *value.Value, act wfTypes.Action) error {
+	app := &v1beta1.Application{}
+	// if specify `app`, use specified application otherwise use default application fron provider
+	appSettings, err := v.LookupValue("app")
+	if err != nil {
+		if strings.Contains(err.Error(), "not exist") {
+			app = p.app
+		} else {
+			return err
+		}
+	} else {
+		if err := appSettings.UnmarshalTo(app); err != nil {
+			return err
+		}
+	}
+	if err := v.FillObject(app.Spec.Components, "value"); err != nil {
+		return err
+	}
+	return nil
+}
+
 // LoadPolicies load policy describe info in application.
 func (p *provider) LoadPolicies(ctx wfContext.Context, v *value.Value, act wfTypes.Action) error {
 	for _, po := range p.app.Spec.Policies {
@@ -190,9 +212,10 @@ func Install(p providers.Providers, app *v1beta1.Application, apply ComponentApp
 		app:    app.DeepCopy(),
 	}
 	p.Register(ProviderName, map[string]providers.Handler{
-		"component-render": prd.RenderComponent,
-		"component-apply":  prd.ApplyComponent,
-		"load":             prd.LoadComponent,
-		"load-policies":    prd.LoadPolicies,
+		"component-render":    prd.RenderComponent,
+		"component-apply":     prd.ApplyComponent,
+		"load":                prd.LoadComponent,
+		"load-policies":       prd.LoadPolicies,
+		"load-comps-in-order": prd.LoadComponentInOrder,
 	})
 }
diff --git a/references/cli/addon.go b/references/cli/addon.go
index 715b5007..b673deac 100644
--- a/references/cli/addon.go
+++ b/references/cli/addon.go
@@ -24,6 +24,9 @@ import (
 	"text/template"
 	"time"
 
+	terraformv1beta1 "github.com/oam-dev/terraform-controller/api/v1beta1"
+	kerrors "k8s.io/apimachinery/pkg/api/errors"
+
 	yaml2 "k8s.io/apimachinery/pkg/util/yaml"
 
 	"github.com/oam-dev/kubevela/pkg/oam/util"
@@ -55,6 +58,11 @@ const (
 
 	// DependsOnWorkFlowStepName is workflow step name which is used to check dependsOn app
 	DependsOnWorkFlowStepName = "depends-on-app"
+
+	// AddonTerraformProviderNamespace is the namespace of addon terraform provider
+	AddonTerraformProviderNamespace = "default"
+	// AddonTerraformProviderNameArgument is the argument name of addon terraform provider
+	AddonTerraformProviderNameArgument = "providerName"
 )
 
 var statusUninstalled = "uninstalled"
@@ -96,7 +104,7 @@ func NewAddonCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command
 	}
 	cmd.AddCommand(
 		NewAddonListCommand(),
-		NewAddonEnableCommand(ioStreams),
+		NewAddonEnableCommand(c, ioStreams),
 		NewAddonDisableCommand(ioStreams),
 	)
 	return cmd
@@ -120,13 +128,19 @@ func NewAddonListCommand() *cobra.Command {
 }
 
 // NewAddonEnableCommand create addon enable command
-func NewAddonEnableCommand(ioStream cmdutil.IOStreams) *cobra.Command {
+func NewAddonEnableCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Command {
+	ctx := context.Background()
 	return &cobra.Command{
 		Use:     "enable",
 		Short:   "enable an addon",
 		Long:    "enable an addon in cluster",
 		Example: "vela addon enable <addon-name>",
 		RunE: func(cmd *cobra.Command, args []string) error {
+			k8sClient, err := c.GetClient()
+			if err != nil {
+				return err
+			}
+
 			if len(args) < 1 {
 				return fmt.Errorf("must specify addon name")
 			}
@@ -135,7 +149,7 @@ func NewAddonEnableCommand(ioStream cmdutil.IOStreams) *cobra.Command {
 			if err != nil {
 				return err
 			}
-			err = enableAddon(name, addonArgs)
+			err = enableAddon(ctx, k8sClient, name, addonArgs)
 			if err != nil {
 				return err
 			}
@@ -198,17 +212,21 @@ func listAddons() error {
 	return nil
 }
 
-func enableAddon(name string, args map[string]string) error {
+func enableAddon(ctx context.Context, k8sClient client.Client, name string, args map[string]string) error {
 	repo, err := NewAddonRepo()
 	if err != nil {
 		return err
 	}
+
 	addon, err := repo.getAddon(name)
 	if err != nil {
 		return err
 	}
+	if strings.HasPrefix(name, "terraform-provider") {
+		args, _ = getTerraformProviderArgumentValue(name, args)
+	}
 	addon.setArgs(args)
-	err = addon.enable()
+	err = addon.enable(ctx, k8sClient, name, args)
 	return err
 }
 
@@ -358,14 +376,25 @@ func (a *Addon) renderApplication() (*v1beta1.Application, error) {
 	return a.application, nil
 }
 
-func (a *Addon) enable() error {
+func (a *Addon) enable(ctx context.Context, k8sClient client.Client, name string, args map[string]string) error {
 	applicator := apply.NewAPIApplicator(clt)
-	ctx := context.Background()
 	obj, err := a.renderApplication()
 	if err != nil {
 		return err
 	}
-	err = a.installDependsOn()
+
+	if strings.HasPrefix(name, "terraform/provider") {
+		providerName, existed, err := checkWhetherTerraformProviderExist(ctx, k8sClient, name, args)
+		if err != nil {
+			return err
+		}
+		if existed {
+			return errors.Errorf("terraform provider %s with name %s already exists", name, providerName)
+		}
+		obj.Name = fmt.Sprintf("%s-%s", obj.Name, providerName)
+	}
+
+	err = a.installDependsOn(ctx, k8sClient, args)
 	if err != nil {
 		return errors.Wrap(err, "Error occurs when install dependent addon")
 	}
@@ -427,7 +456,7 @@ func (a *Addon) setArgs(args map[string]string) {
 	a.Args = args
 }
 
-func (a *Addon) installDependsOn() error {
+func (a *Addon) installDependsOn(ctx context.Context, k8sClient client.Client, args map[string]string) error {
 	if a.application.Spec.Workflow == nil || a.application.Spec.Workflow.Steps == nil {
 		return nil
 	}
@@ -448,7 +477,7 @@ func (a *Addon) installDependsOn() error {
 				return err
 			}
 			if addon.getStatus() != statusInstalled {
-				err = addon.enable()
+				err = addon.enable(ctx, k8sClient, dependsOnAddonName, args)
 				if err != nil {
 					return err
 				}
@@ -462,3 +491,51 @@ func (a *Addon) installDependsOn() error {
 func TransAddonName(name string) string {
 	return strings.ReplaceAll(name, "/", "-")
 }
+
+func getTerraformProviderNames(ctx context.Context, k8sClient client.Client) ([]string, error) {
+	var names []string
+	providerList := &terraformv1beta1.ProviderList{}
+	err := k8sClient.List(ctx, providerList, client.InNamespace(AddonTerraformProviderNamespace))
+	if err != nil {
+		if kerrors.IsNotFound(err) {
+			return nil, err
+		}
+		return nil, err
+	}
+	for _, provider := range providerList.Items {
+		names = append(names, provider.Name)
+	}
+	return names, nil
+}
+
+// Get the value of argument AddonTerraformProviderNameArgument
+func getTerraformProviderArgumentValue(addonName string, args map[string]string) (map[string]string, string) {
+	providerName, ok := args[AddonTerraformProviderNameArgument]
+	if !ok {
+		switch addonName {
+		case "terraform-provider-alibaba":
+			providerName = "default"
+		case "terraform-provider-aws":
+			providerName = "aws"
+		case "terraform-provider-azure":
+			providerName = "azure"
+		}
+		args[AddonTerraformProviderNameArgument] = providerName
+	}
+	return args, providerName
+}
+
+func checkWhetherTerraformProviderExist(ctx context.Context, k8sClient client.Client, addonName string, args map[string]string) (string, bool, error) {
+	_, providerName := getTerraformProviderArgumentValue(addonName, args)
+
+	providerNames, err := getTerraformProviderNames(ctx, k8sClient)
+	if err != nil {
+		return "", false, err
+	}
+	for _, name := range providerNames {
+		if providerName == name {
+			return providerName, true, nil
+		}
+	}
+	return providerName, false, nil
+}
diff --git a/references/cli/cluster.go b/references/cli/cluster.go
index f95b61a6..fd351120 100644
--- a/references/cli/cluster.go
+++ b/references/cli/cluster.go
@@ -59,6 +59,8 @@ const (
 	ClusterGateWayClusterManagement = "cluster-gateway"
 	// OCMClusterManagement ocm cluster management solution
 	OCMClusterManagement = "ocm"
+	// CreateNamespace specifies the namespace need to create in managedCluster
+	CreateNamespace = "create-namespace"
 )
 
 // ClusterCommandGroup create a group of cluster command
@@ -155,6 +157,20 @@ func ensureResourceTrackerCRDInstalled(c client.Client, clusterName string) erro
 	return nil
 }
 
+func ensureVelaSystemNamespaceInstalled(c client.Client, clusterName string, createNamespace string) error {
+	ctx := context.Background()
+	remoteCtx := multicluster.ContextWithClusterName(ctx, clusterName)
+	if err := c.Get(remoteCtx, types2.NamespacedName{Name: createNamespace}, &v1.Namespace{}); err != nil {
+		if !errors2.IsNotFound(err) {
+			return errors.Wrapf(err, "failed to check vela-system ")
+		}
+		if err = c.Create(remoteCtx, &v1.Namespace{ObjectMeta: v12.ObjectMeta{Name: createNamespace}}); err != nil {
+			return errors.Wrapf(err, "failed to create vela-system namespace")
+		}
+	}
+	return nil
+}
+
 // NewClusterJoinCommand create command to help user join cluster to multicluster management
 func NewClusterJoinCommand(c *common.Args) *cobra.Command {
 	cmd := &cobra.Command{
@@ -205,6 +221,16 @@ func NewClusterJoinCommand(c *common.Args) *cobra.Command {
 				clusterManagementType = ClusterGateWayClusterManagement
 			}
 
+			// get need created namespace in managed cluster
+			createNamespace, err := cmd.Flags().GetString(CreateNamespace)
+			if err != nil {
+				return errors.Wrapf(err, "failed to get create namespace")
+			}
+
+			if createNamespace == "" {
+				createNamespace = types.DefaultKubeVelaNS
+			}
+
 			switch clusterManagementType {
 			case ClusterGateWayClusterManagement:
 				if endpoint, err := utils.ParseAPIServerEndpoint(cluster.Server); err == nil {
@@ -212,7 +238,7 @@ func NewClusterJoinCommand(c *common.Args) *cobra.Command {
 				} else {
 					_, _ = cmd.OutOrStdout().Write([]byte("failed to parse server endpoint: " + err.Error()))
 				}
-				if err = registerClusterManagedByVela(c.Client, cluster, authInfo, clusterName); err != nil {
+				if err = registerClusterManagedByVela(c.Client, cluster, authInfo, clusterName, createNamespace); err != nil {
 					return err
 				}
 			case OCMClusterManagement:
@@ -226,10 +252,11 @@ func NewClusterJoinCommand(c *common.Args) *cobra.Command {
 	}
 	cmd.Flags().StringP(FlagClusterName, "n", "", "Specify the cluster name. If empty, it will use the cluster name in config file. Default to be empty.")
 	cmd.Flags().StringP(FlagClusterManagementEngine, "t", "", "Specify the cluster management engine. If empty, it will use cluster-gateway cluster management solution. Default to be empty.")
+	cmd.Flags().StringP(CreateNamespace, "", "", "Specifies the namespace need to create in managedCluster")
 	return cmd
 }
 
-func registerClusterManagedByVela(k8sClient client.Client, cluster *clientcmdapi.Cluster, authInfo *clientcmdapi.AuthInfo, clusterName string) error {
+func registerClusterManagedByVela(k8sClient client.Client, cluster *clientcmdapi.Cluster, authInfo *clientcmdapi.AuthInfo, clusterName string, createNamespace string) error {
 	if err := clustermanager.EnsureClusterNotExists(k8sClient, clusterName); err != nil {
 		return errors.Wrapf(err, "cannot use cluster name %s", clusterName)
 	}
@@ -264,6 +291,10 @@ func registerClusterManagedByVela(k8sClient client.Client, cluster *clientcmdapi
 		_ = k8sClient.Delete(context.Background(), secret)
 		return errors.Wrapf(err, "failed to ensure resourcetracker crd installed in cluster %s", clusterName)
 	}
+	if err := ensureVelaSystemNamespaceInstalled(k8sClient, clusterName, createNamespace); err != nil {
+		_ = k8sClient.Delete(context.Background(), secret)
+		return errors.Wrapf(err, "failed to ensure vela-system namespace installed in cluster %s", clusterName)
+	}
 	return nil
 }
 
diff --git a/references/cli/delete.go b/references/cli/delete.go
index ab7fccd2..9d62d2d1 100644
--- a/references/cli/delete.go
+++ b/references/cli/delete.go
@@ -18,9 +18,9 @@ package cli
 
 import (
 	"errors"
+	"fmt"
 
 	"github.com/spf13/cobra"
-	apierrors "k8s.io/apimachinery/pkg/api/errors"
 
 	"github.com/oam-dev/kubevela/apis/types"
 	common2 "github.com/oam-dev/kubevela/pkg/utils/common"
@@ -45,20 +45,21 @@ func NewDeleteCommand(c common2.Args, ioStreams cmdutil.IOStreams) *cobra.Comman
 	}
 	cmd.SetOut(ioStreams.Out)
 
+	o := &common.DeleteOptions{
+		C: c,
+	}
 	cmd.RunE = func(cmd *cobra.Command, args []string) error {
 		namespace, err := GetFlagNamespaceOrEnv(cmd, c)
 		if err != nil {
 			return err
 		}
+		o.Namespace = namespace
 		newClient, err := c.GetClient()
 		if err != nil {
 			return err
 		}
-		o := &common.DeleteOptions{
-			C:         c,
-			Namespace: namespace,
-			Client:    newClient,
-		}
+		o.Client = newClient
+
 		if len(args) < 1 {
 			return errors.New("must specify name for the app")
 		}
@@ -67,29 +68,36 @@ func NewDeleteCommand(c common2.Args, ioStreams cmdutil.IOStreams) *cobra.Comman
 		if err != nil {
 			return err
 		}
+		wait, err := cmd.Flags().GetBool("wait")
+		if err != nil {
+			return err
+		}
+		o.Wait = wait
+		force, err := cmd.Flags().GetBool("force")
+		if err != nil {
+			return err
+		}
+		o.ForceDelete = force
+
 		if svcname == "" {
-			ioStreams.Infof("Deleting Application \"%s\"\n", o.AppName)
-			info, err := o.DeleteApp()
-			if err != nil {
-				if apierrors.IsNotFound(err) {
-					ioStreams.Info("Already deleted")
-					return nil
-				}
+			ioStreams.Infof(fmt.Sprintf("Deleting Application \"%s\"\n", o.AppName))
+			if err = o.DeleteApp(ioStreams); err != nil {
 				return err
 			}
-			ioStreams.Info(info)
+			ioStreams.Info(green.Sprintf("app \"%s\" deleted from namespace \"%s\"", o.AppName, o.Namespace))
 		} else {
-			ioStreams.Infof("Deleting Service %s from Application \"%s\"\n", svcname, o.AppName)
+			ioStreams.Infof(fmt.Sprintf("Deleting Service %s from Application \"%s\"\n", svcname, o.AppName))
 			o.CompName = svcname
-			message, err := o.DeleteComponent(ioStreams)
-			if err != nil {
+			if err = o.DeleteComponent(ioStreams); err != nil {
 				return err
 			}
-			ioStreams.Info(message)
+			ioStreams.Info(green.Sprintf("component \"%s\" deleted from \"%s\"", o.CompName, o.AppName))
 		}
 		return nil
 	}
 	cmd.PersistentFlags().StringP(Service, "", "", "delete only the specified service in this app")
+	cmd.PersistentFlags().BoolVarP(&o.Wait, "wait", "w", false, "wait util the application is deleted completely")
+	cmd.PersistentFlags().BoolVarP(&o.ForceDelete, "force", "f", false, "force to delete the application")
 	addNamespaceArg(cmd)
 	return cmd
 }
diff --git a/references/cli/workflow.go b/references/cli/workflow.go
index ee67df8a..c047e046 100644
--- a/references/cli/workflow.go
+++ b/references/cli/workflow.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+	k8stypes "k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
@@ -45,6 +46,7 @@ func NewWorkflowCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Comma
 		NewWorkflowResumeCommand(c, ioStreams),
 		NewWorkflowTerminateCommand(c, ioStreams),
 		NewWorkflowRestartCommand(c, ioStreams),
+		NewWorkflowRollbackCommand(c, ioStreams),
 	)
 	return cmd
 }
@@ -223,6 +225,47 @@ func NewWorkflowRestartCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra
 	return cmd
 }
 
+// NewWorkflowRollbackCommand create workflow rollback command
+func NewWorkflowRollbackCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:     "rollback",
+		Short:   "Rollback an application workflow to the latest revision",
+		Long:    "Rollback an application workflow to the latest revision",
+		Example: "vela workflow rollback <application-name>",
+		RunE: func(cmd *cobra.Command, args []string) error {
+			if len(args) < 1 {
+				return fmt.Errorf("must specify application name")
+			}
+			namespace, err := GetFlagNamespaceOrEnv(cmd, c)
+			if err != nil {
+				return err
+			}
+			app, err := appfile.LoadApplication(namespace, args[0], c)
+			if err != nil {
+				return err
+			}
+			if app.Spec.Workflow == nil {
+				return fmt.Errorf("the application must have workflow")
+			}
+			if app.Status.Workflow != nil && !app.Status.Workflow.Terminated && !app.Status.Workflow.Suspend && !app.Status.Workflow.Finished {
+				return fmt.Errorf("can not rollback a running workflow")
+			}
+			kubecli, err := c.GetClient()
+			if err != nil {
+				return err
+			}
+
+			err = rollbackWorkflow(kubecli, app)
+			if err != nil {
+				return err
+			}
+			return nil
+		},
+	}
+	addNamespaceArg(cmd)
+	return cmd
+}
+
 func suspendWorkflow(kubecli client.Client, app *v1beta1.Application) error {
 	// set the workflow suspend to true
 	app.Status.Workflow.Suspend = true
@@ -270,3 +313,22 @@ func restartWorkflow(kubecli client.Client, app *v1beta1.Application) error {
 	fmt.Printf("Successfully restart workflow: %s\n", app.Name)
 	return nil
 }
+
+func rollbackWorkflow(kubecli client.Client, app *v1beta1.Application) error {
+	if app.Status.LatestRevision == nil || app.Status.LatestRevision.Name == "" {
+		return fmt.Errorf("the latest revision is not set: %s", app.Name)
+	}
+	// get the last revision
+	revision := &v1beta1.ApplicationRevision{}
+	if err := kubecli.Get(context.TODO(), k8stypes.NamespacedName{Name: app.Status.LatestRevision.Name, Namespace: app.Namespace}, revision); err != nil {
+		return fmt.Errorf("failed to get the latest revision: %w", err)
+	}
+
+	app.Spec = revision.Spec.Application.Spec
+	if err := kubecli.Status().Update(context.TODO(), app); err != nil {
+		return err
+	}
+
+	fmt.Printf("Successfully rollback workflow to the latest revision: %s\n", app.Name)
+	return nil
+}
diff --git a/references/common/application.go b/references/common/application.go
index 3f4d7a6f..5699bf10 100644
--- a/references/common/application.go
+++ b/references/common/application.go
@@ -24,17 +24,24 @@ import (
 	"os"
 	"path/filepath"
 	"strings"
+	"time"
 
+	"github.com/fatih/color"
+
+	"github.com/crossplane/crossplane-runtime/pkg/meta"
+	"github.com/gosuri/uilive"
 	"github.com/pkg/errors"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/runtime/serializer/json"
 	apitypes "k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/wait"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/yaml"
 
 	corev1alpha2 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
 	corev1beta1 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
 	"github.com/oam-dev/kubevela/apis/types"
+	"github.com/oam-dev/kubevela/pkg/multicluster"
 	"github.com/oam-dev/kubevela/pkg/oam"
 	"github.com/oam-dev/kubevela/pkg/utils/apply"
 	"github.com/oam-dev/kubevela/pkg/utils/common"
@@ -44,6 +51,13 @@ import (
 	"github.com/oam-dev/kubevela/references/appfile/template"
 )
 
+const (
+	resourceTrackerFinalizer = "app.oam.dev/resource-tracker-finalizer"
+	// legacyOnlyRevisionFinalizer is to delete all resource trackers of app revisions which may be used
+	// out of the domain of app controller, e.g., AppRollout controller.
+	legacyOnlyRevisionFinalizer = "app.oam.dev/only-revision-finalizer"
+)
+
 // AppfileOptions is some configuration that modify options for an Appfile
 type AppfileOptions struct {
 	Kubecli   client.Client
@@ -73,23 +87,118 @@ type DeleteOptions struct {
 	CompName  string
 	Client    client.Client
 	C         common.Args
+
+	Wait        bool
+	ForceDelete bool
 }
 
 // DeleteApp will delete app including server side
-func (o *DeleteOptions) DeleteApp() (string, error) {
+func (o *DeleteOptions) DeleteApp(io cmdutil.IOStreams) error {
+	if o.ForceDelete {
+		return o.ForceDeleteApp(io)
+	}
+	if o.Wait {
+		return o.WaitUntilDeleteApp(io)
+	}
+	return o.DeleteAppWithoutDoubleCheck(io)
+}
+
+// ForceDeleteApp force delete the application
+func (o *DeleteOptions) ForceDeleteApp(io cmdutil.IOStreams) error {
+	ctx := context.Background()
+	err := o.DeleteAppWithoutDoubleCheck(io)
+	if err != nil {
+		return err
+	}
+	app := new(corev1beta1.Application)
+	err = o.Client.Get(ctx, client.ObjectKey{Name: o.AppName, Namespace: o.Namespace}, app)
+	if err != nil {
+		return client.IgnoreNotFound(err)
+	}
+
+	listOpts := []client.ListOption{
+		client.MatchingLabels{
+			oam.LabelAppName:      app.Name,
+			oam.LabelAppNamespace: app.Namespace,
+		}}
+	rtList := &corev1beta1.ResourceTrackerList{}
+	if err = o.Client.List(ctx, rtList, listOpts...); err != nil {
+		return err
+	}
+	for _, rt := range rtList.Items {
+		if err = o.Client.Delete(ctx, rt.DeepCopy()); err != nil && !apierrors.IsNotFound(err) {
+			return err
+		}
+	}
+	if err = multicluster.GarbageCollectionForAllResourceTrackersInSubCluster(ctx, o.Client, app); err != nil {
+		return err
+	}
+	io.Info("force deleted the resources created by application")
+
+	err = wait.PollImmediate(1*time.Second, 1*time.Minute, func() (done bool, err error) {
+		err = o.Client.Get(ctx, client.ObjectKeyFromObject(app), app)
+		if err == nil {
+			meta.RemoveFinalizer(app, resourceTrackerFinalizer)
+			meta.RemoveFinalizer(app, legacyOnlyRevisionFinalizer)
+			err = o.Client.Update(ctx, app)
+		}
+		if apierrors.IsNotFound(err) {
+			return true, nil
+		}
+		return false, nil
+	})
+	if err != nil {
+		io.Info("successfully cleanup the resources created by application, but fail to delete the application")
+		return err
+	}
+	return nil
+}
+
+// WaitUntilDeleteApp will wait until the application is completely deleted
+func (o *DeleteOptions) WaitUntilDeleteApp(io cmdutil.IOStreams) error {
+	tryCnt, startTime := 0, time.Now()
+	writer := uilive.New()
+	writer.Start()
+	defer writer.Stop()
+
+	io.Infof(color.New(color.FgYellow).Sprintf("waiting for delete the application \"%s\"...\n", o.AppName))
+	err := wait.PollImmediate(2*time.Second, 5*time.Minute, func() (done bool, err error) {
+		tryCnt++
+		fmt.Fprintf(writer, "try to delete the application for the %d time, wait a total of %f s\n", tryCnt, time.Since(startTime).Seconds())
+		err = o.DeleteAppWithoutDoubleCheck(io)
+		if err != nil {
+			fmt.Printf("Failed delete Application \"%s\": %s\n", o.AppName, err.Error())
+			return false, nil
+		}
+		app := new(corev1beta1.Application)
+		err = o.Client.Get(context.Background(), client.ObjectKey{Name: o.AppName, Namespace: o.Namespace}, app)
+		if apierrors.IsNotFound(err) {
+			return true, nil
+		}
+		return false, nil
+	})
+	if err != nil {
+		io.Info("waiting for the application to be deleted timed out, please try again")
+		return err
+	}
+	return nil
+}
+
+// DeleteAppWithoutDoubleCheck delete application without double check
+func (o *DeleteOptions) DeleteAppWithoutDoubleCheck(io cmdutil.IOStreams) error {
 	ctx := context.Background()
 	var app = new(corev1beta1.Application)
 	err := o.Client.Get(ctx, client.ObjectKey{Name: o.AppName, Namespace: o.Namespace}, app)
 	if err != nil {
 		if apierrors.IsNotFound(err) {
-			return fmt.Sprintf("app \"%s\" already deleted", o.AppName), nil
+			return nil
 		}
-		return "", fmt.Errorf("delete appconfig err: %w", err)
+		return fmt.Errorf("delete application err: %w", err)
 	}
 
 	err = o.Client.Delete(ctx, app)
 	if err != nil && !apierrors.IsNotFound(err) {
-		return "", fmt.Errorf("delete application err: %w", err)
+		return fmt.Errorf("delete application err: %w", err)
 	}
 
 	for _, cmp := range app.Spec.Components {
@@ -100,46 +209,45 @@ func (o *DeleteOptions) DeleteApp() (string, error) {
 				if apierrors.IsNotFound(err) {
 					continue
 				}
-				return "", fmt.Errorf("delete health scope %s err: %w", healthScopeName, err)
+				return fmt.Errorf("delete health scope %s err: %w", healthScopeName, err)
 			}
 			if err = o.Client.Delete(ctx, &healthScope); err != nil {
-				return "", fmt.Errorf("delete health scope %s err: %w", healthScopeName, err)
+				return fmt.Errorf("delete health scope %s err: %w", healthScopeName, err)
 			}
 		}
 	}
-	return fmt.Sprintf("app \"%s\" already deleted from namespace \"%s\"", o.AppName, o.Namespace), nil
+	return nil
 }
 
 // DeleteComponent will delete one component including server side.
-func (o *DeleteOptions) DeleteComponent(io cmdutil.IOStreams) (string, error) {
+func (o *DeleteOptions) DeleteComponent(io cmdutil.IOStreams) error {
 	var err error
 	if o.AppName == "" {
-		return "", errors.New("app name is required")
+		return errors.New("app name is required")
 	}
 	app, err := appfile.LoadApplication(o.Namespace, o.AppName, o.C)
 	if err != nil {
-		return "", err
+		return err
 	}
 
 	if len(appfile.GetComponents(app)) <= 1 {
-		return o.DeleteApp()
+		return o.DeleteApp(io)
 	}
 
 	// Remove component from local appfile
 	if err := appfile.RemoveComponent(app, o.CompName); err != nil {
-		return "", err
+		return err
 	}
 
 	// Remove component from appConfig in k8s cluster
 	ctx := context.Background()
 
 	if err := o.Client.Update(ctx, app); err != nil {
-		return "", err
+		return err
 	}
 
 	// It's the server responsibility to GC component
-
-	return fmt.Sprintf("component \"%s\" deleted from \"%s\"", o.CompName, o.AppName), nil
+	return nil
 }
 
 func saveAndLoadRemoteAppfile(url string) (*api.AppFile, error) {
diff --git a/vela-templates/addons/auto-gen/terraform-provider-alibaba.yaml b/vela-templates/addons/auto-gen/terraform-provider-alibaba.yaml
index 1df824fa..efe27da8 100644
--- a/vela-templates/addons/auto-gen/terraform-provider-alibaba.yaml
+++ b/vela-templates/addons/auto-gen/terraform-provider-alibaba.yaml
@@ -8,12 +8,12 @@ metadata:
   namespace: vela-system
 spec:
   components:
-  - name: alibaba-account-creds
+  - name: alibaba-account-creds-[[ index .Args "providerName" ]]
     properties:
       apiVersion: v1
       kind: Secret
       metadata:
-        name: alibaba-account-creds
+        name: alibaba-account-creds-[[ index .Args "providerName" ]]
         namespace: vela-system
       stringData:
         credentials: |
@@ -22,18 +22,18 @@ spec:
           securityToken: [[ index .Args "ALICLOUD_SECURITY_TOKEN" ]]
       type: Opaque
     type: raw
-  - name: default
+  - name: alibaba-[[ index .Args "providerName" ]]
     properties:
       apiVersion: terraform.core.oam.dev/v1beta1
       kind: Provider
       metadata:
-        name: default
+        name: '[[ index .Args "providerName" ]]'
         namespace: default
       spec:
         credentials:
           secretRef:
             key: credentials
-            name: alibaba-account-creds
+            name: alibaba-account-creds-[[ index .Args "providerName" ]]
             namespace: vela-system
           source: Secret
         provider: alibaba
diff --git a/vela-templates/addons/terraform-provider-alibaba/template.yaml b/vela-templates/addons/terraform-provider-alibaba/template.yaml
index 6c98e9b5..27760db5 100644
--- a/vela-templates/addons/terraform-provider-alibaba/template.yaml
+++ b/vela-templates/addons/terraform-provider-alibaba/template.yaml
@@ -8,12 +8,12 @@ metadata:
   namespace: vela-system
 spec:
   components:
-    - name: alibaba-account-creds
+    - name: alibaba-account-creds-[[ index .Args "providerName" ]]
       properties:
         apiVersion: v1
         kind: Secret
         metadata:
-          name: alibaba-account-creds
+          name: alibaba-account-creds-[[ index .Args "providerName" ]]
           namespace: vela-system
         type: Opaque
         stringData:
@@ -22,13 +22,13 @@ spec:
             accessKeySecret: [[ index .Args "ALICLOUD_SECRET_KEY" ]]
             securityToken: [[ index .Args "ALICLOUD_SECURITY_TOKEN" ]]
       type: raw
-    - name: default
+    - name: alibaba-[[ index .Args "providerName" ]]
       type: raw
       properties:
         apiVersion: terraform.core.oam.dev/v1beta1
         kind: Provider
         metadata:
-          name: default
+          name: '[[ index .Args "providerName" ]]'
           namespace: default
         spec:
           provider: alibaba
@@ -37,7 +37,7 @@ spec:
             source: Secret
             secretRef:
               namespace: vela-system
-              name: alibaba-account-creds
+              name: alibaba-account-creds-[[ index .Args "providerName" ]]
               key: credentials
 {{ range .ResourceFiles }}
     - name: {{ .Name }}
diff --git a/vela-templates/definitions/uischema/component-webservice.yaml b/vela-templates/definitions/uischema/component-webservice.yaml
new file mode 100644
index 00000000..a27a31cd
--- /dev/null
+++ b/vela-templates/definitions/uischema/component-webservice.yaml
@@ -0,0 +1,99 @@
+- uiType: ImageInput
+  jsonKey: image
+  label: Application Image
+  sort: 1
+- jsonKey: memory
+  uiType: MemoryNumber
+  sort: 3
+  label: Memory Request&Limit
+- uiType: CPUNumber
+  jsonKey: cpu
+  sort: 5
+  label: CPU Request&Limit
+- description: Specify image pull policy for your service
+  jsonKey: imagePullPolicy
+  label: Image Pull Policy
+  uiType: Select
+  sort: 7
+  validate: 
+    options:
+      - label: IfNotPresent
+        value: IfNotPresent
+      - label: Always
+        value: Always
+      - label: Never
+        value: Never
+- jsonKey: exposeType
+  sort: 8
+- jsonKey: cmd
+  label: Container Start Command
+  sort: 9
+- jsonKey: env
+  sort: 10
+  label: Environments
+  subParameterGroupOption:
+    - label: Add By Value
+      keys:
+        - name
+        - value
+    - label: Add By Secret
+      keys:
+       - name
+       - valueFrom
+  subParameters:
+  - jsonKey: valueFrom
+    label: Secret Selector
+    uiType: InnerGroup
+    subParameters:
+      - jsonKey: configMapKeyRef
+        disable: true
+      - jsonKey: secretKeyRef
+        uiType: Ignore
+        subParameters:
+          - jsonKey: name
+            label: Secret Name
+            uiType: SecretSelect
+            sort: 1
+          - jsonKey: key
+            label: Secret Key
+            uiType: SecretKeySelect
+            sort: 3
+  uiType: Structs
+- jsonKey: ports
+  label: Service Ports
+  sort: 11
+- jsonKey: volumeMounts
+  label: Persistent Storage
+  uiType: Group
+  sort: 12
+
+- jsonKey: readinessProbe
+  uiType: Group
+  label: ReadinessProbe
+  subParameters:
+  - jsonKey: hostAliases
+    disable: true
+  sort: 13
+
+- jsonKey: livenessProbe
+  uiType: Group
+  label: LivenessProbe
+  subParameters:
+  - jsonKey: hostAliases
+    disable: true
+  sort: 15
+
+- jsonKey: annotations
+  sort: 19
+
+- jsonKey: lables
+  sort: 21
+
+- jsonKey: addRevisionLabel
+  disable: true
+
+- jsonKey: port
+  disable: true
+
+- jsonKey: volumes
+  disable: true
\ No newline at end of file


Test patch:
diff --git a/e2e/application/application_test.go b/e2e/application/application_test.go
index 844434dd..bea919c2 100644
--- a/e2e/application/application_test.go
+++ b/e2e/application/application_test.go
@@ -22,6 +22,7 @@ import (
 	"time"
 
 	"github.com/Netflix/go-expect"
+	"github.com/crossplane/crossplane-runtime/pkg/meta"
 	"github.com/onsi/ginkgo"
 	"github.com/onsi/gomega"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -38,6 +39,7 @@ var (
 	traitAlias                  = "scaler"
 	appNameForInit              = "initmyapp"
 	jsonAppFile                 = `{"name":"nginx-vela","services":{"nginx":{"type":"webservice","image":"nginx:1.9.4","port":80}}}`
+	testDeleteJsonAppFile       = `{"name":"test-vela-delete","services":{"nginx-test":{"type":"webservice","image":"nginx:1.9.4","port":80}}}`
 	appbasicJsonAppFile         = `{"name":"app-basic","services":{"app-basic":{"type":"webservice","image":"nginx:1.9.4","port":80}}}`
 	appbasicAddTraitJsonAppFile = `{"name":"app-basic","services":{"app-basic":{"type":"webservice","image":"nginx:1.9.4","port":80,"scaler":{"replicas":2}}}}`
 )
@@ -59,6 +61,12 @@ var _ = ginkgo.Describe("Test Vela Application", func() {
 
 	ApplicationInitIntercativeCliContext("test vela init app", appNameForInit, workloadType)
 	e2e.WorkloadDeleteContext("delete", appNameForInit)
+
+	e2e.JsonAppFileContext("json appfile apply", testDeleteJsonAppFile)
+	ApplicationDeleteWithWaitOptions("test delete with wait option", "test-vela-delete")
+
+	e2e.JsonAppFileContext("json appfile apply", testDeleteJsonAppFile)
+	ApplicationDeleteWithForceOptions("test delete with force option", "test-vela-delete")
 })
 
 var ApplicationStatusContext = func(context string, applicationName string, workloadType string) bool {
@@ -178,3 +186,54 @@ var ApplicationInitIntercativeCliContext = func(context string, appName string,
 		})
 	})
 }
+
+var ApplicationDeleteWithWaitOptions = func(context string, appName string) bool {
+	return ginkgo.Context(context, func() {
+		ginkgo.It("should print successful deletion information", func() {
+			cli := fmt.Sprintf("vela delete %s --wait", appName)
+			output, err := e2e.ExecAndTerminate(cli)
+			gomega.Expect(err).NotTo(gomega.HaveOccurred())
+			gomega.Expect(output).To(gomega.ContainSubstring("deleted from namespace"))
+		})
+	})
+}
+
+var ApplicationDeleteWithForceOptions = func(context string, appName string) bool {
+	return ginkgo.Context(context, func() {
+		ginkgo.It("should print successful deletion information", func() {
+			args := common.Args{
+				Schema: common.Scheme,
+			}
+			ctx := context2.Background()
+
+			k8sClient, err := args.GetClient()
+			gomega.Expect(err).NotTo(gomega.HaveOccurred())
+
+			app := new(v1beta1.Application)
+			gomega.Eventually(func() error {
+				if err := k8sClient.Get(ctx, client.ObjectKey{Name: appName, Namespace: "default"}, app); err != nil {
+					return err
+				}
+				meta.AddFinalizer(app, "test")
+				return k8sClient.Update(ctx, app)
+			}, time.Second*3, time.Millisecond*300).Should(gomega.BeNil())
+
+			cli := fmt.Sprintf("vela delete %s --force", appName)
+			output, err := e2e.LongTimeExec(cli, 3*time.Minute)
+			gomega.Expect(err).NotTo(gomega.HaveOccurred())
+			gomega.Expect(output).To(gomega.ContainSubstring("timed out"))
+
+			app = new(v1beta1.Application)
+			gomega.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: appName, Namespace: "default"}, app)).NotTo(gomega.HaveOccurred())
+			meta.RemoveFinalizer(app, "test")
+			gomega.Eventually(func() error {
+				return k8sClient.Update(ctx, app)
+			}, time.Second*3, time.Millisecond*300).Should(gomega.BeNil())
+
+			cli = fmt.Sprintf("vela delete %s --force", appName)
+			output, err = e2e.ExecAndTerminate(cli)
+			gomega.Expect(err).NotTo(gomega.HaveOccurred())
+			gomega.Expect(output).To(gomega.ContainSubstring("deleted from namespace"))
+		})
+	})
+}
diff --git a/pkg/apiserver/rest/usecase/definition_test.go b/pkg/apiserver/rest/usecase/definition_test.go
index b3a2b462..673b118d 100644
--- a/pkg/apiserver/rest/usecase/definition_test.go
+++ b/pkg/apiserver/rest/usecase/definition_test.go
@@ -145,7 +145,7 @@ var _ = Describe("Test namespace usecase functions", func() {
 
 		uiSchema := patchSchema(defaultschema, customschema)
 		Expect(cmp.Diff(len(uiSchema), 12)).Should(BeEmpty())
-		Expect(cmp.Diff(uiSchema[7].JSONKey, "readinessProbe")).Should(BeEmpty())
+		Expect(cmp.Diff(uiSchema[7].JSONKey, "livenessProbe")).Should(BeEmpty())
 		Expect(cmp.Diff(len(uiSchema[7].SubParameters), 8)).Should(BeEmpty())
 
 		outdata, err := yaml.Marshal(uiSchema)
diff --git a/pkg/apiserver/rest/usecase/testdata/ui-custom-schema.yaml b/pkg/apiserver/rest/usecase/testdata/ui-custom-schema.yaml
index 03bfd508..a27a31cd 100755
--- a/pkg/apiserver/rest/usecase/testdata/ui-custom-schema.yaml
+++ b/pkg/apiserver/rest/usecase/testdata/ui-custom-schema.yaml
@@ -1,25 +1,36 @@
 - uiType: ImageInput
   jsonKey: image
-  label: 服务镜像
+  label: Application Image
   sort: 1
-- description: Specifies the attributes of the memory resource required for the container.
-  disable: false
-  jsonKey: memory
+- jsonKey: memory
   uiType: MemoryNumber
   sort: 3
-  label: 分配内存
+  label: Memory Request&Limit
 - uiType: CPUNumber
   jsonKey: cpu
   sort: 5
-  label: 分配CPU
-- jsonKey: cmd
-  label: 启动参数
+  label: CPU Request&Limit
+- description: Specify image pull policy for your service
+  jsonKey: imagePullPolicy
+  label: Image Pull Policy
+  uiType: Select
   sort: 7
-- description: Define arguments by using environment variables
-  disable: false
-  jsonKey: env
-  label: 环境变量
+  validate: 
+    options:
+      - label: IfNotPresent
+        value: IfNotPresent
+      - label: Always
+        value: Always
+      - label: Never
+        value: Never
+- jsonKey: exposeType
+  sort: 8
+- jsonKey: cmd
+  label: Container Start Command
   sort: 9
+- jsonKey: env
+  sort: 10
+  label: Environments
   subParameterGroupOption:
     - label: Add By Value
       keys:
@@ -30,48 +41,59 @@
        - name
        - valueFrom
   subParameters:
-  - description: Specifies a source the value of this var should come from
-    disable: false
-    jsonKey: valueFrom
-    label: Secret选择器
+  - jsonKey: valueFrom
+    label: Secret Selector
     uiType: InnerGroup
     subParameters:
+      - jsonKey: configMapKeyRef
+        disable: true
       - jsonKey: secretKeyRef
         uiType: Ignore
         subParameters:
           - jsonKey: name
-            label: Secret选择
+            label: Secret Name
             uiType: SecretSelect
+            sort: 1
           - jsonKey: key
-            label: SecretKey选择
+            label: Secret Key
             uiType: SecretKeySelect
+            sort: 3
   uiType: Structs
-  validate: {}
-- jsonKey: port
-  label: 端口设置
-  sort: 10
-- jsonKey: volumes
-  label: 持久化存储
+- jsonKey: ports
+  label: Service Ports
   sort: 11
+- jsonKey: volumeMounts
+  label: Persistent Storage
+  uiType: Group
+  sort: 12
+
 - jsonKey: readinessProbe
   uiType: Group
-  label: ReadinessProbe检测
+  label: ReadinessProbe
+  subParameters:
+  - jsonKey: hostAliases
+    disable: true
   sort: 13
+
 - jsonKey: livenessProbe
   uiType: Group
-  label: LivenessProbe检测
+  label: LivenessProbe
+  subParameters:
+  - jsonKey: hostAliases
+    disable: true
   sort: 15
-- description: Specify image pull policy for your service
-  disable: false
-  jsonKey: imagePullPolicy
-  label: 镜像更新策略
-  uiType: Select
-  sort: 17
-  validate: 
-    options:
-      - label: 镜像不存在时更新
-        value: IfNotPresent
-      - label: 总是更新
-        value: Always
-      - label: 永不更新
-        value: Never
\ No newline at end of file
+
+- jsonKey: annotations
+  sort: 19
+
+- jsonKey: lables
+  sort: 21
+
+- jsonKey: addRevisionLabel
+  disable: true
+
+- jsonKey: port
+  disable: true
+
+- jsonKey: volumes
+  disable: true
\ No newline at end of file
diff --git a/pkg/cue/model/value/value_test.go b/pkg/cue/model/value/value_test.go
index 1e34310b..2eafcfd0 100644
--- a/pkg/cue/model/value/value_test.go
+++ b/pkg/cue/model/value/value_test.go
@@ -610,6 +610,16 @@ apply: workload: name: "main"
 			script: `
 apply.workload.name`,
 			expect: `"main"
+`,
+		},
+		{
+			src: `
+apply: arr: ["abc","def"]
+`,
+			script: `
+import "strings"
+strings.Join(apply.arr,".")+"$"`,
+			expect: `"abc.def$"
 `,
 		},
 	}
@@ -634,7 +644,7 @@ apply.workload.name`,
    op: 12
 `,
 			script: `op(1`,
-			err:    "expected ')', found 'EOF'",
+			err:    "parse script: expected ')', found 'EOF'",
 		},
 		{
 			src: `
diff --git a/pkg/workflow/providers/oam/apply_test.go b/pkg/workflow/providers/oam/apply_test.go
index 14354225..0ffd2060 100644
--- a/pkg/workflow/providers/oam/apply_test.go
+++ b/pkg/workflow/providers/oam/apply_test.go
@@ -193,6 +193,48 @@ func TestLoadComponent(t *testing.T) {
 	r.NoError(err)
 }
 
+func TestLoadComponentInOrder(t *testing.T) {
+	r := require.New(t)
+	p := &provider{
+		app: &v1beta1.Application{
+			Spec: v1beta1.ApplicationSpec{
+				Components: []common.ApplicationComponent{
+					{
+						Name:       "c1",
+						Type:       "web",
+						Properties: &runtime.RawExtension{Raw: []byte(`{"image": "busybox"}`)},
+					},
+					{
+						Name:       "c2",
+						Type:       "web2",
+						Properties: &runtime.RawExtension{Raw: []byte(`{"image": "busybox"}`)},
+					},
+				},
+			},
+		},
+	}
+	v, err := value.NewValue(``, nil, "")
+	r.NoError(err)
+	err = p.LoadComponentInOrder(nil, v, nil)
+	r.NoError(err)
+	s, err := v.String()
+	r.NoError(err)
+	r.Equal(s, `value: [{
+	name: "c1"
+	type: "web"
+	properties: {
+		image: "busybox"
+	}
+}, {
+	name: "c2"
+	type: "web2"
+	properties: {
+		image: "busybox"
+	}
+}]
+`)
+}
+
 var testHealthy bool
 
 func simpleComponentApplyForTest(comp common.ApplicationComponent, _ *value.Value, _ string, _ string) (*unstructured.Unstructured, []*unstructured.Unstructured, bool, error) {
diff --git a/references/cli/workflow_test.go b/references/cli/workflow_test.go
index 4d8edd28..6624c81a 100644
--- a/references/cli/workflow_test.go
+++ b/references/cli/workflow_test.go
@@ -429,3 +429,137 @@ func TestWorkflowRestart(t *testing.T) {
 		})
 	}
 }
+
+func TestWorkflowRollback(t *testing.T) {
+	c := initArgs()
+	ioStream := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
+	ctx := context.TODO()
+
+	testCases := map[string]struct {
+		app         *v1beta1.Application
+		revision    *v1beta1.ApplicationRevision
+		expectedErr error
+	}{
+		"no app name specified": {
+			expectedErr: fmt.Errorf("must specify application name"),
+		},
+		"no workflow in app": {
+			app: &v1beta1.Application{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "no-workflow",
+					Namespace: "default",
+				},
+			},
+			expectedErr: fmt.Errorf("the application must have workflow"),
+		},
+		"workflow running": {
+			app: &v1beta1.Application{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "workflow-not-running",
+					Namespace: "default",
+				},
+				Spec: workflowSpec,
+				Status: common.AppStatus{
+					Workflow: &common.WorkflowStatus{
+						Suspend:    false,
+						Terminated: false,
+						Finished:   false,
+					},
+				},
+			},
+			expectedErr: fmt.Errorf("can not rollback a running workflow"),
+		},
+		"invalid revision": {
+			app: &v1beta1.Application{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "invalid-revision",
+					Namespace: "default",
+				},
+				Spec: workflowSpec,
+				Status: common.AppStatus{
+					Workflow: &common.WorkflowStatus{
+						Suspend: true,
+					},
+				},
+			},
+			expectedErr: fmt.Errorf("the latest revision is not set: invalid-revision"),
+		},
+		"rollback successfully": {
+			app: &v1beta1.Application{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "workflow",
+					Namespace: "test",
+				},
+				Spec: workflowSpec,
+				Status: common.AppStatus{
+					LatestRevision: &common.Revision{
+						Name: "revision-v1",
+					},
+					Workflow: &common.WorkflowStatus{
+						Terminated: true,
+					},
+				},
+			},
+			revision: &v1beta1.ApplicationRevision{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "revision-v1",
+					Namespace: "test",
+				},
+				Spec: v1beta1.ApplicationRevisionSpec{
+					Application: v1beta1.Application{
+						Spec: v1beta1.ApplicationSpec{
+							Components: []common.ApplicationComponent{{
+								Name:       "revision-component",
+								Type:       "worker",
+								Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
+							}},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			r := require.New(t)
+			cmd := NewWorkflowRollbackCommand(c, ioStream)
+			initCommand(cmd)
+
+			if tc.app != nil {
+				err := c.Client.Create(ctx, tc.app)
+				r.NoError(err)
+
+				if tc.app.Namespace != corev1.NamespaceDefault {
+					err := c.Client.Create(ctx, &corev1.Namespace{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: tc.app.Namespace,
+						},
+					})
+					r.NoError(err)
+					cmd.SetArgs([]string{tc.app.Name, "-n", tc.app.Namespace})
+				} else {
+					cmd.SetArgs([]string{tc.app.Name})
+				}
+			}
+			if tc.revision != nil {
+				err := c.Client.Create(ctx, tc.revision)
+				r.NoError(err)
+			}
+			err := cmd.Execute()
+			if tc.expectedErr != nil {
+				r.Equal(tc.expectedErr, err)
+				return
+			}
+			r.NoError(err)
+
+			wf := &v1beta1.Application{}
+			err = c.Client.Get(ctx, types.NamespacedName{
+				Namespace: tc.app.Namespace,
+				Name:      tc.app.Name,
+			}, wf)
+			r.NoError(err)
+			r.Equal(wf.Spec.Components[0].Name, "revision-component")
+		})
+	}
+}
diff --git a/test/e2e-apiserver-test/addon_test.go b/test/e2e-apiserver-test/addon_test.go
index eb013418..12e0f62e 100644
--- a/test/e2e-apiserver-test/addon_test.go
+++ b/test/e2e-apiserver-test/addon_test.go
@@ -83,15 +83,12 @@ var _ = Describe("Test addon rest api", func() {
 		err = json.NewDecoder(listRes.Body).Decode(&lres)
 		Expect(err).Should(BeNil())
 		Expect(lres.Addons).ShouldNot(BeZero())
-		firstAddon := lres.Addons[0]
-		Expect(firstAddon.Name).Should(Equal("fluxcd"))
-
 	})
 
 	PIt("should enable and disable an addon", func() {
 		defer GinkgoRecover()
 		req := apis.EnableAddonRequest{
-			Args: map[string]string{
+			Args: map[string]interface{}{
 				"example": "test-args",
 			},
 		}
diff --git a/test/e2e-multicluster-test/multicluster_test.go b/test/e2e-multicluster-test/multicluster_test.go
index 8cf22d65..8cedb85e 100644
--- a/test/e2e-multicluster-test/multicluster_test.go
+++ b/test/e2e-multicluster-test/multicluster_test.go
@@ -25,6 +25,8 @@ import (
 	"strings"
 	"time"
 
+	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
+
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	v13 "k8s.io/api/apps/v1"
@@ -339,6 +341,59 @@ var _ = Describe("Test multicluster scenario", func() {
 				g.Expect(len(deploys.Items)).Should(Equal(0))
 			}, time.Minute).Should(Succeed())
 		})
-	})
 
+		It("Test helm addon relied feature", func() {
+			By("apply application")
+			app := &v1beta1.Application{}
+			bs, err := ioutil.ReadFile("./testdata/app/app-apply-in-order.yaml")
+			Expect(err).Should(Succeed())
+			Expect(yaml.Unmarshal([]byte(bs), app)).Should(Succeed())
+			app.SetNamespace(testNamespace)
+			err = k8sClient.Create(hubCtx, app)
+			Expect(err).Should(Succeed())
+			By("wait application resource ready")
+			targetNamespace := "test-addon-namespace"
+			Eventually(func() error {
+				// check deployments in clusters
+				ns := v1.Namespace{}
+				if err := k8sClient.Get(hubCtx, types.NamespacedName{Name: targetNamespace}, &ns); err != nil {
+					return err
+				}
+				svc := v1.Service{}
+				if err := k8sClient.Get(hubCtx, types.NamespacedName{Namespace: targetNamespace, Name: "addon-fluxcd-service"}, &svc); err != nil {
+					return err
+				}
+				return nil
+			}, 20*time.Minute).Should(Succeed())
+
+			Eventually(func() error {
+				// check deployments in clusters
+				ns := v1.Namespace{}
+				if err := k8sClient.Get(workerCtx, types.NamespacedName{Name: targetNamespace}, &ns); err != nil {
+					return err
+				}
+				svc := v1.Service{}
+				if err := k8sClient.Get(workerCtx, types.NamespacedName{Namespace: targetNamespace, Name: "addon-fluxcd-service"}, &svc); err != nil {
+					return err
+				}
+				return nil
+			}, 20*time.Minute).Should(Succeed())
+
+			checkApp := v1beta1.Application{}
+			Eventually(func() error {
+				err := k8sClient.Get(hubCtx, types.NamespacedName{Namespace: testNamespace, Name: app.Name}, &checkApp)
+				if err != nil {
+					return err
+				}
+				if checkApp.Status.Phase != common.ApplicationRunning {
+					return fmt.Errorf("application not running")
+				}
+				return nil
+			}, 30*time.Minute, 500*time.Millisecond).Should(BeNil())
+
+			By("test delete env")
+			err = k8sClient.Delete(hubCtx, &checkApp)
+			Expect(err).Should(BeNil())
+		})
+	})
 })
diff --git a/test/e2e-multicluster-test/testdata/app/app-apply-in-order.yaml b/test/e2e-multicluster-test/testdata/app/app-apply-in-order.yaml
new file mode 100644
index 00000000..c16866a5
--- /dev/null
+++ b/test/e2e-multicluster-test/testdata/app/app-apply-in-order.yaml
@@ -0,0 +1,37 @@
+apiVersion: core.oam.dev/v1beta1
+kind: Application
+metadata:
+  name: addon-fluxcd
+  namespace: TEST_NAMESPACE
+spec:
+  components:
+    - name: flux-system-namespace
+      properties:
+        apiVersion: v1
+        kind: Namespace
+        metadata:
+          name: test-addon-namespace
+      type: raw
+    - name: service
+      properties:
+        apiVersion: v1
+        kind: Service
+        metadata:
+          name: addon-fluxcd-service
+          namespace: test-addon-namespace
+        spec:
+          ports:
+            - name: http
+              port: 80
+              protocol: TCP
+              targetPort: http-webhook
+          selector:
+            app: notification-controller
+          type: ClusterIP
+      type: raw
+  workflow:
+    steps:
+      - name: deploy-control-plane
+        type: apply-application
+      - name: deploy-runtime
+        type: deploy2runtime
\ No newline at end of file