github.com/oam-dev/kubevela@v1.9.11/references/cli/adopt.go (about)

     1  /*
     2  Copyright 2022 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package cli
    18  
    19  import (
    20  	"context"
    21  	_ "embed"
    22  	"encoding/json"
    23  	"fmt"
    24  	"os"
    25  	"strings"
    26  	"time"
    27  
    28  	"cuelang.org/go/cue"
    29  	"github.com/kubevela/pkg/cue/cuex"
    30  	"github.com/spf13/cobra"
    31  	"helm.sh/helm/v3/pkg/action"
    32  	"helm.sh/helm/v3/pkg/release"
    33  	"helm.sh/helm/v3/pkg/releaseutil"
    34  	"helm.sh/helm/v3/pkg/storage"
    35  	appsv1 "k8s.io/api/apps/v1"
    36  	"k8s.io/apimachinery/pkg/api/meta"
    37  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    38  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    39  	"k8s.io/apimachinery/pkg/runtime/schema"
    40  	apitypes "k8s.io/apimachinery/pkg/types"
    41  	"k8s.io/apimachinery/pkg/util/wait"
    42  	"k8s.io/klog/v2"
    43  	"k8s.io/kubectl/pkg/util/i18n"
    44  	"k8s.io/kubectl/pkg/util/templates"
    45  	"k8s.io/utils/strings/slices"
    46  	"sigs.k8s.io/controller-runtime/pkg/client"
    47  	"sigs.k8s.io/yaml"
    48  
    49  	"github.com/kubevela/pkg/util/k8s"
    50  	"github.com/kubevela/pkg/util/resourcetopology"
    51  	velaslices "github.com/kubevela/pkg/util/slices"
    52  
    53  	"github.com/kubevela/pkg/multicluster"
    54  
    55  	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    56  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
    57  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    58  	"github.com/oam-dev/kubevela/apis/types"
    59  	velacmd "github.com/oam-dev/kubevela/pkg/cmd"
    60  	cmdutil "github.com/oam-dev/kubevela/pkg/cmd/util"
    61  	"github.com/oam-dev/kubevela/pkg/oam"
    62  	"github.com/oam-dev/kubevela/pkg/utils/apply"
    63  	"github.com/oam-dev/kubevela/pkg/utils/env"
    64  	"github.com/oam-dev/kubevela/pkg/utils/util"
    65  )
    66  
    67  const (
    68  	adoptTypeNative   = "native"
    69  	adoptTypeHelm     = "helm"
    70  	adoptModeReadOnly = v1alpha1.ReadOnlyPolicyType
    71  	adoptModeTakeOver = v1alpha1.TakeOverPolicyType
    72  	helmDriverEnvKey  = "HELM_DRIVER"
    73  	defaultHelmDriver = "secret"
    74  	adoptCUETempVal   = "adopt"
    75  	adoptCUETempFunc  = "#Adopt"
    76  )
    77  
    78  //go:embed adopt-templates/default.cue
    79  var defaultAdoptTemplate string
    80  
    81  //go:embed resource-topology/builtin-rule.cue
    82  var defaultResourceTopologyRule string
    83  
    84  var (
    85  	adoptTypes = []string{adoptTypeNative, adoptTypeHelm}
    86  	adoptModes = []string{adoptModeReadOnly, adoptModeTakeOver}
    87  )
    88  
    89  type resourceRef struct {
    90  	schema.GroupVersionKind
    91  	apitypes.NamespacedName
    92  	Cluster string
    93  	Arg     string
    94  }
    95  
    96  // AdoptOptions options for vela adopt command
    97  type AdoptOptions struct {
    98  	Type         string `json:"type"`
    99  	Mode         string `json:"mode"`
   100  	AppName      string `json:"appName"`
   101  	AppNamespace string `json:"appNamespace"`
   102  
   103  	HelmReleaseName      string
   104  	HelmReleaseNamespace string
   105  	HelmDriver           string
   106  	HelmConfig           *action.Configuration
   107  	HelmStore            *storage.Storage
   108  	HelmRelease          *release.Release
   109  	HelmReleaseRevisions []*release.Release
   110  
   111  	NativeResourceRefs []*resourceRef
   112  
   113  	Apply   bool
   114  	Recycle bool
   115  	Yes     bool
   116  	All     bool
   117  
   118  	AdoptTemplateFile     string
   119  	AdoptTemplate         string
   120  	AdoptTemplateCUEValue cue.Value
   121  
   122  	ResourceTopologyRuleFile string
   123  	ResourceTopologyRule     string
   124  	AllGVKs                  []schema.GroupVersionKind
   125  
   126  	Resources []*unstructured.Unstructured `json:"resources"`
   127  
   128  	util.IOStreams
   129  }
   130  
   131  func (opt *AdoptOptions) parseResourceGVK(f velacmd.Factory, arg string) (schema.GroupVersionKind, error) {
   132  	_, gr := schema.ParseResourceArg(arg)
   133  	gvks, err := f.Client().RESTMapper().KindsFor(gr.WithVersion(""))
   134  	if err != nil {
   135  		return schema.GroupVersionKind{}, fmt.Errorf("failed to find types for resource %s: %w", arg, err)
   136  	}
   137  	if len(gvks) == 0 {
   138  		return schema.GroupVersionKind{}, fmt.Errorf("no schema found for resource %s: %w", arg, err)
   139  	}
   140  	return gvks[0], nil
   141  }
   142  
   143  func (opt *AdoptOptions) parseResourceRef(f velacmd.Factory, cmd *cobra.Command, arg string) (*resourceRef, error) {
   144  	parts := strings.Split(arg, "/")
   145  	gvk, err := opt.parseResourceGVK(f, parts[0])
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  	mappings, err := f.Client().RESTMapper().RESTMappings(gvk.GroupKind(), gvk.Version)
   150  	if err != nil {
   151  		return nil, fmt.Errorf("failed to find mappings for resource %s: %w", arg, err)
   152  	}
   153  	if len(mappings) == 0 {
   154  		return nil, fmt.Errorf("no mappings found for resource %s: %w", arg, err)
   155  	}
   156  	mapping := mappings[0]
   157  	or := &resourceRef{GroupVersionKind: gvk, Cluster: multicluster.Local, Arg: arg}
   158  	switch len(parts) {
   159  	case 2:
   160  		or.Name = parts[1]
   161  		if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
   162  			or.Namespace = velacmd.GetNamespace(f, cmd)
   163  			if or.Namespace == "" {
   164  				or.Namespace = env.DefaultEnvNamespace
   165  			}
   166  		}
   167  	case 3:
   168  		or.Namespace = parts[1]
   169  		or.Name = parts[2]
   170  	case 4:
   171  		or.Cluster = parts[1]
   172  		or.Namespace = parts[2]
   173  		or.Name = parts[3]
   174  	default:
   175  		return nil, fmt.Errorf("resource should be like <type>/<name> or <type>/<namespace>/<name> or <type>/<cluster>/<namespace>/<name>")
   176  	}
   177  	return or, nil
   178  }
   179  
   180  // Init .
   181  func (opt *AdoptOptions) Init(f velacmd.Factory, cmd *cobra.Command, args []string) (err error) {
   182  	if opt.All {
   183  		if len(args) > 0 {
   184  			for _, arg := range args {
   185  				gvk, err := opt.parseResourceGVK(f, arg)
   186  				if err != nil {
   187  					return err
   188  				}
   189  				opt.AllGVKs = append(opt.AllGVKs, gvk)
   190  				apiVersion, kind := gvk.ToAPIVersionAndKind()
   191  				_, _ = fmt.Fprintf(opt.Out, "Adopt all %s/%s resources\n", apiVersion, kind)
   192  			}
   193  		}
   194  		if len(opt.AllGVKs) == 0 {
   195  			opt.AllGVKs = []schema.GroupVersionKind{
   196  				appsv1.SchemeGroupVersion.WithKind("Deployment"),
   197  				appsv1.SchemeGroupVersion.WithKind("StatefulSet"),
   198  				appsv1.SchemeGroupVersion.WithKind("DaemonSet"),
   199  			}
   200  			_, _ = opt.Out.Write([]byte("No arguments specified, adopt all Deployment/StatefulSet/DaemonSet resources by default\n"))
   201  		}
   202  	}
   203  	if opt.AdoptTemplateFile != "" {
   204  		bs, err := os.ReadFile(opt.AdoptTemplateFile)
   205  		if err != nil {
   206  			return fmt.Errorf("failed to load file %s", opt.AdoptTemplateFile)
   207  		}
   208  		opt.AdoptTemplate = string(bs)
   209  	} else {
   210  		opt.AdoptTemplate = defaultAdoptTemplate
   211  	}
   212  	if opt.ResourceTopologyRuleFile != "" {
   213  		bs, err := os.ReadFile(opt.ResourceTopologyRuleFile)
   214  		if err != nil {
   215  			return fmt.Errorf("failed to load file %s", opt.ResourceTopologyRuleFile)
   216  		}
   217  		opt.ResourceTopologyRule = string(bs)
   218  	} else {
   219  		opt.ResourceTopologyRule = defaultResourceTopologyRule
   220  	}
   221  	opt.AppNamespace = velacmd.GetNamespace(f, cmd)
   222  	opt.AdoptTemplateCUEValue, err = cuex.CompileString(cmd.Context(), fmt.Sprintf("%s\n\n%s: %s", opt.AdoptTemplate, adoptCUETempVal, adoptCUETempFunc))
   223  	if err != nil {
   224  		return fmt.Errorf("failed to compile template: %w", err)
   225  	}
   226  	switch opt.Type {
   227  	case adoptTypeNative:
   228  		if opt.Recycle {
   229  			return fmt.Errorf("native resource adoption does not support --recycle flag")
   230  		}
   231  	case adoptTypeHelm:
   232  		if len(opt.HelmDriver) == 0 {
   233  			opt.HelmDriver = os.Getenv(helmDriverEnvKey)
   234  		}
   235  		if len(opt.HelmDriver) == 0 {
   236  			opt.HelmDriver = defaultHelmDriver
   237  		}
   238  		actionConfig := new(action.Configuration)
   239  		opt.HelmReleaseNamespace = opt.AppNamespace
   240  		if err := actionConfig.Init(
   241  			util.NewRestConfigGetterByConfig(f.Config(), opt.HelmReleaseNamespace),
   242  			opt.HelmReleaseNamespace,
   243  			opt.HelmDriver,
   244  			klog.Infof); err != nil {
   245  			return err
   246  		}
   247  		opt.HelmConfig = actionConfig
   248  	default:
   249  		return fmt.Errorf("invalid adopt type: %s, available types: [%s]", opt.Type, strings.Join(adoptTypes, ", "))
   250  	}
   251  	if slices.Index(adoptModes, opt.Mode) < 0 {
   252  		return fmt.Errorf("invalid adopt mode: %s, available modes: [%s]", opt.Mode, strings.Join(adoptModes, ", "))
   253  	}
   254  	if opt.Recycle && !opt.Apply {
   255  		return fmt.Errorf("old data can only be recycled when the adoption application is applied")
   256  	}
   257  	return nil
   258  }
   259  
   260  // MultipleRun .
   261  func (opt *AdoptOptions) MultipleRun(f velacmd.Factory, cmd *cobra.Command) error {
   262  	resources := make([][]*unstructured.Unstructured, 0)
   263  	releases := make([]*release.Release, 0)
   264  	var err error
   265  	ctx := context.Background()
   266  
   267  	matchLabels := metav1.LabelSelector{
   268  		MatchExpressions: []metav1.LabelSelectorRequirement{
   269  			{
   270  				Key:      oam.LabelAppName,
   271  				Operator: metav1.LabelSelectorOpDoesNotExist,
   272  			},
   273  		},
   274  	}
   275  	selector, err := metav1.LabelSelectorAsSelector(&matchLabels)
   276  	if err != nil {
   277  		return err
   278  	}
   279  
   280  	switch opt.Type {
   281  	case adoptTypeNative:
   282  		for _, gvk := range opt.AllGVKs {
   283  			list := &unstructured.UnstructuredList{}
   284  			list.SetGroupVersionKind(gvk)
   285  			if err := f.Client().List(ctx, list, &client.ListOptions{Namespace: opt.AppNamespace, LabelSelector: selector}); err != nil {
   286  				apiVersion, kind := gvk.ToAPIVersionAndKind()
   287  				_, _ = fmt.Fprintf(opt.Out, "Warning: failed to list resources from %s/%s: %s\n", apiVersion, kind, err.Error())
   288  				continue
   289  			}
   290  			dedup := make([]k8s.ResourceIdentifier, 0)
   291  			for _, item := range list.Items {
   292  				engine := resourcetopology.New(opt.ResourceTopologyRule)
   293  				itemIdentifier := k8s.ResourceIdentifier{
   294  					Name:       item.GetName(),
   295  					Namespace:  item.GetNamespace(),
   296  					Kind:       item.GetKind(),
   297  					APIVersion: item.GetAPIVersion(),
   298  				}
   299  				if velaslices.Contains(dedup, itemIdentifier) {
   300  					continue
   301  				}
   302  				firstElement := item
   303  				r := []*unstructured.Unstructured{&firstElement}
   304  				peers, err := engine.GetPeerResources(ctx, itemIdentifier)
   305  				if err != nil {
   306  					_, _ = fmt.Fprintf(opt.Out, "Warning: failed to get peer resources for %s/%s: %s\n", itemIdentifier.APIVersion, itemIdentifier.Kind, err.Error())
   307  					resources = append(resources, r)
   308  					continue
   309  				}
   310  				dedup = append(dedup, peers...)
   311  				for _, peer := range peers {
   312  					gvk, err := k8s.GetGVKFromResource(peer)
   313  					if err != nil {
   314  						_, _ = fmt.Fprintf(opt.Out, "Warning: failed to get gvk from resource %s/%s: %s\n", peer.APIVersion, peer.Kind, err.Error())
   315  						continue
   316  					}
   317  					peerResource := &unstructured.Unstructured{}
   318  					peerResource.SetGroupVersionKind(gvk)
   319  					if err := f.Client().Get(ctx, apitypes.NamespacedName{Namespace: peer.Namespace, Name: peer.Name}, peerResource); err != nil {
   320  						_, _ = fmt.Fprintf(opt.Out, "Warning: failed to get resource %s/%s: %s\n", peer.Namespace, peer.Name, err.Error())
   321  						continue
   322  					}
   323  					r = append(r, peerResource)
   324  				}
   325  				resources = append(resources, r)
   326  			}
   327  		}
   328  	case adoptTypeHelm:
   329  		releases, err = opt.HelmConfig.Releases.List(func(release *release.Release) bool {
   330  			return true
   331  		})
   332  		if err != nil {
   333  			return err
   334  		}
   335  	}
   336  	for _, r := range resources {
   337  		opt.Resources = r
   338  		opt.AppName = r[0].GetName()
   339  		opt.AppNamespace = r[0].GetNamespace()
   340  		if err := opt.Run(f, cmd); err != nil {
   341  			_, _ = fmt.Fprintf(opt.Out, "Error: failed to adopt %s/%s: %s", opt.AppNamespace, opt.AppName, err.Error())
   342  			continue
   343  		}
   344  	}
   345  	for _, r := range releases {
   346  		opt.AppName = r.Name
   347  		opt.AppNamespace = r.Namespace
   348  		opt.HelmReleaseName = r.Name
   349  		opt.HelmReleaseNamespace = r.Namespace
   350  		// TODO(fog): filter the helm that already adopted by vela
   351  		if err := opt.loadHelm(); err != nil {
   352  			_, _ = fmt.Fprintf(opt.Out, "Error: failed to load helm for %s/%s: %s", opt.AppNamespace, opt.AppName, err.Error())
   353  			continue
   354  		}
   355  		if err := opt.Run(f, cmd); err != nil {
   356  			_, _ = fmt.Fprintf(opt.Out, "Error: failed to adopt %s/%s: %s", opt.AppNamespace, opt.AppName, err.Error())
   357  			continue
   358  		}
   359  	}
   360  	return nil
   361  }
   362  
   363  // Complete autofill fields in opts
   364  func (opt *AdoptOptions) Complete(f velacmd.Factory, cmd *cobra.Command, args []string) (err error) {
   365  	opt.AppNamespace = velacmd.GetNamespace(f, cmd)
   366  	switch opt.Type {
   367  	case adoptTypeNative:
   368  		for _, arg := range args {
   369  			or, err := opt.parseResourceRef(f, cmd, arg)
   370  			if err != nil {
   371  				return err
   372  			}
   373  			opt.NativeResourceRefs = append(opt.NativeResourceRefs, or)
   374  		}
   375  		if opt.AppName == "" && velaslices.All(opt.NativeResourceRefs, func(ref *resourceRef) bool {
   376  			return ref.Name == opt.NativeResourceRefs[0].Name
   377  		}) {
   378  			opt.AppName = opt.NativeResourceRefs[0].Name
   379  		}
   380  		if opt.AppNamespace == "" {
   381  			opt.AppNamespace = opt.NativeResourceRefs[0].Namespace
   382  		}
   383  		if err := opt.loadNative(f, cmd); err != nil {
   384  			return err
   385  		}
   386  	case adoptTypeHelm:
   387  		if len(args) > 0 {
   388  			opt.HelmReleaseName = args[0]
   389  		}
   390  		if len(args) > 1 {
   391  			return fmt.Errorf("helm type adoption only support one helm release by far")
   392  		}
   393  		if opt.AppName == "" {
   394  			opt.AppName = opt.HelmReleaseName
   395  		}
   396  		if err := opt.loadHelm(); err != nil {
   397  			return err
   398  		}
   399  	default:
   400  	}
   401  	if opt.AppName != "" {
   402  		app := &v1beta1.Application{}
   403  		err := f.Client().Get(cmd.Context(), apitypes.NamespacedName{Namespace: opt.AppNamespace, Name: opt.AppName}, app)
   404  		if err == nil && app != nil {
   405  			if !opt.Yes && opt.Apply {
   406  				userInput := NewUserInput()
   407  				confirm := userInput.AskBool(
   408  					fmt.Sprintf("Application '%s' already exists, apply will override the existing app with the adopted one, please confirm [Y/n]: ", opt.AppName),
   409  					&UserInputOptions{AssumeYes: false})
   410  				if !confirm {
   411  					return nil
   412  				}
   413  			}
   414  		}
   415  	}
   416  	opt.AdoptTemplateCUEValue, err = cuex.CompileString(cmd.Context(), fmt.Sprintf("%s\n\n%s: %s", opt.AdoptTemplate, adoptCUETempVal, adoptCUETempFunc))
   417  	if err != nil {
   418  		return fmt.Errorf("failed to compile cue template: %w", err)
   419  	}
   420  	return err
   421  }
   422  
   423  // Validate if opts is valid
   424  func (opt *AdoptOptions) Validate() error {
   425  	switch opt.Type {
   426  	case adoptTypeNative:
   427  		if len(opt.NativeResourceRefs) == 0 {
   428  			return fmt.Errorf("at least one resource should be specified")
   429  		}
   430  		if opt.AppName == "" {
   431  			return fmt.Errorf("app-name flag must be set for native resource adoption when multiple resources have different names")
   432  		}
   433  	case adoptTypeHelm:
   434  		if len(opt.HelmReleaseName) == 0 {
   435  			return fmt.Errorf("helm release name must not be empty")
   436  		}
   437  	}
   438  	return nil
   439  }
   440  
   441  func (opt *AdoptOptions) loadNative(f velacmd.Factory, cmd *cobra.Command) error {
   442  	for _, ref := range opt.NativeResourceRefs {
   443  		obj := &unstructured.Unstructured{}
   444  		obj.SetGroupVersionKind(ref.GroupVersionKind)
   445  		if err := f.Client().Get(multicluster.WithCluster(cmd.Context(), ref.Cluster), apitypes.NamespacedName{Namespace: ref.Namespace, Name: ref.Name}, obj); err != nil {
   446  			return fmt.Errorf("fail to get resource for %s: %w", ref.Arg, err)
   447  		}
   448  		_ = k8s.AddLabel(obj, oam.LabelAppCluster, ref.Cluster)
   449  		opt.Resources = append(opt.Resources, obj)
   450  	}
   451  	return nil
   452  }
   453  
   454  func (opt *AdoptOptions) loadHelm() error {
   455  	opt.HelmStore = opt.HelmConfig.Releases
   456  	revisions, err := opt.HelmStore.History(opt.HelmReleaseName)
   457  	if err != nil {
   458  		return fmt.Errorf("helm release %s/%s not loaded: %w", opt.HelmReleaseNamespace, opt.HelmReleaseName, err)
   459  	}
   460  	if len(revisions) == 0 {
   461  		return fmt.Errorf("helm release %s/%s not found", opt.HelmReleaseNamespace, opt.HelmReleaseName)
   462  	}
   463  	releaseutil.SortByRevision(revisions)
   464  	opt.HelmRelease = revisions[len(revisions)-1]
   465  	opt.HelmReleaseRevisions = revisions
   466  	manifests := releaseutil.SplitManifests(opt.HelmRelease.Manifest)
   467  	var objs []*unstructured.Unstructured
   468  	for _, val := range manifests {
   469  		obj := &unstructured.Unstructured{}
   470  		if err = yaml.Unmarshal([]byte(val), obj); err != nil {
   471  			klog.Warningf("unable to decode object %s: %s", val, err)
   472  			continue
   473  		}
   474  		_ = k8s.AddLabel(obj, oam.LabelAppCluster, multicluster.Local)
   475  		objs = append(objs, obj)
   476  	}
   477  	opt.Resources = objs
   478  	return nil
   479  }
   480  
   481  func (opt *AdoptOptions) render() (*v1beta1.Application, error) {
   482  	app := &v1beta1.Application{}
   483  	val := opt.AdoptTemplateCUEValue.FillPath(cue.ParsePath(adoptCUETempVal+".$args"), opt)
   484  	bs, err := val.LookupPath(cue.ParsePath(adoptCUETempVal + ".$returns")).MarshalJSON()
   485  	if err != nil {
   486  		return nil, fmt.Errorf("failed to parse adoption template: %w", err)
   487  	}
   488  	if err = json.Unmarshal(bs, app); err != nil {
   489  		return nil, fmt.Errorf("failed to parse template $returns into application: %w", err)
   490  	}
   491  	if app.Name == "" {
   492  		app.Name = opt.AppName
   493  	}
   494  	if app.Namespace == "" {
   495  		app.Namespace = opt.AppNamespace
   496  	}
   497  	return app, nil
   498  }
   499  
   500  // Run collect resources, assemble into application and print/apply
   501  func (opt *AdoptOptions) Run(f velacmd.Factory, cmd *cobra.Command) error {
   502  	app, err := opt.render()
   503  	if err != nil {
   504  		return fmt.Errorf("failed to make adoption application for resources: %w", err)
   505  	}
   506  	if opt.Apply {
   507  		if err = apply.NewAPIApplicator(f.Client()).Apply(cmd.Context(), app); err != nil {
   508  			return fmt.Errorf("failed to apply application %s/%s: %w", app.Namespace, app.Name, err)
   509  		}
   510  		_, _ = fmt.Fprintf(opt.Out, "resources adopted in app %s/%s\n", app.Namespace, app.Name)
   511  	} else {
   512  		var bs []byte
   513  		if bs, err = yaml.Marshal(app); err != nil {
   514  			return fmt.Errorf("failed to encode application into YAML format: %w", err)
   515  		}
   516  		if opt.All {
   517  			_, _ = opt.Out.Write([]byte("\n---\n"))
   518  		}
   519  		_, _ = opt.Out.Write(bs)
   520  	}
   521  	if opt.Recycle && opt.Apply {
   522  		spinner := newTrackingSpinner("")
   523  		spinner.Writer = opt.Out
   524  		spinner.Start()
   525  		err = wait.PollImmediate(time.Second, time.Minute, func() (done bool, err error) {
   526  			_app := &v1beta1.Application{}
   527  			if err = f.Client().Get(cmd.Context(), client.ObjectKeyFromObject(app), _app); err != nil {
   528  				return false, err
   529  			}
   530  			spinner.UpdateCharSet([]string{fmt.Sprintf("waiting application %s/%s to be running, current status: %s", app.Namespace, app.Name, _app.Status.Phase)})
   531  			return _app.Status.Phase == common.ApplicationRunning, nil
   532  		})
   533  		spinner.Stop()
   534  		if err != nil {
   535  			return fmt.Errorf("failed to wait application %s/%s to be running: %w", app.Namespace, app.Name, err)
   536  		}
   537  		switch opt.Type {
   538  		case adoptTypeHelm:
   539  			for _, r := range opt.HelmReleaseRevisions {
   540  				if _, err = opt.HelmStore.Delete(r.Name, r.Version); err != nil {
   541  					return fmt.Errorf("failed to clean up helm release: %w", err)
   542  				}
   543  			}
   544  			_, _ = fmt.Fprintf(opt.Out, "successfully clean up old helm release\n")
   545  		default:
   546  		}
   547  	}
   548  	return nil
   549  }
   550  
   551  var (
   552  	adoptLong = templates.LongDesc(i18n.T(`
   553  		Adopt resources into applications
   554  
   555  		Adopt resources into a KubeVela application. This command is useful when you already
   556  		have resources applied in your Kubernetes cluster. These resources could be applied
   557  		natively or with other tools, such as Helm. This command will automatically find out
   558  		the resources to be adopted and assemble them into a new application which won't 
   559  		trigger any damage such as restart on the adoption.
   560  
   561  		There are two types of adoption supported by far, 'native' Kubernetes resources (by
   562  		default) and 'helm' releases.
   563  		1. For 'native' type, you can specify a list of resources you want to adopt in the
   564  		application. Only resources in local cluster are supported for now.
   565  		2. For 'helm' type, you can specify a helm release name. This helm release should
   566  		be already published in the local cluster. The command will find the resources
   567  		managed by the helm release and convert them into an adoption application.
   568  
   569  		There are two working mechanism (called 'modes' here) for the adoption by far, 
   570  		'read-only' mode (by default) and 'take-over' mode.
   571  		1. In 'read-only' mode, adopted resources will not be touched. You can leverage vela 
   572  		tools (like Vela CLI or VelaUX) to observe those resources and attach traits to add 
   573  		new capabilities. The adopted resources will not be recycled or updated. This mode
   574  		is recommended if you still want to keep using other tools to manage resources updates
   575  		or deletion, like Helm.
   576  		2. In 'take-over' mode, adopted resources are completely managed by application which 
   577  		means they can be modified. You can use traits or directly modify the component to make
   578  		edits to those resources. This mode can be helpful if you want to migrate existing 
   579  		resources into KubeVela system and let KubeVela to handle the life-cycle of target
   580  		resources.
   581  
   582  		The adopted application can be customized. You can provide a CUE template file to
   583  		the command and make your own assemble rules for the adoption application. You can
   584  		refer to https://github.com/kubevela/kubevela/blob/master/references/cli/adopt-templates/default.cue
   585  		to see the default implementation of adoption rules.
   586  
   587  		If you want to adopt all resources with resource topology rule to Applications,
   588  		you can use: 'vela adopt --all'. The resource topology rule can be customized by
   589  		'--resource-topology-rule' flag.
   590  	`))
   591  	adoptExample = templates.Examples(i18n.T(`
   592  		# Native Resources Adoption
   593  
   594  		## Adopt resources into new application
   595  
   596  		## Adopt all resources to Applications with resource topology rule
   597  		## Use: vela adopt <resources-type> --all
   598  		vela adopt --all
   599  		vela adopt deployment --all --resource-topology-rule myrule.cue
   600  
   601  		## Use: vela adopt <resources-type>[/<resource-cluster>][/<resource-namespace>]/<resource-name> <resources-type>[/<resource-cluster>][/<resource-namespace>]/<resource-name> ...
   602  		vela adopt deployment/my-app configmap/my-app
   603  
   604  		## Adopt resources into new application with specified app name
   605  		vela adopt deployment/my-deploy configmap/my-config --app-name my-app
   606  
   607  		## Adopt resources into new application in specified namespace
   608  		vela adopt deployment/my-app configmap/my-app -n demo
   609  
   610  		## Adopt resources into new application across multiple namespace
   611  		vela adopt deployment/ns-1/my-app configmap/ns-2/my-app
   612  
   613  		## Adopt resources into new application with take-over mode
   614  		vela adopt deployment/my-app configmap/my-app --mode take-over
   615  
   616  		## Adopt resources into new application and apply it into cluster
   617  		vela adopt deployment/my-app configmap/my-app --apply
   618  
   619  		-----------------------------------------------------------
   620  
   621  		# Helm Chart Adoption
   622  
   623  		## Adopt all helm releases to Applications with resource topology rule
   624  		## Use: vela adopt <resources-type> --all
   625  		vela adopt --all --type helm
   626  		vela adopt my-chart --all --resource-topology-rule myrule.cue --type helm
   627  
   628  		## Adopt resources in a deployed helm chart
   629  		vela adopt my-chart -n my-namespace --type helm
   630  		
   631  		## Adopt resources in a deployed helm chart with take-over mode
   632  		vela adopt my-chart --type helm --mode take-over
   633  
   634  		## Adopt resources in a deployed helm chart in an application and apply it into cluster
   635  		vela adopt my-chart --type helm --apply
   636  
   637  		## Adopt resources in a deployed helm chart in an application, apply it into cluster, and recycle the old helm release after the adoption application successfully runs
   638  		vela adopt my-chart --type helm --apply --recycle
   639  
   640  		-----------------------------------------------------------
   641  
   642  		## Customize your adoption rules
   643  		vela adopt my-chart -n my-namespace --type helm --adopt-template my-rules.cue
   644  	`))
   645  )
   646  
   647  // NewAdoptCommand command for adopt resources into KubeVela Application
   648  func NewAdoptCommand(f velacmd.Factory, order string, streams util.IOStreams) *cobra.Command {
   649  	o := &AdoptOptions{
   650  		Type:      adoptTypeNative,
   651  		Mode:      adoptModeReadOnly,
   652  		IOStreams: streams,
   653  	}
   654  	cmd := &cobra.Command{
   655  		Use:     "adopt",
   656  		Short:   i18n.T("Adopt resources into new application."),
   657  		Long:    adoptLong,
   658  		Example: adoptExample,
   659  		Annotations: map[string]string{
   660  			types.TagCommandType:  types.TypeCD,
   661  			types.TagCommandOrder: order,
   662  		},
   663  		Run: func(cmd *cobra.Command, args []string) {
   664  			cmdutil.CheckErr(o.Init(f, cmd, args))
   665  			if o.All {
   666  				cmdutil.CheckErr(o.MultipleRun(f, cmd))
   667  				return
   668  			}
   669  			cmdutil.CheckErr(o.Complete(f, cmd, args))
   670  			cmdutil.CheckErr(o.Validate())
   671  			cmdutil.CheckErr(o.Run(f, cmd))
   672  		},
   673  	}
   674  	cmd.Flags().StringVarP(&o.Type, "type", "t", o.Type, fmt.Sprintf("The type of adoption. Available values: [%s]", strings.Join(adoptTypes, ", ")))
   675  	cmd.Flags().StringVarP(&o.Mode, "mode", "m", o.Mode, fmt.Sprintf("The mode of adoption. Available values: [%s]", strings.Join(adoptModes, ", ")))
   676  	cmd.Flags().StringVarP(&o.AppName, "app-name", "", o.AppName, "The name of application for adoption. If empty for helm type adoption, it will inherit the helm chart's name.")
   677  	cmd.Flags().StringVarP(&o.AdoptTemplateFile, "adopt-template", "", o.AdoptTemplate, "The CUE template for adoption. If not provided, the default template will be used when --auto is switched on.")
   678  	cmd.Flags().StringVarP(&o.ResourceTopologyRuleFile, "resource-topology-rule", "", o.ResourceTopologyRule, "The CUE template for specify the rule of the resource topology. If not provided, the default rule will be used.")
   679  	cmd.Flags().StringVarP(&o.HelmDriver, "driver", "d", o.HelmDriver, "The storage backend of helm adoption. Only take effect when --type=helm.")
   680  	cmd.Flags().BoolVarP(&o.Apply, "apply", "", o.Apply, "If true, the application for adoption will be applied. Otherwise, it will only be printed.")
   681  	cmd.Flags().BoolVarP(&o.Recycle, "recycle", "", o.Recycle, "If true, when the adoption application is successfully applied, the old storage (like Helm secret) will be recycled.")
   682  	cmd.Flags().BoolVarP(&o.Yes, "yes", "y", o.Yes, "Skip confirmation prompt")
   683  	cmd.Flags().BoolVarP(&o.All, "all", "", o.All, "Adopt all resources in the namespace")
   684  	return velacmd.NewCommandBuilder(f, cmd).
   685  		WithNamespaceFlag().
   686  		WithResponsiveWriter().
   687  		Build()
   688  }