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

     1  /*
     2  Copyright 2021 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  	"bufio"
    21  	"bytes"
    22  	"context"
    23  	"fmt"
    24  	"io"
    25  	"os"
    26  	"os/exec"
    27  	"path"
    28  	"path/filepath"
    29  	"reflect"
    30  	"regexp"
    31  	"strconv"
    32  	"strings"
    33  	"time"
    34  
    35  	"cuelang.org/go/cue"
    36  	"cuelang.org/go/cue/cuecontext"
    37  	"cuelang.org/go/encoding/gocode/gocodec"
    38  	"github.com/kubevela/workflow/pkg/cue/model/sets"
    39  	crossplane "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
    40  	"github.com/pkg/errors"
    41  	"github.com/spf13/cobra"
    42  	"gopkg.in/yaml.v3"
    43  	errors2 "k8s.io/apimachinery/pkg/api/errors"
    44  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    45  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    46  	"k8s.io/apimachinery/pkg/runtime/serializer/json"
    47  	types2 "k8s.io/apimachinery/pkg/types"
    48  	"k8s.io/klog/v2"
    49  	"sigs.k8s.io/controller-runtime/pkg/client"
    50  
    51  	commontype "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    52  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    53  	"github.com/oam-dev/kubevela/apis/types"
    54  	"github.com/oam-dev/kubevela/pkg/cue/process"
    55  	pkgdef "github.com/oam-dev/kubevela/pkg/definition"
    56  	"github.com/oam-dev/kubevela/pkg/definition/gen_sdk"
    57  	"github.com/oam-dev/kubevela/pkg/utils"
    58  	addonutil "github.com/oam-dev/kubevela/pkg/utils/addon"
    59  	"github.com/oam-dev/kubevela/pkg/utils/common"
    60  	"github.com/oam-dev/kubevela/pkg/utils/filters"
    61  	"github.com/oam-dev/kubevela/pkg/utils/util"
    62  	"github.com/oam-dev/kubevela/references/cuegen"
    63  	providergen "github.com/oam-dev/kubevela/references/cuegen/generators/provider"
    64  	"github.com/oam-dev/kubevela/references/docgen"
    65  )
    66  
    67  const (
    68  	// HelmChartNamespacePlaceholder is used as a placeholder for rendering definitions into helm chart format
    69  	HelmChartNamespacePlaceholder = "###HELM_NAMESPACE###"
    70  	// HelmChartFormatEnvName is the name of the environment variable to enable render helm chart format YAML
    71  	HelmChartFormatEnvName = "AS_HELM_CHART"
    72  )
    73  
    74  // DefinitionCommandGroup create the command group for `vela def` command to manage definitions
    75  func DefinitionCommandGroup(c common.Args, order string, ioStreams util.IOStreams) *cobra.Command {
    76  	cmd := &cobra.Command{
    77  		Use:   "def",
    78  		Short: "Manage definitions.",
    79  		Long:  "Manage X-Definitions for extension.",
    80  		Annotations: map[string]string{
    81  			types.TagCommandOrder: order,
    82  			types.TagCommandType:  types.TypeExtension,
    83  		},
    84  	}
    85  	cmd.SetOut(ioStreams.Out)
    86  	cmd.AddCommand(
    87  		NewDefinitionGetCommand(c),
    88  		NewDefinitionListCommand(c),
    89  		NewDefinitionEditCommand(c),
    90  		NewDefinitionRenderCommand(c),
    91  		NewDefinitionApplyCommand(c, ioStreams),
    92  		NewDefinitionDelCommand(c),
    93  		NewDefinitionInitCommand(c),
    94  		NewDefinitionValidateCommand(c),
    95  		NewDefinitionDocGenCommand(c, ioStreams),
    96  		NewCapabilityShowCommand(c, "", ioStreams),
    97  		NewDefinitionGenAPICommand(c),
    98  		NewDefinitionGenCUECommand(c, ioStreams),
    99  		NewDefinitionGenDocCommand(c, ioStreams),
   100  	)
   101  	return cmd
   102  }
   103  
   104  func getPrompt(cmd *cobra.Command, reader *bufio.Reader, description string, prompt string, validate func(string) error) (string, error) {
   105  	cmd.Printf(description)
   106  	for {
   107  		cmd.Printf(prompt)
   108  		resp, err := reader.ReadString('\n')
   109  		resp = strings.TrimSpace(resp)
   110  		if err != nil {
   111  			return "", errors.Wrapf(err, "failed to read user response")
   112  		}
   113  		if validate == nil {
   114  			return resp, nil
   115  		}
   116  		err = validate(resp)
   117  		if err != nil {
   118  			cmd.Println(err)
   119  		} else {
   120  			return resp, nil
   121  		}
   122  	}
   123  }
   124  
   125  // nolint:staticcheck
   126  func buildTemplateFromYAML(templateYAML string, def *pkgdef.Definition) error {
   127  	templateYAMLBytes, err := utils.ReadRemoteOrLocalPath(templateYAML, false)
   128  	if err != nil {
   129  		return errors.Wrapf(err, "failed to get template YAML file %s", templateYAML)
   130  	}
   131  	yamlStrings := regexp.MustCompile(`\n---[^\n]*\n`).Split(string(templateYAMLBytes), -1)
   132  	templateObject := map[string]interface{}{
   133  		process.OutputFieldName:    map[string]interface{}{},
   134  		process.OutputsFieldName:   map[string]interface{}{},
   135  		process.ParameterFieldName: map[string]interface{}{},
   136  	}
   137  	for index, yamlString := range yamlStrings {
   138  		var yamlObject map[string]interface{}
   139  		if err = yaml.Unmarshal([]byte(yamlString), &yamlObject); err != nil {
   140  			return errors.Wrapf(err, "failed to unmarshal template yaml file")
   141  		}
   142  		if index == 0 {
   143  			templateObject[process.OutputFieldName] = yamlObject
   144  		} else {
   145  			name, _, _ := unstructured.NestedString(yamlObject, "metadata", "name")
   146  			if name == "" {
   147  				name = fmt.Sprintf("output-%d", index)
   148  			}
   149  			templateObject[process.OutputsFieldName].(map[string]interface{})[name] = yamlObject
   150  		}
   151  	}
   152  	codec := gocodec.New((*cue.Runtime)(cuecontext.New()), &gocodec.Config{})
   153  	val, err := codec.Decode(templateObject)
   154  	if err != nil {
   155  		return errors.Wrapf(err, "failed to decode template into cue")
   156  	}
   157  	templateString, err := sets.ToString(val)
   158  	if err != nil {
   159  		return errors.Wrapf(err, "failed to encode template cue string")
   160  	}
   161  	err = unstructured.SetNestedField(def.Object, templateString, pkgdef.DefinitionTemplateKeys...)
   162  	if err != nil {
   163  		return errors.Wrapf(err, "failed to merge template cue string")
   164  	}
   165  	return nil
   166  }
   167  
   168  // NewDefinitionInitCommand create the `vela def init` command to help user initialize a definition locally
   169  func NewDefinitionInitCommand(_ common.Args) *cobra.Command {
   170  	cmd := &cobra.Command{
   171  		Use:   "init DEF_NAME",
   172  		Short: "Init a new definition",
   173  		Long: "Init a new definition with given arguments or interactively\n* We support parsing a single YAML file (like kubernetes objects) into the cue-style template. \n" +
   174  			"However, we do not support variables in YAML file currently, which prevents users from directly feeding files like helm chart directly. \n" +
   175  			"We may introduce such features in the future.",
   176  		Example: "# Command below initiate an empty TraitDefinition named my-ingress\n" +
   177  			"> vela def init my-ingress -t trait --desc \"My ingress trait definition.\" > ./my-ingress.cue\n" +
   178  			"# Command below initiate a definition named my-def interactively and save it to ./my-def.cue\n" +
   179  			"> vela def init my-def -i --output ./my-def.cue\n" +
   180  			"# Command below initiate a ComponentDefinition named my-webservice with the template parsed from ./template.yaml.\n" +
   181  			"> vela def init my-webservice -i --template-yaml ./template.yaml\n" +
   182  			"# Initiate a Terraform ComponentDefinition named vswitch from Github for Alibaba Cloud.\n" +
   183  			"> vela def init vswitch --type component --provider alibaba --desc xxx --git https://github.com/kubevela-contrib/terraform-modules.git --path alibaba/vswitch\n" +
   184  			"# Initiate a Terraform ComponentDefinition named redis from local file for AWS.\n" +
   185  			"> vela def init redis --type component --provider aws --desc \"Terraform configuration for AWS Redis\" --local redis.tf",
   186  		Args: cobra.ExactArgs(1),
   187  		RunE: func(cmd *cobra.Command, args []string) error {
   188  			var defStr string
   189  			definitionType, err := cmd.Flags().GetString(FlagType)
   190  			if err != nil {
   191  				return errors.Wrapf(err, "failed to get `%s`", FlagType)
   192  			}
   193  			alias, err := cmd.Flags().GetString(FlagAlias)
   194  			if err != nil {
   195  				return errors.Wrapf(err, "failed to get `%s`", FlagAlias)
   196  			}
   197  			desc, err := cmd.Flags().GetString(FlagDescription)
   198  			if err != nil {
   199  				return errors.Wrapf(err, "failed to get `%s`", FlagDescription)
   200  			}
   201  			templateYAML, err := cmd.Flags().GetString(FlagTemplateYAML)
   202  			if err != nil {
   203  				return errors.Wrapf(err, "failed to get `%s`", FlagTemplateYAML)
   204  			}
   205  			output, err := cmd.Flags().GetString(FlagOutput)
   206  			if err != nil {
   207  				return errors.Wrapf(err, "failed to get `%s`", FlagOutput)
   208  			}
   209  			interactive, err := cmd.Flags().GetBool(FlagInteractive)
   210  			if err != nil {
   211  				return errors.Wrapf(err, "failed to get `%s`", FlagInteractive)
   212  			}
   213  
   214  			if interactive {
   215  				reader := bufio.NewReader(cmd.InOrStdin())
   216  				if definitionType == "" {
   217  					if definitionType, err = getPrompt(cmd, reader, "Please choose one definition type from the following values: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", ")+"\n", "> Definition type: ", func(resp string) error {
   218  						if _, ok := pkgdef.DefinitionTypeToKind[resp]; !ok {
   219  							return errors.New("invalid definition type")
   220  						}
   221  						return nil
   222  					}); err != nil {
   223  						return err
   224  					}
   225  				}
   226  				if desc == "" {
   227  					if desc, err = getPrompt(cmd, reader, "", "> Definition description: ", nil); err != nil {
   228  						return err
   229  					}
   230  				}
   231  				if templateYAML == "" {
   232  					if templateYAML, err = getPrompt(cmd, reader, "Please enter the location the template YAML file to build definition. Leave it empty to generate default template.\n", "> Definition template filename: ", func(resp string) error {
   233  						if resp == "" {
   234  							return nil
   235  						}
   236  						_, err = os.Stat(resp)
   237  						return err
   238  					}); err != nil {
   239  						return err
   240  					}
   241  				}
   242  				if output == "" {
   243  					if output, err = getPrompt(cmd, reader, "Please enter the output location of the generated definition. Leave it empty to print definition to stdout.\n", "> Definition output filename: ", nil); err != nil {
   244  						return err
   245  					}
   246  				}
   247  			}
   248  
   249  			kind, ok := pkgdef.DefinitionTypeToKind[definitionType]
   250  			if !ok {
   251  				return errors.New("invalid definition type")
   252  			}
   253  
   254  			name := args[0]
   255  			provider, err := cmd.Flags().GetString(FlagProvider)
   256  			if err != nil {
   257  				return errors.Wrapf(err, "failed to get `%s`", FlagProvider)
   258  			}
   259  			if provider != "" {
   260  				defStr, err = generateTerraformTypedComponentDefinition(cmd, name, kind, provider, desc)
   261  				if err != nil {
   262  					return errors.Wrapf(err, "failed to generate Terraform typed component definition")
   263  				}
   264  			} else {
   265  				def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
   266  				def.SetGVK(kind)
   267  				def.SetName(name)
   268  				def.SetAnnotations(map[string]string{
   269  					pkgdef.DescriptionKey: desc,
   270  					pkgdef.AliasKey:       alias,
   271  				})
   272  				def.SetLabels(map[string]string{})
   273  				def.Object["spec"] = pkgdef.GetDefinitionDefaultSpec(def.GetKind())
   274  				if templateYAML != "" {
   275  					if err = buildTemplateFromYAML(templateYAML, &def); err != nil {
   276  						return err
   277  					}
   278  				}
   279  				defStr, err = def.ToCUEString()
   280  				if err != nil {
   281  					return errors.Wrapf(err, "failed to generate cue string")
   282  				}
   283  			}
   284  			if output != "" {
   285  				if err = os.WriteFile(path.Clean(output), []byte(defStr), 0600); err != nil {
   286  					return errors.Wrapf(err, "failed to write definition into %s", output)
   287  				}
   288  				cmd.Printf("Definition written to %s\n", output)
   289  			} else if _, err = cmd.OutOrStdout().Write([]byte(defStr + "\n")); err != nil {
   290  				return errors.Wrapf(err, "failed to write out cue string")
   291  			}
   292  			return nil
   293  		},
   294  	}
   295  	cmd.Flags().StringP(FlagType, "t", "", "Specify the type of the new definition. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", "))
   296  	cmd.Flags().StringP(FlagDescription, "d", "", "Specify the description of the new definition.")
   297  	cmd.Flags().StringP(FlagAlias, "a", "", "Specify the alias of the new definition.")
   298  	cmd.Flags().StringP(FlagTemplateYAML, "f", "", "Specify the template yaml file that definition will use to build the schema. If empty, a default template for the given definition type will be used.")
   299  	cmd.Flags().StringP(FlagOutput, "o", "", "Specify the output path of the generated definition. If empty, the definition will be printed in the console.")
   300  	cmd.Flags().BoolP(FlagInteractive, "i", false, "Specify whether use interactive process to help generate definitions.")
   301  	cmd.Flags().StringP(FlagProvider, "p", "", "Specify which provider the cloud resource definition belongs to. Only `alibaba`, `aws`, `azure`, `gcp`, `baidu`, `tencent`, `elastic`, `ucloud`, `vsphere` are supported.")
   302  	cmd.Flags().StringP(FlagGit, "", "", "Specify which git repository the configuration(HCL) is stored in. Valid when --provider/-p is set.")
   303  	cmd.Flags().StringP(FlagLocal, "", "", "Specify the local path of the configuration(HCL) file. Valid when --provider/-p is set.")
   304  	cmd.Flags().StringP(FlagPath, "", "", "Specify which path the configuration(HCL) is stored in the Git repository. Valid when --git is set.")
   305  	return cmd
   306  }
   307  
   308  func generateTerraformTypedComponentDefinition(cmd *cobra.Command, name, kind, provider, desc string) (string, error) {
   309  	if kind != v1beta1.ComponentDefinitionKind {
   310  		return "", errors.New("provider is only valid when the type of the definition is component")
   311  	}
   312  
   313  	switch provider {
   314  	case "aws", "azure", "alibaba", "tencent", "gcp", "baidu", "elastic", "ucloud", "vsphere", "huawei":
   315  		var terraform *commontype.Terraform
   316  
   317  		git, err := cmd.Flags().GetString(FlagGit)
   318  		if err != nil {
   319  			return "", errors.Wrapf(err, "failed to get `%s`", FlagGit)
   320  		}
   321  		local, err := cmd.Flags().GetString(FlagLocal)
   322  		if err != nil {
   323  			return "", errors.Wrapf(err, "failed to get `%s`", FlagLocal)
   324  		}
   325  		if git != "" && local != "" {
   326  			return "", errors.New("only one of --git and --local can be set")
   327  		}
   328  		gitPath, err := cmd.Flags().GetString(FlagPath)
   329  		if err != nil {
   330  			return "", errors.Wrapf(err, "failed to get `%s`", FlagPath)
   331  		}
   332  		if git != "" {
   333  			if !strings.HasPrefix(git, "https://") || !strings.HasSuffix(git, ".git") {
   334  				return "", errors.Errorf("invalid git url: %s", git)
   335  			}
   336  			terraform = &commontype.Terraform{
   337  				Configuration: git,
   338  				Type:          "remote",
   339  				Path:          gitPath,
   340  			}
   341  		} else if local != "" {
   342  			hcl, err := os.ReadFile(filepath.Clean(local))
   343  			if err != nil {
   344  				return "", errors.Wrapf(err, "failed to read Terraform configuration from file %s", local)
   345  			}
   346  			terraform = &commontype.Terraform{
   347  				Configuration: string(hcl),
   348  			}
   349  		}
   350  		def := v1beta1.ComponentDefinition{
   351  			TypeMeta: metav1.TypeMeta{
   352  				APIVersion: "core.oam.dev/v1beta1",
   353  				Kind:       "ComponentDefinition",
   354  			},
   355  			ObjectMeta: metav1.ObjectMeta{
   356  				Name:      fmt.Sprintf("%s-%s", provider, name),
   357  				Namespace: types.DefaultKubeVelaNS,
   358  				Annotations: map[string]string{
   359  					"definition.oam.dev/description": desc,
   360  				},
   361  				Labels: map[string]string{
   362  					"type": "terraform",
   363  				},
   364  			},
   365  			Spec: v1beta1.ComponentDefinitionSpec{
   366  				Workload: commontype.WorkloadTypeDescriptor{
   367  					Definition: commontype.WorkloadGVK{
   368  						APIVersion: "terraform.core.oam.dev/v1beta2",
   369  						Kind:       "Configuration",
   370  					},
   371  				},
   372  				Schematic: &commontype.Schematic{
   373  					Terraform: terraform,
   374  				},
   375  			},
   376  		}
   377  		if provider != "alibaba" {
   378  			def.Spec.Schematic.Terraform.ProviderReference = &crossplane.Reference{
   379  				Name:      provider,
   380  				Namespace: "default",
   381  			}
   382  		}
   383  		var out bytes.Buffer
   384  		err = json.NewSerializerWithOptions(json.DefaultMetaFactory, nil, nil, json.SerializerOptions{Yaml: true}).Encode(&def, &out)
   385  		if err != nil {
   386  			return "", errors.Wrapf(err, "failed to marshal component definition")
   387  		}
   388  		return out.String(), nil
   389  	default:
   390  		return "", errors.Errorf("Provider `%s` is not supported. Only `alibaba`, `aws`, `azure`, `gcp`, `baidu`, `tencent`, `elastic`, `ucloud`, `vsphere` are supported.", provider)
   391  	}
   392  }
   393  
   394  func getSingleDefinition(cmd *cobra.Command, definitionName string, client client.Client, definitionType string, namespace string) (*pkgdef.Definition, error) {
   395  	definitions, err := pkgdef.SearchDefinition(client, definitionType, namespace, filters.ByName(definitionName))
   396  	if err != nil {
   397  		return nil, err
   398  	}
   399  	if len(definitions) == 0 {
   400  		return nil, fmt.Errorf("definition not found")
   401  	}
   402  	if len(definitions) > 1 {
   403  		table := newUITable()
   404  		table.AddRow("NAME", "TYPE", "NAMESPACE", "DESCRIPTION")
   405  		for _, definition := range definitions {
   406  			desc := ""
   407  			if annotations := definition.GetAnnotations(); annotations != nil {
   408  				desc = annotations[pkgdef.DescriptionKey]
   409  			}
   410  			table.AddRow(definition.GetName(), definition.GetKind(), definition.GetNamespace(), desc)
   411  		}
   412  		cmd.Println(table)
   413  		return nil, fmt.Errorf("found %d definitions, please specify which one to select with more arguments", len(definitions))
   414  	}
   415  	return &pkgdef.Definition{Unstructured: definitions[0]}, nil
   416  }
   417  
   418  // getDefRevs will search for DefinitionRevisions with specified conditions.
   419  // Check SearchDefinitionRevisions for details.
   420  func getDefRevs(ctx context.Context, client client.Client, ns, defTypeStr, defName string, rev int64) ([]v1beta1.DefinitionRevision, error) {
   421  	defType, ok := pkgdef.StringToDefinitionType[defTypeStr]
   422  	// Empty definition type is intentionally allowed, to allow the user to match all definition types
   423  	if defTypeStr != "" && !ok {
   424  		return nil, fmt.Errorf("%s is not a valid type. Valid types are %v", defTypeStr, reflect.ValueOf(pkgdef.StringToDefinitionType).MapKeys())
   425  	}
   426  
   427  	return pkgdef.SearchDefinitionRevisions(ctx, client, ns, defName, defType, rev)
   428  }
   429  
   430  // printDefRevs will print DefinitionRevisions
   431  func printDefRevs(ctx context.Context, cmd *cobra.Command, client client.Client, ns, defTypeStr, defName string) error {
   432  	revs, err := getDefRevs(ctx, client, ns, defTypeStr, defName, 0)
   433  	if err != nil {
   434  		return err
   435  	}
   436  
   437  	table := newUITable()
   438  	table.AddRow("NAME", "REVISION", "TYPE", "HASH")
   439  	for _, rev := range revs {
   440  		table.AddRow(defName, rev.Spec.Revision, rev.Spec.DefinitionType, rev.Spec.RevisionHash)
   441  	}
   442  	cmd.Println(table)
   443  
   444  	return nil
   445  }
   446  
   447  // NewDefinitionGetCommand create the `vela def get` command to get definition from k8s
   448  func NewDefinitionGetCommand(c common.Args) *cobra.Command {
   449  	var listRevisions bool
   450  	var targetRevision string
   451  	cmd := &cobra.Command{
   452  		Use:   "get NAME",
   453  		Short: "Get definition",
   454  		Long:  "Get definition from kubernetes cluster",
   455  		Example: "# Command below will get the ComponentDefinition(or other definitions if exists) of webservice in all namespaces\n" +
   456  			"> vela def get webservice\n" +
   457  			"# Command below will get the TraitDefinition of annotations in namespace vela-system\n" +
   458  			"> vela def get annotations --type trait --namespace vela-system",
   459  		Args: cobra.ExactArgs(1),
   460  		RunE: func(cmd *cobra.Command, args []string) error {
   461  			definitionType, err := cmd.Flags().GetString(FlagType)
   462  			if err != nil {
   463  				return errors.Wrapf(err, "failed to get `%s`", FlagType)
   464  			}
   465  			namespace, err := cmd.Flags().GetString(FlagNamespace)
   466  			if err != nil {
   467  				return errors.Wrapf(err, "failed to get `%s`", Namespace)
   468  			}
   469  			k8sClient, err := c.GetClient()
   470  			if err != nil {
   471  				return errors.Wrapf(err, "failed to get k8s client")
   472  			}
   473  
   474  			if listRevisions {
   475  				return printDefRevs(context.Background(), cmd, k8sClient, namespace, definitionType, args[0])
   476  			}
   477  
   478  			var def *pkgdef.Definition
   479  
   480  			// Get history Definition from DefinitionRevisions
   481  			if targetRevision != "" {
   482  				// "v1", "1", both need to work
   483  				targetRevision = strings.TrimPrefix(targetRevision, "v")
   484  				ver, err := strconv.Atoi(targetRevision)
   485  				if err != nil {
   486  					return fmt.Errorf("invalid version: %w", err)
   487  				}
   488  
   489  				// Get the user-specified revision.
   490  				revs, err := getDefRevs(context.Background(), k8sClient, namespace, definitionType, args[0], int64(ver))
   491  				if err != nil {
   492  					return err
   493  				}
   494  				if len(revs) == 0 {
   495  					return fmt.Errorf("no %s with revision %s found in namespace %s", args[0], targetRevision, namespace)
   496  				}
   497  
   498  				// Now we have at least one DefinitionRevision (typically it will only be one).
   499  				// They all fit user's conditions. We will use the first one.
   500  				// Extract Definition from DefinitionRevision that we just got.
   501  				def, err = pkgdef.GetDefinitionFromDefinitionRevision(&revs[0])
   502  				if err != nil {
   503  					return err
   504  				}
   505  			} else {
   506  				def, err = getSingleDefinition(cmd, args[0], k8sClient, definitionType, namespace)
   507  				if err != nil {
   508  					return err
   509  				}
   510  			}
   511  
   512  			cueString, err := def.ToCUEString()
   513  			if err != nil {
   514  				return errors.Wrapf(err, "failed to get cue format definition")
   515  			}
   516  			if _, err = cmd.OutOrStdout().Write([]byte(cueString + "\n")); err != nil {
   517  				return errors.Wrapf(err, "failed to write out cue string")
   518  			}
   519  			return nil
   520  		},
   521  	}
   522  	cmd.Flags().StringP(FlagType, "t", "", "Specify which definition type to get. If empty, all types will be searched. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", "))
   523  	cmd.Flags().BoolVarP(&listRevisions, "revisions", "", false, "List revisions of the specified definition.")
   524  	cmd.Flags().StringVarP(&targetRevision, "revision", "r", "", "Get the specified version of a definition.")
   525  	cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
   526  	return cmd
   527  }
   528  
   529  // NewDefinitionDocGenCommand create the `vela def doc-gen` command to generate documentation of definitions
   530  func NewDefinitionDocGenCommand(c common.Args, ioStreams util.IOStreams) *cobra.Command {
   531  	var docPath, location, i18nPath string
   532  	cmd := &cobra.Command{
   533  		Use:   "doc-gen NAME",
   534  		Short: "Generate documentation for definitions",
   535  		Long:  "Generate documentation for definitions",
   536  		Example: "1. Generate documentation for ComponentDefinition webservice:\n" +
   537  			"> vela def doc-gen webservice -n vela-system\n" +
   538  			"2. Generate documentation for local CUE Definition file webservice.cue:\n" +
   539  			"> vela def doc-gen webservice.cue\n" +
   540  			"3. Generate documentation for local Cloud Resource Definition YAML alibaba-vpc.yaml:\n" +
   541  			"> vela def doc-gen alibaba-vpc.yaml\n",
   542  		Deprecated: "This command has been replaced by 'vela show' or 'vela def show'.",
   543  		RunE: func(cmd *cobra.Command, args []string) error {
   544  			if len(args) == 0 {
   545  				return fmt.Errorf("please specify definition name, cue file or a cloud resource definition yaml")
   546  			}
   547  			namespace, err := cmd.Flags().GetString(FlagNamespace)
   548  			if err != nil {
   549  				return errors.Wrapf(err, "failed to get `%s`", Namespace)
   550  			}
   551  			return ShowReferenceMarkdown(context.Background(), c, ioStreams, args[0], docPath, location, i18nPath, namespace, 0)
   552  
   553  		},
   554  	}
   555  	cmd.Flags().StringVarP(&docPath, "path", "p", "", "Specify the path for of the doc generated from definition.")
   556  	cmd.Flags().StringVarP(&location, "location", "l", "", "specify the location for of the doc generated from definition, now supported options 'zh', 'en'. ")
   557  	cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
   558  	cmd.Flags().StringVarP(&i18nPath, "i18n", "", "https://kubevela.io/reference-i18n.json", "specify the location for of the doc generated from definition, now supported options 'zh', 'en'. ")
   559  	return cmd
   560  }
   561  
   562  // NewDefinitionListCommand create the `vela def list` command to list definition from k8s
   563  func NewDefinitionListCommand(c common.Args) *cobra.Command {
   564  	cmd := &cobra.Command{
   565  		Use:   "list",
   566  		Short: "List definitions.",
   567  		Long:  "List definitions in kubernetes cluster.",
   568  		Example: "# Command below will list all definitions in all namespaces\n" +
   569  			"> vela def list\n" +
   570  			"# Command below will list all definitions in the vela-system namespace\n" +
   571  			"> vela def get annotations --type trait --namespace vela-system",
   572  		Args: cobra.ExactArgs(0),
   573  		RunE: func(cmd *cobra.Command, args []string) error {
   574  			definitionType, err := cmd.Flags().GetString(FlagType)
   575  			if err != nil {
   576  				return errors.Wrapf(err, "failed to get `%s`", FlagType)
   577  			}
   578  			namespace, err := cmd.Flags().GetString(FlagNamespace)
   579  			if err != nil {
   580  				return errors.Wrapf(err, "failed to get `%s`", Namespace)
   581  			}
   582  			addonName, err := cmd.Flags().GetString("from")
   583  			if err != nil {
   584  				return errors.Wrapf(err, "failed to get `%s`", "from")
   585  			}
   586  			k8sClient, err := c.GetClient()
   587  			if err != nil {
   588  				return errors.Wrapf(err, "failed to get k8s client")
   589  			}
   590  			definitions, err := pkgdef.SearchDefinition(k8sClient,
   591  				definitionType,
   592  				namespace,
   593  				filters.ByOwnerAddon(addonName))
   594  			if err != nil {
   595  				return err
   596  			}
   597  			if len(definitions) == 0 {
   598  				cmd.Println("No definition found.")
   599  				return nil
   600  			}
   601  			// Determine if there is a definition in the list from some addons
   602  			// This is used to tell if we want the SOURCE-ADDON column
   603  			showSourceAddon := false
   604  			for _, def := range definitions {
   605  				ownerRef := def.GetOwnerReferences()
   606  				if len(ownerRef) > 0 && strings.HasPrefix(ownerRef[0].Name, addonutil.AddonAppPrefix) {
   607  					showSourceAddon = true
   608  					break
   609  				}
   610  			}
   611  			table := newUITable()
   612  
   613  			// We only include SOURCE-ADDON if there is at least one definition from an addon
   614  			if showSourceAddon {
   615  				table.AddRow("NAME", "TYPE", "NAMESPACE", "SOURCE-ADDON", "DESCRIPTION")
   616  			} else {
   617  				table.AddRow("NAME", "TYPE", "NAMESPACE", "DESCRIPTION")
   618  			}
   619  
   620  			for _, definition := range definitions {
   621  				desc := ""
   622  				if annotations := definition.GetAnnotations(); annotations != nil {
   623  					desc = annotations[pkgdef.DescriptionKey]
   624  				}
   625  
   626  				// Do not show SOURCE-ADDON column
   627  				if !showSourceAddon {
   628  					table.AddRow(definition.GetName(), definition.GetKind(), definition.GetNamespace(), desc)
   629  					continue
   630  				}
   631  
   632  				sourceAddon := ""
   633  				if len(definition.GetOwnerReferences()) > 0 {
   634  					sourceAddon = strings.TrimPrefix(definition.GetOwnerReferences()[0].Name, "addon-")
   635  				}
   636  				table.AddRow(definition.GetName(), definition.GetKind(), definition.GetNamespace(), sourceAddon, desc)
   637  			}
   638  			cmd.Println(table)
   639  			return nil
   640  		},
   641  	}
   642  	cmd.Flags().StringP(FlagType, "t", "", "Specify which definition type to list. If empty, all types will be searched. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", "))
   643  	cmd.Flags().String("from", "", "Filter definitions by which addon installed them.")
   644  	cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
   645  	return cmd
   646  }
   647  
   648  // NewDefinitionEditCommand create the `vela def edit` command to help user edit remote definitions
   649  func NewDefinitionEditCommand(c common.Args) *cobra.Command {
   650  	cmd := &cobra.Command{
   651  		Use:   "edit NAME",
   652  		Short: "Edit X-Definition.",
   653  		Long: "Edit X-Definition in kubernetes. If type and namespace are not specified, the command will automatically search all possible results.\n" +
   654  			"By default, this command will use the vi editor and can be altered by setting EDITOR environment variable.",
   655  		Example: "# Command below will edit the ComponentDefinition (and other definitions if exists) of webservice in kubernetes\n" +
   656  			"> vela def edit webservice\n" +
   657  			"# Command below will edit the TraitDefinition of ingress in vela-system namespace\n" +
   658  			"> vela def edit ingress --type trait --namespace vela-system",
   659  		Args: cobra.ExactArgs(1),
   660  		RunE: func(cmd *cobra.Command, args []string) error {
   661  			definitionType, err := cmd.Flags().GetString(FlagType)
   662  			if err != nil {
   663  				return errors.Wrapf(err, "failed to get `%s`", FlagType)
   664  			}
   665  			namespace, err := cmd.Flags().GetString(FlagNamespace)
   666  			if err != nil {
   667  				return errors.Wrapf(err, "failed to get `%s`", Namespace)
   668  			}
   669  			config, err := c.GetConfig()
   670  			if err != nil {
   671  				return err
   672  			}
   673  			k8sClient, err := c.GetClient()
   674  			if err != nil {
   675  				return errors.Wrapf(err, "failed to get k8s client")
   676  			}
   677  			def, err := getSingleDefinition(cmd, args[0], k8sClient, definitionType, namespace)
   678  			if err != nil {
   679  				return err
   680  			}
   681  			cueString, err := def.ToCUEString()
   682  			if err != nil {
   683  				return errors.Wrapf(err, "failed to get cue format definition")
   684  			}
   685  			cleanup := func(filePath string) {
   686  				if err := os.Remove(filePath); err != nil {
   687  					cmd.PrintErrf("failed to remove file %s: %v", filePath, err)
   688  				}
   689  			}
   690  			filename := fmt.Sprintf("vela-def-%d", time.Now().UnixNano())
   691  			tempFilePath := filepath.Join(os.TempDir(), filename+CUEExtension)
   692  			if err := os.WriteFile(tempFilePath, []byte(cueString), 0600); err != nil {
   693  				return errors.Wrapf(err, "failed to write temporary file")
   694  			}
   695  			defer cleanup(tempFilePath)
   696  			editor := os.Getenv("EDITOR")
   697  			if editor == "" {
   698  				editor = "vi"
   699  			}
   700  			scriptFilePath := filepath.Join(os.TempDir(), filename+".sh")
   701  			if err := os.WriteFile(scriptFilePath, []byte(editor+" "+tempFilePath), 0600); err != nil {
   702  				return errors.Wrapf(err, "failed to write temporary script file")
   703  			}
   704  			defer cleanup(scriptFilePath)
   705  
   706  			editCmd := exec.Command("sh", path.Clean(scriptFilePath)) //nolint:gosec
   707  			editCmd.Stdin = os.Stdin
   708  			editCmd.Stdout = os.Stdout
   709  			editCmd.Stderr = os.Stderr
   710  			if err = editCmd.Run(); err != nil {
   711  				return errors.Wrapf(err, "failed to run editor %s at path %s", editor, scriptFilePath)
   712  			}
   713  			newBuf, err := os.ReadFile(path.Clean(tempFilePath))
   714  			if err != nil {
   715  				return errors.Wrapf(err, "failed to read temporary file %s", tempFilePath)
   716  			}
   717  			if cueString == string(newBuf) {
   718  				cmd.Printf("definition unchanged\n")
   719  				return nil
   720  			}
   721  			if err := def.FromCUEString(string(newBuf), config); err != nil {
   722  				return errors.Wrapf(err, "failed to load edited cue string")
   723  			}
   724  			if err := k8sClient.Update(context.Background(), def); err != nil {
   725  				return errors.Wrapf(err, "failed to apply changes to kubernetes")
   726  			}
   727  			cmd.Printf("Definition edited successfully.\n")
   728  			return nil
   729  		},
   730  	}
   731  	cmd.Flags().StringP(FlagType, "t", "", "Specify which definition type to get. If empty, all types will be searched. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", "))
   732  	cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
   733  	return cmd
   734  }
   735  
   736  func prettyYAMLMarshal(obj map[string]interface{}) (string, error) {
   737  	var b bytes.Buffer
   738  	encoder := yaml.NewEncoder(&b)
   739  	encoder.SetIndent(2)
   740  	err := encoder.Encode(&obj)
   741  	if err != nil {
   742  		return "", err
   743  	}
   744  	return b.String(), nil
   745  }
   746  
   747  // NewDefinitionRenderCommand create the `vela def render` command to help user render definition cue file into k8s YAML file, if used without kubernetes environment, set IGNORE_KUBE_CONFIG=true
   748  func NewDefinitionRenderCommand(c common.Args) *cobra.Command {
   749  	cmd := &cobra.Command{
   750  		Use:   "render DEFINITION.cue",
   751  		Short: "Render X-Definition.",
   752  		Long:  "Render X-Definition with cue format into kubernetes YAML format. Could be used to check whether the cue format definition is working as expected. If a directory is used as input, all cue definitions in the directory will be rendered.",
   753  		Example: "# Command below will render my-webservice.cue into YAML format and print it out.\n" +
   754  			"> vela def render my-webservice.cue\n" +
   755  			"# Command below will render my-webservice.cue and save it in my-webservice.yaml.\n" +
   756  			"> vela def render my-webservice.cue -o my-webservice.yaml" +
   757  			"# Command below will render all cue format definitions in the ./defs/cue/ directory and save the YAML objects in ./defs/yaml/.\n" +
   758  			"> vela def render ./defs/cue/ -o ./defs/yaml/",
   759  		Args: cobra.ExactArgs(1),
   760  		RunE: func(cmd *cobra.Command, args []string) error {
   761  			output, err := cmd.Flags().GetString(FlagOutput)
   762  			if err != nil {
   763  				return errors.Wrapf(err, "failed to get `%s`", FlagOutput)
   764  			}
   765  			message, err := cmd.Flags().GetString(FlagMessage)
   766  			if err != nil {
   767  				return errors.Wrapf(err, "failed to get `%s`", FlagMessage)
   768  			}
   769  
   770  			render := func(inputFilename, outputFilename string) error {
   771  				cueBytes, err := utils.ReadRemoteOrLocalPath(inputFilename, false)
   772  				if err != nil {
   773  					return errors.Wrapf(err, "failed to get %s", args[0])
   774  				}
   775  				config, err := c.GetConfig()
   776  				if err != nil {
   777  					klog.Infof("ignore kubernetes cluster, unable to get kubeconfig: %s", err.Error())
   778  				}
   779  				def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
   780  				if err := def.FromCUEString(string(cueBytes), config); err != nil {
   781  					return errors.Wrapf(err, "failed to parse CUE")
   782  				}
   783  
   784  				helmChartFormatEnv := strings.ToLower(os.Getenv(HelmChartFormatEnvName))
   785  				if helmChartFormatEnv == "true" {
   786  					def.SetNamespace(HelmChartNamespacePlaceholder)
   787  				} else if helmChartFormatEnv == "system" {
   788  					def.SetNamespace(types.DefaultKubeVelaNS)
   789  				}
   790  				if len(def.GetLabels()) == 0 {
   791  					def.SetLabels(nil)
   792  				}
   793  				s, err := prettyYAMLMarshal(def.Object)
   794  				if err != nil {
   795  					return errors.Wrapf(err, "failed to marshal CRD into YAML")
   796  				}
   797  				s = strings.ReplaceAll(s, "'"+HelmChartNamespacePlaceholder+"'", "{{ include \"systemDefinitionNamespace\" . }}") + "\n"
   798  				if outputFilename == "" {
   799  					s = fmt.Sprintf("--- %s ---\n%s", filepath.Base(inputFilename), s)
   800  					cmd.Print(s)
   801  				} else {
   802  					if message != "" {
   803  						s = "# " + strings.ReplaceAll(message, "{{INPUT_FILENAME}}", filepath.Base(inputFilename)) + "\n" + s
   804  					}
   805  					s = "# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.\n" + s
   806  					if err := os.WriteFile(outputFilename, []byte(s), 0600); err != nil {
   807  						return errors.Wrapf(err, "failed to write YAML format definition to file %s", outputFilename)
   808  					}
   809  				}
   810  				return nil
   811  			}
   812  			inputFilenames := []string{args[0]}
   813  			outputFilenames := []string{output}
   814  			fi, err := os.Stat(args[0])
   815  			if err != nil {
   816  				return errors.Wrapf(err, "failed to get input %s", args[0])
   817  			}
   818  			if fi.IsDir() {
   819  				inputFilenames = []string{}
   820  				outputFilenames = []string{}
   821  				err := filepath.Walk(args[0], func(path string, info os.FileInfo, err error) error {
   822  					filename := filepath.Base(path)
   823  					fileSuffix := filepath.Ext(path)
   824  					if fileSuffix != CUEExtension {
   825  						return nil
   826  					}
   827  					inputFilenames = append(inputFilenames, path)
   828  					if output != "" {
   829  						outputFilenames = append(outputFilenames, filepath.Join(output, strings.ReplaceAll(filename, CUEExtension, YAMLExtension)))
   830  					} else {
   831  						outputFilenames = append(outputFilenames, "")
   832  					}
   833  					return nil
   834  				})
   835  				if err != nil {
   836  					return errors.Wrapf(err, "failed to read directory %s", args[0])
   837  				}
   838  			}
   839  			for i, inputFilename := range inputFilenames {
   840  				if err = render(inputFilename, outputFilenames[i]); err != nil {
   841  					if _, err = fmt.Fprintf(cmd.ErrOrStderr(), "failed to render %s, reason: %v", inputFilename, err); err != nil {
   842  						return errors.Wrapf(err, "failed to write err")
   843  					}
   844  				}
   845  			}
   846  			return nil
   847  		},
   848  	}
   849  	cmd.Flags().StringP(FlagOutput, "o", "", "Specify the output path of the rendered definition YAML. If empty, the definition will be printed in the console. If input is a directory, the output path is expected to be a directory as well.")
   850  	cmd.Flags().StringP(FlagMessage, "", "", "Specify the header message of the generated YAML file. For example, declaring author information.")
   851  	return cmd
   852  }
   853  
   854  // NewDefinitionApplyCommand create the `vela def apply` command to help user apply local definitions to k8s
   855  func NewDefinitionApplyCommand(c common.Args, streams util.IOStreams) *cobra.Command {
   856  	cmd := &cobra.Command{
   857  		Use:   "apply DEFINITION.cue",
   858  		Short: "Apply X-Definition.",
   859  		Long:  "Apply X-Definition from local storage to kubernetes cluster. It will apply file to vela-system namespace by default.",
   860  		Example: "# Command below will apply the local my-webservice.cue file to kubernetes vela-system namespace\n" +
   861  			"> vela def apply my-webservice.cue\n" +
   862  			"# Apply the local directory including all files(YAML and CUE definition) to kubernetes vela-system namespace\n" +
   863  			"> vela def apply def/\n" +
   864  			"# Command below will apply the ./defs/my-trait.cue file to kubernetes default namespace\n" +
   865  			"> vela def apply ./defs/my-trait.cue --namespace default" +
   866  			"# Command below will convert the ./defs/my-trait.cue file to kubernetes CRD object and print it without applying it to kubernetes\n" +
   867  			"> vela def apply ./defs/my-trait.cue --dry-run" +
   868  			"# Apply a CUE from URL \n" +
   869  			"> vela def apply https://my-host-to-def/my-trait.cue --dry-run" +
   870  			"# Apply a CUE from stdin \n" +
   871  			"> vela def apply -",
   872  		Args: cobra.ExactArgs(1),
   873  		RunE: func(cmd *cobra.Command, args []string) error {
   874  			ctx := context.Background()
   875  			dryRun, err := cmd.Flags().GetBool(FlagDryRun)
   876  			if err != nil {
   877  				return errors.Wrapf(err, "failed to get `%s`", FlagDryRun)
   878  			}
   879  			namespace, err := cmd.Flags().GetString(FlagNamespace)
   880  			if err != nil {
   881  				return errors.Wrapf(err, "failed to get `%s`", Namespace)
   882  			}
   883  			if len(args) < 1 {
   884  				return errors.New("you must specify the definition path, directory or URL")
   885  			}
   886  			return defApplyAll(ctx, c, streams, namespace, args[0], dryRun)
   887  		},
   888  	}
   889  
   890  	cmd.Flags().BoolP(FlagDryRun, "", false, "only build definition from CUE into CRB object without applying it to kubernetes clusters")
   891  	cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
   892  	return cmd
   893  }
   894  
   895  func defApplyAll(ctx context.Context, c common.Args, io util.IOStreams, namespace, path string, dryRun bool) error {
   896  	files, err := utils.LoadDataFromPath(ctx, path, utils.IsJSONYAMLorCUEFile)
   897  	if err != nil {
   898  		return errors.Wrapf(err, "failed to get from %s", path)
   899  	}
   900  	for _, f := range files {
   901  		result, err := defApplyOne(ctx, c, namespace, f.Path, f.Data, dryRun)
   902  		if err != nil {
   903  			return err
   904  		}
   905  		io.Infonln(result)
   906  	}
   907  	return nil
   908  }
   909  
   910  func defApplyOne(ctx context.Context, c common.Args, namespace, defpath string, defBytes []byte, dryRun bool) (string, error) {
   911  	config, err := c.GetConfig()
   912  	if err != nil {
   913  		return "", err
   914  	}
   915  	k8sClient, err := c.GetClient()
   916  	if err != nil {
   917  		return "", errors.Wrapf(err, "failed to get k8s client")
   918  	}
   919  
   920  	def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
   921  
   922  	switch {
   923  	case strings.HasSuffix(defpath, YAMLExtension) || strings.HasSuffix(defpath, YMLExtension):
   924  		// In this case, it's not in cue format, it's a yaml
   925  		if err = def.FromYAML(defBytes); err != nil {
   926  			return "", errors.Wrapf(err, "failed to parse YAML to definition")
   927  		}
   928  		if dryRun {
   929  			return "", errors.New("dry-run will render CUE to YAML, while the input is already in yaml")
   930  		}
   931  		// YAML won't validate or format CUE schematic
   932  		op, err := utils.CreateOrUpdate(ctx, k8sClient, &def)
   933  		if err != nil {
   934  			return "", err
   935  		}
   936  		return fmt.Sprintf("%s %s in namespace %s %s.\n", def.GetKind(), def.GetName(), def.GetNamespace(), op), nil
   937  	default:
   938  		if err := def.FromCUEString(string(defBytes), config); err != nil {
   939  			return "", errors.Wrapf(err, "failed to parse CUE for definition")
   940  		}
   941  		def.SetNamespace(namespace)
   942  	}
   943  
   944  	if dryRun {
   945  		s, err := prettyYAMLMarshal(def.Object)
   946  		if err != nil {
   947  			return "", errors.Wrapf(err, "failed to marshal CRD into YAML")
   948  		}
   949  		return s, nil
   950  	}
   951  
   952  	oldDef := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
   953  	oldDef.SetGroupVersionKind(def.GroupVersionKind())
   954  	err = k8sClient.Get(ctx, types2.NamespacedName{
   955  		Namespace: def.GetNamespace(),
   956  		Name:      def.GetName(),
   957  	}, &oldDef)
   958  	if err != nil {
   959  		if errors2.IsNotFound(err) {
   960  			kind := def.GetKind()
   961  			if err = k8sClient.Create(ctx, &def); err != nil {
   962  				return "", errors.Wrapf(err, "failed to create new definition in kubernetes")
   963  			}
   964  			return fmt.Sprintf("%s %s created in namespace %s.\n", kind, def.GetName(), def.GetNamespace()), nil
   965  		}
   966  		return "", errors.Wrapf(err, "failed to check existence of target definition in kubernetes")
   967  	}
   968  	if err := oldDef.FromCUEString(string(defBytes), config); err != nil {
   969  		return "", errors.Wrapf(err, "failed to merge with existing definition")
   970  	}
   971  	if err = k8sClient.Update(ctx, &oldDef); err != nil {
   972  		return "", errors.Wrapf(err, "failed to update existing definition in kubernetes")
   973  	}
   974  	return fmt.Sprintf("%s %s in namespace %s updated.\n", oldDef.GetKind(), oldDef.GetName(), oldDef.GetNamespace()), nil
   975  }
   976  
   977  // NewDefinitionDelCommand create the `vela def del` command to help user delete existing definitions conveniently
   978  func NewDefinitionDelCommand(c common.Args) *cobra.Command {
   979  	cmd := &cobra.Command{
   980  		Use:   "del DEFINITION_NAME",
   981  		Short: "Delete X-Definition.",
   982  		Long:  "Delete X-Definition in kubernetes cluster.",
   983  		Example: "# Command below will delete TraitDefinition of annotations in default namespace\n" +
   984  			"> vela def del annotations -t trait -n default",
   985  		Args: cobra.ExactArgs(1),
   986  		RunE: func(cmd *cobra.Command, args []string) error {
   987  			definitionType, err := cmd.Flags().GetString(FlagType)
   988  			if err != nil {
   989  				return errors.Wrapf(err, "failed to get `%s`", FlagType)
   990  			}
   991  			namespace, err := cmd.Flags().GetString(FlagNamespace)
   992  			if err != nil {
   993  				return errors.Wrapf(err, "failed to get `%s`", Namespace)
   994  			}
   995  			k8sClient, err := c.GetClient()
   996  			if err != nil {
   997  				return errors.Wrapf(err, "failed to get k8s client")
   998  			}
   999  			def, err := getSingleDefinition(cmd, args[0], k8sClient, definitionType, namespace)
  1000  			if err != nil {
  1001  				return err
  1002  			}
  1003  			desc := def.GetAnnotations()[pkgdef.DescriptionKey]
  1004  			toDelete := false
  1005  			_, err = getPrompt(cmd, bufio.NewReader(cmd.InOrStdin()),
  1006  				fmt.Sprintf("Are you sure to delete the following definition in namespace %s?\n", def.GetNamespace())+
  1007  					fmt.Sprintf("%s %s: %s\n", def.GetKind(), def.GetName(), desc),
  1008  				"[yes|no] > ",
  1009  				func(resp string) error {
  1010  					switch strings.ToLower(resp) {
  1011  					case "yes":
  1012  						toDelete = true
  1013  					case "y":
  1014  						toDelete = true
  1015  					case "no":
  1016  						toDelete = false
  1017  					case "n":
  1018  						toDelete = false
  1019  					default:
  1020  						return errors.New("invalid input")
  1021  					}
  1022  					return nil
  1023  				})
  1024  			if err != nil {
  1025  				return err
  1026  			}
  1027  			if !toDelete {
  1028  				return nil
  1029  			}
  1030  			if err := k8sClient.Delete(context.Background(), def); err != nil {
  1031  				return errors.Wrapf(err, "failed to delete %s %s in namespace %s", def.GetKind(), def.GetName(), def.GetNamespace())
  1032  			}
  1033  			cmd.Printf("%s %s in namespace %s deleted.\n", def.GetKind(), def.GetName(), def.GetNamespace())
  1034  			return nil
  1035  		},
  1036  	}
  1037  	cmd.Flags().StringP(FlagType, "t", "", "Specify the definition type of target. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", "))
  1038  	cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
  1039  	return cmd
  1040  }
  1041  
  1042  // NewDefinitionValidateCommand create the `vela def vet` command to help user validate the definition
  1043  func NewDefinitionValidateCommand(c common.Args) *cobra.Command {
  1044  	cmd := &cobra.Command{
  1045  		Use:   "vet DEFINITION.cue",
  1046  		Short: "Validate X-Definition.",
  1047  		Long: "Validate definition file by checking whether it has the valid cue format with fields set correctly\n" +
  1048  			"* Currently, this command only checks the cue format. This function is still working in progress and we will support more functional validation mechanism in the future.",
  1049  		Example: "# Command below will validate the my-def.cue file.\n" +
  1050  			"> vela def vet my-def.cue\n" +
  1051  			"# Validate every cue file provided\n" +
  1052  			"> vela def vet my-def1.cue my-def2.cue my-def3.cue\n" +
  1053  			"# Validate every cue file in the specified directories" +
  1054  			"> vela def vet ./test1/ ./test2/",
  1055  		Args: cobra.MinimumNArgs(1),
  1056  		RunE: func(cmd *cobra.Command, args []string) error {
  1057  			for _, arg := range args {
  1058  				files, err := utils.LoadDataFromPath(cmd.Context(), arg, utils.IsCUEFile)
  1059  				if err != nil {
  1060  					return errors.Wrapf(err, "failed to get file from %s", arg)
  1061  				}
  1062  				for _, file := range files {
  1063  					validateRes, err := validateSingleCueFile(file.Path, file.Data, c)
  1064  					if err != nil {
  1065  						return err
  1066  					}
  1067  					fmt.Fprintf(cmd.OutOrStdout(), "%s", validateRes)
  1068  				}
  1069  			}
  1070  			return nil
  1071  		},
  1072  	}
  1073  	return cmd
  1074  }
  1075  
  1076  func validateSingleCueFile(fileName string, fileData []byte, c common.Args) (string, error) {
  1077  	def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
  1078  	config, err := c.GetConfig()
  1079  	if err != nil {
  1080  		klog.Infof("ignore kubernetes cluster, unable to get kubeconfig: %s", err.Error())
  1081  	}
  1082  	if err := def.FromCUEString(string(fileData), config); err != nil {
  1083  		return "", errors.Wrapf(err, "failed to parse CUE: %s", fileName)
  1084  	}
  1085  	return fmt.Sprintf("Validation %s succeed.\n", fileName), nil
  1086  }
  1087  
  1088  // NewDefinitionGenAPICommand create the `vela def gen-api` command to help user generate Go code from the definition
  1089  func NewDefinitionGenAPICommand(c common.Args) *cobra.Command {
  1090  	meta := gen_sdk.GenMeta{}
  1091  	var languageArgs []string
  1092  
  1093  	cmd := &cobra.Command{
  1094  		Use:   "gen-api DEFINITION.cue",
  1095  		Short: "Generate SDK from X-Definition.",
  1096  		Long: "Generate SDK from X-definition file.\n" +
  1097  			"* This command leverage openapi-generator project. Therefore demands \"docker\" exist in PATH\n" +
  1098  			"* Currently, this function is still working in progress and not all formats of parameter in X-definition are supported yet.",
  1099  		Example: "# Generate SDK for golang with scaffold initialized\n" +
  1100  			"> vela def gen-api --init --language go -f /path/to/def -o /path/to/sdk\n" +
  1101  			"# Generate incremental definition files to existing sdk directory\n" +
  1102  			"> vela def gen-api --language go -f /path/to/def -o /path/to/sdk\n" +
  1103  			"# Generate definitions to a sub-module\n" +
  1104  			"> vela def gen-api --language go -f /path/to/def -o /path/to/sdk --submodule --api-dir path/relative/to/output --language-args arg1=val1,arg2=val2\n",
  1105  		RunE: func(cmd *cobra.Command, args []string) error {
  1106  			err := meta.Init(c, languageArgs)
  1107  			if err != nil {
  1108  				return err
  1109  			}
  1110  			err = meta.CreateScaffold()
  1111  			if err != nil {
  1112  				return err
  1113  			}
  1114  			err = meta.PrepareGeneratorAndTemplate()
  1115  			if err != nil {
  1116  				return err
  1117  			}
  1118  			err = meta.Run()
  1119  			if err != nil {
  1120  				return err
  1121  			}
  1122  
  1123  			return nil
  1124  		},
  1125  	}
  1126  
  1127  	cmd.Flags().StringVarP(&meta.Output, "output", "o", "./apis", "Output directory path")
  1128  	cmd.Flags().StringVar(&meta.APIDirectory, "api-dir", "", "API directory path to put definition API files, relative to output directory. Default value: go: pkg/apis")
  1129  	cmd.Flags().BoolVar(&meta.IsSubModule, "submodule", false, "Whether the generated code is a submodule of the project. If set, the directory specified by `api-dir` will be treated as a submodule of the project")
  1130  	cmd.Flags().StringVarP(&meta.Package, "package", "p", gen_sdk.PackagePlaceHolder, "Package name of generated code")
  1131  	cmd.Flags().StringVarP(&meta.Lang, "language", "g", "go", "Language to generate code. Valid languages: go")
  1132  	cmd.Flags().StringVarP(&meta.Template, "template", "t", "", "Template file path, if not specified, the default template will be used")
  1133  	cmd.Flags().StringSliceVarP(&meta.File, "file", "f", nil, "File name of definitions, can be specified multiple times, or use comma to separate multiple files. If directory specified, all files found recursively in the directory will be used")
  1134  	cmd.Flags().BoolVar(&meta.InitSDK, "init", false, "Init the whole SDK project, if not set, only the API file will be generated")
  1135  	cmd.Flags().BoolVarP(&meta.Verbose, "verbose", "v", false, "Print verbose logs")
  1136  	var langArgsDescStr string
  1137  	for lang, args := range gen_sdk.LangArgsRegistry {
  1138  		langArgsDescStr += lang + ": \n"
  1139  		for key, arg := range args {
  1140  			langArgsDescStr += fmt.Sprintf("\t%s: %s(default: %s)\n", key, arg.Name, arg.Default)
  1141  		}
  1142  	}
  1143  	cmd.Flags().StringSliceVar(&languageArgs, "language-args", []string{},
  1144  		fmt.Sprintf("language-specific arguments to pass to the go generator, available options: \n"+langArgsDescStr),
  1145  	)
  1146  
  1147  	return cmd
  1148  }
  1149  
  1150  const (
  1151  	genTypeProvider = "provider"
  1152  )
  1153  
  1154  // NewDefinitionGenCUECommand create the `vela def gen-cue` command to help user generate CUE schema from the go code
  1155  func NewDefinitionGenCUECommand(_ common.Args, streams util.IOStreams) *cobra.Command {
  1156  	var (
  1157  		typ      string
  1158  		typeMap  map[string]string
  1159  		nullable bool
  1160  	)
  1161  
  1162  	cmd := &cobra.Command{
  1163  		Use:   "gen-cue [flags] SOURCE.go",
  1164  		Args:  cobra.ExactArgs(1),
  1165  		Short: "Generate CUE schema from Go code.",
  1166  		Long: "Generate CUE schema from Go code.\n" +
  1167  			"* This command provide a way to generate CUE schema from Go code,\n" +
  1168  			"* Which can be used to keep consistency between Go code and CUE schema automatically.\n",
  1169  		Example: "# Generate CUE schema for provider type\n" +
  1170  			"> vela def gen-cue -t provider /path/to/myprovider.go > /path/to/myprovider.cue\n" +
  1171  			"# Generate CUE schema for provider type with custom types\n" +
  1172  			"> vela def gen-cue -t provider --types *k8s.io/apimachinery/pkg/apis/meta/v1/unstructured.Unstructured=ellipsis /path/to/myprovider.go > /path/to/myprovider.cue",
  1173  		RunE: func(cmd *cobra.Command, args []string) (rerr error) {
  1174  			// convert map[string]string to map[string]cuegen.Type
  1175  			newTypeMap := make(map[string]cuegen.Type, len(typeMap))
  1176  			for k, v := range typeMap {
  1177  				newTypeMap[k] = cuegen.Type(v)
  1178  			}
  1179  
  1180  			file := args[0]
  1181  			if !strings.HasSuffix(file, ".go") {
  1182  				return fmt.Errorf("invalid file %s, must be a go file", file)
  1183  			}
  1184  
  1185  			switch typ {
  1186  			case genTypeProvider:
  1187  				return providergen.Generate(providergen.Options{
  1188  					File:     file,
  1189  					Writer:   streams.Out,
  1190  					Types:    newTypeMap,
  1191  					Nullable: nullable,
  1192  				})
  1193  			default:
  1194  				return fmt.Errorf("invalid type %s", typ)
  1195  			}
  1196  		},
  1197  	}
  1198  
  1199  	cmd.Flags().StringVarP(&typ, "type", "t", "", "Type of the definition to generate. Valid types: [provider]")
  1200  	cmd.Flags().BoolVar(&nullable, "nullable", false, "Whether to generate null enum for pointer type")
  1201  	cmd.Flags().StringToStringVar(&typeMap, "types", map[string]string{}, "Special types to generate, format: <package+struct>=[any|ellipsis]. e.g. --types=*k8s.io/apimachinery/pkg/apis/meta/v1/unstructured.Unstructured=ellipsis")
  1202  
  1203  	return cmd
  1204  }
  1205  
  1206  // NewDefinitionGenDocCommand create the `vela def gen-doc` command to generate documentation of definitions
  1207  func NewDefinitionGenDocCommand(_ common.Args, streams util.IOStreams) *cobra.Command {
  1208  	var typ string
  1209  
  1210  	cmd := &cobra.Command{
  1211  		Use:   "gen-doc [flags] SOURCE.cue...",
  1212  		Args:  cobra.MinimumNArgs(1),
  1213  		Short: "Generate documentation for non component, trait, policy and workflow definitions",
  1214  		Long:  "Generate documentation for non component, trait, policy and workflow definitions",
  1215  		Example: "1. Generate documentation for provider definitions\n" +
  1216  			"> vela def gen-doc -t provider provider1.cue provider2.cue > provider.md",
  1217  		RunE: func(cmd *cobra.Command, args []string) error {
  1218  			readers := make([]io.Reader, 0, len(args))
  1219  
  1220  			for _, arg := range args {
  1221  				if !strings.HasSuffix(arg, CUEExtension) {
  1222  					return fmt.Errorf("invalid file %s, must be a cue file", arg)
  1223  				}
  1224  
  1225  				f, err := os.ReadFile(filepath.Clean(arg))
  1226  				if err != nil {
  1227  					return fmt.Errorf("read file %s: %w", arg, err)
  1228  				}
  1229  
  1230  				readers = append(readers, bytes.NewReader(f))
  1231  			}
  1232  
  1233  			switch typ {
  1234  			case genTypeProvider:
  1235  				return docgen.GenerateProvidersMarkdown(cmd.Context(), readers, streams.Out)
  1236  			default:
  1237  				return fmt.Errorf("invalid type %s", typ)
  1238  			}
  1239  		},
  1240  	}
  1241  
  1242  	cmd.Flags().StringVarP(&typ, "type", "t", "", "Type of the definition to generate. Valid types: [provider]")
  1243  
  1244  	return cmd
  1245  }