mirror of
https://github.com/kubevela/kubevela.git
synced 2026-05-21 08:43:35 +00:00
2113 lines
79 KiB
Go
2113 lines
79 KiB
Go
/*
|
|
Copyright 2021 The KubeVela Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package cli
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"cuelang.org/go/cue/cuecontext"
|
|
"cuelang.org/go/encoding/gocode/gocodec"
|
|
"github.com/kubevela/pkg/util/slices"
|
|
"github.com/kubevela/workflow/pkg/cue/model/sets"
|
|
crossplane "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
"go.yaml.in/yaml/v3"
|
|
errors2 "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
|
types2 "k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/client-go/rest"
|
|
"k8s.io/klog/v2"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
commontype "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
|
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
|
"github.com/oam-dev/kubevela/apis/types"
|
|
"github.com/oam-dev/kubevela/pkg/cue/process"
|
|
"github.com/oam-dev/kubevela/pkg/cue/upgrade"
|
|
pkgdef "github.com/oam-dev/kubevela/pkg/definition"
|
|
"github.com/oam-dev/kubevela/pkg/definition/gen_sdk"
|
|
"github.com/oam-dev/kubevela/pkg/definition/goloader"
|
|
"github.com/oam-dev/kubevela/pkg/utils"
|
|
addonutil "github.com/oam-dev/kubevela/pkg/utils/addon"
|
|
"github.com/oam-dev/kubevela/pkg/utils/common"
|
|
"github.com/oam-dev/kubevela/pkg/utils/filters"
|
|
"github.com/oam-dev/kubevela/pkg/utils/util"
|
|
"github.com/oam-dev/kubevela/references/cuegen"
|
|
providergen "github.com/oam-dev/kubevela/references/cuegen/generators/provider"
|
|
"github.com/oam-dev/kubevela/references/docgen"
|
|
)
|
|
|
|
const (
|
|
// HelmChartNamespacePlaceholder is used as a placeholder for rendering definitions into helm chart format
|
|
HelmChartNamespacePlaceholder = "###HELM_NAMESPACE###"
|
|
// HelmChartFormatEnvName is the name of the environment variable to enable render helm chart format YAML
|
|
HelmChartFormatEnvName = "AS_HELM_CHART"
|
|
)
|
|
|
|
// DefinitionCommandGroup create the command group for `vela def` command to manage definitions
|
|
func DefinitionCommandGroup(c common.Args, order string, ioStreams util.IOStreams) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "def",
|
|
Short: "Manage definitions.",
|
|
Long: "Manage X-Definitions for extension.",
|
|
Annotations: map[string]string{
|
|
types.TagCommandOrder: order,
|
|
types.TagCommandType: types.TypeExtension,
|
|
},
|
|
}
|
|
cmd.SetOut(ioStreams.Out)
|
|
cmd.AddCommand(
|
|
NewDefinitionGetCommand(c),
|
|
NewDefinitionListCommand(c),
|
|
NewDefinitionEditCommand(c),
|
|
NewDefinitionRenderCommand(c),
|
|
NewDefinitionApplyCommand(c, ioStreams),
|
|
NewDefinitionDelCommand(c),
|
|
NewDefinitionInitCommand(c),
|
|
NewDefinitionValidateCommand(c),
|
|
NewDefinitionUpgradeCommand(c, ioStreams),
|
|
NewDefinitionDocGenCommand(c, ioStreams),
|
|
NewCapabilityShowCommand(c, "", ioStreams),
|
|
NewDefinitionGenAPICommand(c),
|
|
NewDefinitionGenCUECommand(c, ioStreams),
|
|
NewDefinitionGenDocCommand(c, ioStreams),
|
|
// Module commands for Go definition modules
|
|
NewDefinitionInitModuleCommand(c, ioStreams),
|
|
NewDefinitionApplyModuleCommand(c, ioStreams),
|
|
NewDefinitionListModuleCommand(c, ioStreams),
|
|
NewDefinitionValidateModuleCommand(c, ioStreams),
|
|
NewDefinitionGenModuleCommand(c, ioStreams),
|
|
)
|
|
// Set custom help function for grouped output
|
|
cmd.SetHelpFunc(defHelpFunc)
|
|
return cmd
|
|
}
|
|
|
|
// defHelpFunc provides grouped help output for the def command
|
|
func defHelpFunc(cmd *cobra.Command, args []string) {
|
|
// If this is being called for a subcommand (not the root "def" command),
|
|
// use the standard cobra help output
|
|
if cmd.Name() != "def" {
|
|
// Use standard help template for subcommands
|
|
cmd.Println(cmd.Long)
|
|
if cmd.Example != "" {
|
|
cmd.Println()
|
|
cmd.Println("Examples:")
|
|
cmd.Println(cmd.Example)
|
|
}
|
|
cmd.Println()
|
|
cmd.Println("Usage:")
|
|
cmd.Printf(" %s\n", cmd.UseLine())
|
|
cmd.Println()
|
|
localFlags := cmd.LocalFlags().FlagUsages()
|
|
if localFlags != "" {
|
|
cmd.Println("Flags:")
|
|
cmd.Print(localFlags)
|
|
}
|
|
inheritedFlags := cmd.InheritedFlags().FlagUsages()
|
|
if inheritedFlags != "" {
|
|
cmd.Println()
|
|
cmd.Println("Global Flags:")
|
|
cmd.Print(inheritedFlags)
|
|
}
|
|
return
|
|
}
|
|
|
|
if len(args) > 0 {
|
|
// If args provided, find and show help for subcommand
|
|
foundCmd, _, err := cmd.Find(args)
|
|
if foundCmd != nil && err == nil {
|
|
_ = foundCmd.Help()
|
|
return
|
|
}
|
|
}
|
|
|
|
cmd.Println(cmd.Long)
|
|
cmd.Println()
|
|
cmd.Println("Usage:")
|
|
cmd.Printf(" %s [command]\n", cmd.UseLine())
|
|
cmd.Println()
|
|
|
|
// Print commands grouped by type
|
|
for _, t := range []string{types.TypeDefManagement, types.TypeDefGeneration, types.TypeDefModule} {
|
|
PrintDefHelpByTag(cmd, cmd.Commands(), t)
|
|
}
|
|
|
|
cmd.Println("Flags:")
|
|
cmd.Println(" -h, --help help for def")
|
|
cmd.Println()
|
|
cmd.Println("Global Flags:")
|
|
cmd.Println(" -V, --verbosity Level number for the log level verbosity")
|
|
cmd.Println(" -y, --yes Assume yes for all user prompts")
|
|
cmd.Println()
|
|
cmd.Printf("Use \"%s [command] --help\" for more information about a command.\n", cmd.CommandPath())
|
|
}
|
|
|
|
// PrintDefHelpByTag prints commands grouped by their TagCommandType annotation
|
|
func PrintDefHelpByTag(cmd *cobra.Command, all []*cobra.Command, tag string) {
|
|
table := newUITable()
|
|
table.MaxColWidth = 80
|
|
var pl []Printable
|
|
for _, c := range all {
|
|
if c.Hidden || c.IsAdditionalHelpTopicCommand() {
|
|
continue
|
|
}
|
|
if val, ok := c.Annotations[types.TagCommandType]; ok && val == tag {
|
|
pl = append(pl, NewPrintable(c, c.Short))
|
|
}
|
|
}
|
|
if len(pl) == 0 {
|
|
return
|
|
}
|
|
slices.Sort(pl, func(i, j Printable) bool { return i.Order < j.Order })
|
|
cmd.Println(tag + ":")
|
|
for _, v := range pl {
|
|
table.AddRow(fmt.Sprintf(" %-18s", v.Use), v.Desc)
|
|
}
|
|
cmd.Println(table.String())
|
|
cmd.Println()
|
|
}
|
|
|
|
func getPrompt(cmd *cobra.Command, reader *bufio.Reader, description string, prompt string, validate func(string) error) (string, error) {
|
|
cmd.Printf("%s", description)
|
|
for {
|
|
cmd.Printf("%s", prompt)
|
|
resp, err := reader.ReadString('\n')
|
|
resp = strings.TrimSpace(resp)
|
|
if err != nil {
|
|
return "", errors.Wrapf(err, "failed to read user response")
|
|
}
|
|
if validate == nil {
|
|
return resp, nil
|
|
}
|
|
err = validate(resp)
|
|
if err != nil {
|
|
cmd.Println(err)
|
|
} else {
|
|
return resp, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// nolint:staticcheck
|
|
func buildTemplateFromYAML(templateYAML string, def *pkgdef.Definition) error {
|
|
templateYAMLBytes, err := utils.ReadRemoteOrLocalPath(templateYAML, false)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get template YAML file %s", templateYAML)
|
|
}
|
|
yamlStrings := regexp.MustCompile(`\n---[^\n]*\n`).Split(string(templateYAMLBytes), -1)
|
|
templateObject := map[string]interface{}{
|
|
process.OutputFieldName: map[string]interface{}{},
|
|
process.OutputsFieldName: map[string]interface{}{},
|
|
process.ParameterFieldName: map[string]interface{}{},
|
|
}
|
|
kind := def.GetKind()
|
|
for index, yamlString := range yamlStrings {
|
|
var yamlObject map[string]interface{}
|
|
if err = yaml.Unmarshal([]byte(yamlString), &yamlObject); err != nil {
|
|
return errors.Wrapf(err, "failed to unmarshal template yaml file")
|
|
}
|
|
|
|
if index == 0 && kind != v1beta1.TraitDefinitionKind {
|
|
templateObject[process.OutputFieldName] = yamlObject
|
|
} else {
|
|
name, _, _ := unstructured.NestedString(yamlObject, "metadata", "name")
|
|
if name == "" {
|
|
name = fmt.Sprintf("output-%d", index)
|
|
}
|
|
templateObject[process.OutputsFieldName].(map[string]interface{})[name] = yamlObject
|
|
}
|
|
}
|
|
codec := gocodec.New(cuecontext.New(), &gocodec.Config{})
|
|
val, err := codec.Decode(templateObject)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to decode template into cue")
|
|
}
|
|
templateString, err := sets.ToString(val)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to encode template cue string")
|
|
}
|
|
err = unstructured.SetNestedField(def.Object, templateString, pkgdef.DefinitionTemplateKeys...)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to merge template cue string")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FlagLang is the flag for specifying the language of a definition
|
|
const FlagLang = "lang"
|
|
|
|
// NewDefinitionInitCommand create the `vela def init` command to help user initialize a definition locally
|
|
func NewDefinitionInitCommand(_ common.Args) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "init DEF_NAME",
|
|
Short: "Init a new definition",
|
|
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" +
|
|
"However, we do not support variables in YAML file currently, which prevents users from directly feeding files like helm chart directly. \n" +
|
|
"We may introduce such features in the future.\n\n" +
|
|
"Use --lang go to generate a Go-based definition using the defkit package.",
|
|
Example: "# Command below initiate an empty TraitDefinition named my-ingress\n" +
|
|
"> vela def init my-ingress -t trait --desc \"My ingress trait definition.\" > ./my-ingress.cue\n" +
|
|
"# Command below initiate a definition named my-def interactively and save it to ./my-def.cue\n" +
|
|
"> vela def init my-def -i --output ./my-def.cue\n" +
|
|
"# Command below initiate a ComponentDefinition named my-webservice with the template parsed from ./template.yaml.\n" +
|
|
"> vela def init my-webservice -i --template-yaml ./template.yaml\n" +
|
|
"# Command below initiate a Go-based ComponentDefinition\n" +
|
|
"> vela def init my-webservice -t component --lang go -o ./my-webservice.go\n" +
|
|
"# Initiate a Terraform ComponentDefinition named vswitch from Github for Alibaba Cloud.\n" +
|
|
"> vela def init vswitch --type component --provider alibaba --desc xxx --git https://github.com/kubevela-contrib/terraform-modules.git --path alibaba/vswitch\n" +
|
|
"# Initiate a Terraform ComponentDefinition named redis from local file for AWS.\n" +
|
|
"> vela def init redis --type component --provider aws --desc \"Terraform configuration for AWS Redis\" --local redis.tf",
|
|
Args: cobra.ExactArgs(1),
|
|
Annotations: map[string]string{
|
|
types.TagCommandType: types.TypeDefManagement,
|
|
types.TagCommandOrder: "1",
|
|
},
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
var defStr string
|
|
definitionType, err := cmd.Flags().GetString(FlagType)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", FlagType)
|
|
}
|
|
alias, err := cmd.Flags().GetString(FlagAlias)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", FlagAlias)
|
|
}
|
|
desc, err := cmd.Flags().GetString(FlagDescription)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", FlagDescription)
|
|
}
|
|
templateYAML, err := cmd.Flags().GetString(FlagTemplateYAML)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", FlagTemplateYAML)
|
|
}
|
|
output, err := cmd.Flags().GetString(FlagOutput)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", FlagOutput)
|
|
}
|
|
interactive, err := cmd.Flags().GetBool(FlagInteractive)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", FlagInteractive)
|
|
}
|
|
lang, err := cmd.Flags().GetString(FlagLang)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", FlagLang)
|
|
}
|
|
|
|
if interactive {
|
|
reader := bufio.NewReader(cmd.InOrStdin())
|
|
if definitionType == "" {
|
|
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 {
|
|
if _, ok := pkgdef.DefinitionTypeToKind[resp]; !ok {
|
|
return errors.New("invalid definition type")
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if desc == "" {
|
|
if desc, err = getPrompt(cmd, reader, "", "> Definition description: ", nil); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if templateYAML == "" && lang != "go" {
|
|
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 {
|
|
if resp == "" {
|
|
return nil
|
|
}
|
|
_, err = os.Stat(resp)
|
|
return err
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if output == "" {
|
|
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 {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
kind, ok := pkgdef.DefinitionTypeToKind[definitionType]
|
|
if !ok {
|
|
return errors.New("invalid definition type")
|
|
}
|
|
|
|
name := args[0]
|
|
|
|
// Handle Go-based definitions
|
|
if lang == "go" {
|
|
defStr, err = generateGoDefinition(name, definitionType, desc)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to generate Go definition")
|
|
}
|
|
} else {
|
|
provider, err := cmd.Flags().GetString(FlagProvider)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", FlagProvider)
|
|
}
|
|
if provider != "" {
|
|
defStr, err = generateTerraformTypedComponentDefinition(cmd, name, kind, provider, desc)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to generate Terraform typed component definition")
|
|
}
|
|
} else {
|
|
def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
|
|
def.SetGVK(kind)
|
|
def.SetName(name)
|
|
def.SetAnnotations(map[string]string{
|
|
pkgdef.DescriptionKey: desc,
|
|
pkgdef.AliasKey: alias,
|
|
})
|
|
def.SetLabels(map[string]string{})
|
|
def.Object["spec"] = pkgdef.GetDefinitionDefaultSpec(def.GetKind())
|
|
if templateYAML != "" {
|
|
if err = buildTemplateFromYAML(templateYAML, &def); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
defStr, err = def.ToCUEString()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to generate cue string")
|
|
}
|
|
}
|
|
}
|
|
if output != "" {
|
|
if err = os.WriteFile(path.Clean(output), []byte(defStr), 0600); err != nil {
|
|
return errors.Wrapf(err, "failed to write definition into %s", output)
|
|
}
|
|
cmd.Printf("Definition written to %s\n", output)
|
|
} else if _, err = cmd.OutOrStdout().Write([]byte(defStr + "\n")); err != nil {
|
|
return errors.Wrapf(err, "failed to write out definition")
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
cmd.Flags().StringP(FlagType, "t", "", "Specify the type of the new definition. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", "))
|
|
cmd.Flags().StringP(FlagDescription, "d", "", "Specify the description of the new definition.")
|
|
cmd.Flags().StringP(FlagAlias, "a", "", "Specify the alias of the new definition.")
|
|
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.")
|
|
cmd.Flags().StringP(FlagOutput, "o", "", "Specify the output path of the generated definition. If empty, the definition will be printed in the console.")
|
|
cmd.Flags().BoolP(FlagInteractive, "i", false, "Specify whether use interactive process to help generate definitions.")
|
|
cmd.Flags().StringP(FlagLang, "l", "cue", "Specify the language of the definition. Valid options: cue, go")
|
|
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.")
|
|
cmd.Flags().StringP(FlagGit, "", "", "Specify which git repository the configuration(HCL) is stored in. Valid when --provider/-p is set.")
|
|
cmd.Flags().StringP(FlagLocal, "", "", "Specify the local path of the configuration(HCL) file. Valid when --provider/-p is set.")
|
|
cmd.Flags().StringP(FlagPath, "", "", "Specify which path the configuration(HCL) is stored in the Git repository. Valid when --git is set.")
|
|
return cmd
|
|
}
|
|
|
|
// generateGoDefinition generates a Go-based definition scaffold
|
|
func generateGoDefinition(name, definitionType, desc string) (string, error) {
|
|
// Convert name to Go-friendly format (PascalCase)
|
|
funcName := toPascalCase(name)
|
|
|
|
switch definitionType {
|
|
case "component":
|
|
return generateGoComponentDefinition(name, funcName, desc), nil
|
|
case "trait":
|
|
return generateGoTraitDefinition(name, funcName, desc), nil
|
|
case "policy":
|
|
return generateGoPolicyDefinition(name, funcName, desc), nil
|
|
case "workflow-step":
|
|
return generateGoWorkflowStepDefinition(name, funcName, desc), nil
|
|
default:
|
|
return "", errors.Errorf("unsupported definition type for Go: %s", definitionType)
|
|
}
|
|
}
|
|
|
|
// toPascalCase converts a kebab-case or snake_case string to PascalCase
|
|
func toPascalCase(s string) string {
|
|
// Split by common separators
|
|
parts := strings.FieldsFunc(s, func(r rune) bool {
|
|
return r == '-' || r == '_'
|
|
})
|
|
var result strings.Builder
|
|
for _, part := range parts {
|
|
if len(part) > 0 {
|
|
result.WriteString(strings.ToUpper(part[:1]))
|
|
result.WriteString(strings.ToLower(part[1:]))
|
|
}
|
|
}
|
|
return result.String()
|
|
}
|
|
|
|
func generateGoComponentDefinition(name, funcName, desc string) string {
|
|
return fmt.Sprintf(`/*
|
|
Copyright 2025 The KubeVela Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package main
|
|
|
|
import (
|
|
"github.com/oam-dev/kubevela/pkg/definition/defkit"
|
|
)
|
|
|
|
// %sComponent creates the %s component definition.
|
|
func %sComponent() *defkit.ComponentDefinition {
|
|
// Define parameters
|
|
image := defkit.String("image").
|
|
Required().
|
|
Description("Container image to use")
|
|
|
|
// Create the component definition
|
|
return defkit.NewComponent("%s").
|
|
Description(%q).
|
|
Workload("apps/v1", "Deployment").
|
|
Params(image).
|
|
Template(func(tpl *defkit.Template) {
|
|
// Access runtime context
|
|
vela := defkit.VelaCtx()
|
|
|
|
// Create the Deployment resource
|
|
deployment := defkit.NewResource("apps/v1", "Deployment").
|
|
Set("metadata.name", vela.Name()).
|
|
Set("metadata.labels.app", vela.Name()).
|
|
Set("spec.selector.matchLabels.app", vela.Name()).
|
|
Set("spec.template.metadata.labels.app", vela.Name()).
|
|
Set("spec.template.spec.containers[0].name", vela.Name()).
|
|
Set("spec.template.spec.containers[0].image", image)
|
|
|
|
// Set as primary output
|
|
tpl.Output(deployment)
|
|
})
|
|
}
|
|
|
|
func main() {
|
|
// Print the CUE definition
|
|
component := %sComponent()
|
|
println(component.ToCue())
|
|
}
|
|
`, funcName, name, funcName, name, desc, funcName)
|
|
}
|
|
|
|
func generateGoTraitDefinition(name, funcName, desc string) string {
|
|
return fmt.Sprintf(`/*
|
|
Copyright 2025 The KubeVela Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package main
|
|
|
|
import (
|
|
"github.com/oam-dev/kubevela/pkg/definition/defkit"
|
|
)
|
|
|
|
// %sTrait creates the %s trait definition.
|
|
func %sTrait() *defkit.TraitDefinition {
|
|
// Define parameters
|
|
replicas := defkit.Int("replicas").
|
|
Default(1).
|
|
Description("Number of replicas")
|
|
|
|
// Create the trait definition
|
|
return defkit.NewTrait("%s").
|
|
Description(%q).
|
|
AppliesTo("deployments.apps", "statefulsets.apps").
|
|
Params(replicas).
|
|
Template(func(tpl *defkit.Template) {
|
|
// Access the context
|
|
vela := defkit.VelaCtx()
|
|
|
|
// Patch the workload
|
|
patch := defkit.NewPatch().
|
|
Set("spec.replicas", replicas)
|
|
|
|
tpl.Patch(patch)
|
|
_ = vela // use vela context as needed
|
|
})
|
|
}
|
|
|
|
func main() {
|
|
// Print the CUE definition
|
|
trait := %sTrait()
|
|
println(trait.ToCue())
|
|
}
|
|
`, funcName, name, funcName, name, desc, funcName)
|
|
}
|
|
|
|
func generateGoPolicyDefinition(name, funcName, desc string) string {
|
|
return fmt.Sprintf(`/*
|
|
Copyright 2025 The KubeVela Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package main
|
|
|
|
import (
|
|
"github.com/oam-dev/kubevela/pkg/definition/defkit"
|
|
)
|
|
|
|
// %sPolicy creates the %s policy definition.
|
|
func %sPolicy() *defkit.PolicyDefinition {
|
|
// Define parameters
|
|
enabled := defkit.Bool("enabled").
|
|
Default(true).
|
|
Description("Whether to enable this policy")
|
|
|
|
// Create the policy definition
|
|
return defkit.NewPolicy("%s").
|
|
Description(%q).
|
|
Params(enabled).
|
|
Template(func(tpl *defkit.Template) {
|
|
// Policy logic here
|
|
_ = enabled
|
|
})
|
|
}
|
|
|
|
func main() {
|
|
// Print the CUE definition
|
|
policy := %sPolicy()
|
|
println(policy.ToCue())
|
|
}
|
|
`, funcName, name, funcName, name, desc, funcName)
|
|
}
|
|
|
|
func generateGoWorkflowStepDefinition(name, funcName, desc string) string {
|
|
return fmt.Sprintf(`/*
|
|
Copyright 2025 The KubeVela Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package main
|
|
|
|
import (
|
|
"github.com/oam-dev/kubevela/pkg/definition/defkit"
|
|
)
|
|
|
|
// %sWorkflowStep creates the %s workflow step definition.
|
|
func %sWorkflowStep() *defkit.WorkflowStepDefinition {
|
|
// Define parameters
|
|
message := defkit.String("message").
|
|
Default("Hello").
|
|
Description("Message to use in the workflow step")
|
|
|
|
// Create the workflow step definition
|
|
return defkit.NewWorkflowStep("%s").
|
|
Description(%q).
|
|
Params(message).
|
|
Template(func(tpl *defkit.Template) {
|
|
// Workflow step logic here
|
|
_ = message
|
|
})
|
|
}
|
|
|
|
func main() {
|
|
// Print the CUE definition
|
|
step := %sWorkflowStep()
|
|
println(step.ToCue())
|
|
}
|
|
`, funcName, name, funcName, name, desc, funcName)
|
|
}
|
|
|
|
func generateTerraformTypedComponentDefinition(cmd *cobra.Command, name, kind, provider, desc string) (string, error) {
|
|
if kind != v1beta1.ComponentDefinitionKind {
|
|
return "", errors.New("provider is only valid when the type of the definition is component")
|
|
}
|
|
|
|
switch provider {
|
|
case "aws", "azure", "alibaba", "tencent", "gcp", "baidu", "elastic", "ucloud", "vsphere", "huawei":
|
|
var terraform *commontype.Terraform
|
|
|
|
git, err := cmd.Flags().GetString(FlagGit)
|
|
if err != nil {
|
|
return "", errors.Wrapf(err, "failed to get `%s`", FlagGit)
|
|
}
|
|
local, err := cmd.Flags().GetString(FlagLocal)
|
|
if err != nil {
|
|
return "", errors.Wrapf(err, "failed to get `%s`", FlagLocal)
|
|
}
|
|
if git != "" && local != "" {
|
|
return "", errors.New("only one of --git and --local can be set")
|
|
}
|
|
gitPath, err := cmd.Flags().GetString(FlagPath)
|
|
if err != nil {
|
|
return "", errors.Wrapf(err, "failed to get `%s`", FlagPath)
|
|
}
|
|
if git != "" {
|
|
if !strings.HasPrefix(git, "https://") || !strings.HasSuffix(git, ".git") {
|
|
return "", errors.Errorf("invalid git url: %s", git)
|
|
}
|
|
terraform = &commontype.Terraform{
|
|
Configuration: git,
|
|
Type: "remote",
|
|
Path: gitPath,
|
|
}
|
|
} else if local != "" {
|
|
hcl, err := os.ReadFile(filepath.Clean(local))
|
|
if err != nil {
|
|
return "", errors.Wrapf(err, "failed to read Terraform configuration from file %s", local)
|
|
}
|
|
terraform = &commontype.Terraform{
|
|
Configuration: string(hcl),
|
|
}
|
|
}
|
|
def := v1beta1.ComponentDefinition{
|
|
TypeMeta: metav1.TypeMeta{
|
|
APIVersion: "core.oam.dev/v1beta1",
|
|
Kind: "ComponentDefinition",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: fmt.Sprintf("%s-%s", provider, name),
|
|
Namespace: types.DefaultKubeVelaNS,
|
|
Annotations: map[string]string{
|
|
"definition.oam.dev/description": desc,
|
|
},
|
|
Labels: map[string]string{
|
|
"type": "terraform",
|
|
},
|
|
},
|
|
Spec: v1beta1.ComponentDefinitionSpec{
|
|
Workload: commontype.WorkloadTypeDescriptor{
|
|
Definition: commontype.WorkloadGVK{
|
|
APIVersion: "terraform.core.oam.dev/v1beta2",
|
|
Kind: "Configuration",
|
|
},
|
|
},
|
|
Schematic: &commontype.Schematic{
|
|
Terraform: terraform,
|
|
},
|
|
},
|
|
}
|
|
if provider != "alibaba" {
|
|
def.Spec.Schematic.Terraform.ProviderReference = &crossplane.Reference{
|
|
Name: provider,
|
|
Namespace: "default",
|
|
}
|
|
}
|
|
var out bytes.Buffer
|
|
err = json.NewSerializerWithOptions(json.DefaultMetaFactory, nil, nil, json.SerializerOptions{Yaml: true}).Encode(&def, &out)
|
|
if err != nil {
|
|
return "", errors.Wrapf(err, "failed to marshal component definition")
|
|
}
|
|
return out.String(), nil
|
|
default:
|
|
return "", errors.Errorf("Provider `%s` is not supported. Only `alibaba`, `aws`, `azure`, `gcp`, `baidu`, `tencent`, `elastic`, `ucloud`, `vsphere` are supported.", provider)
|
|
}
|
|
}
|
|
|
|
func getSingleDefinition(cmd *cobra.Command, definitionName string, client client.Client, definitionType string, namespace string) (*pkgdef.Definition, error) {
|
|
definitions, err := pkgdef.SearchDefinition(client, definitionType, namespace, filters.ByName(definitionName))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(definitions) == 0 {
|
|
return nil, fmt.Errorf("definition not found")
|
|
}
|
|
if len(definitions) > 1 {
|
|
table := newUITable()
|
|
table.AddRow("NAME", "TYPE", "NAMESPACE", "DESCRIPTION")
|
|
for _, definition := range definitions {
|
|
desc := ""
|
|
if annotations := definition.GetAnnotations(); annotations != nil {
|
|
desc = annotations[pkgdef.DescriptionKey]
|
|
}
|
|
table.AddRow(definition.GetName(), definition.GetKind(), definition.GetNamespace(), desc)
|
|
}
|
|
cmd.Println(table)
|
|
return nil, fmt.Errorf("found %d definitions, please specify which one to select with more arguments", len(definitions))
|
|
}
|
|
return &pkgdef.Definition{Unstructured: definitions[0]}, nil
|
|
}
|
|
|
|
// getDefRevs will search for DefinitionRevisions with specified conditions.
|
|
// Check SearchDefinitionRevisions for details.
|
|
func getDefRevs(ctx context.Context, client client.Client, ns, defTypeStr, defName string, rev int64) ([]v1beta1.DefinitionRevision, error) {
|
|
defType, ok := pkgdef.StringToDefinitionType[defTypeStr]
|
|
// Empty definition type is intentionally allowed, to allow the user to match all definition types
|
|
if defTypeStr != "" && !ok {
|
|
return nil, fmt.Errorf("%s is not a valid type. Valid types are %v", defTypeStr, reflect.ValueOf(pkgdef.StringToDefinitionType).MapKeys())
|
|
}
|
|
|
|
return pkgdef.SearchDefinitionRevisions(ctx, client, ns, defName, defType, rev)
|
|
}
|
|
|
|
// printDefRevs will print DefinitionRevisions
|
|
func printDefRevs(ctx context.Context, cmd *cobra.Command, client client.Client, ns, defTypeStr, defName string) error {
|
|
revs, err := getDefRevs(ctx, client, ns, defTypeStr, defName, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
table := newUITable()
|
|
table.AddRow("NAME", "REVISION", "TYPE", "HASH")
|
|
for _, rev := range revs {
|
|
table.AddRow(defName, rev.Spec.Revision, rev.Spec.DefinitionType, rev.Spec.RevisionHash)
|
|
}
|
|
cmd.Println(table)
|
|
|
|
return nil
|
|
}
|
|
|
|
// NewDefinitionGetCommand create the `vela def get` command to get definition from k8s
|
|
func NewDefinitionGetCommand(c common.Args) *cobra.Command {
|
|
var listRevisions bool
|
|
var targetRevision string
|
|
cmd := &cobra.Command{
|
|
Use: "get NAME",
|
|
Short: "Get definition",
|
|
Long: "Get definition from kubernetes cluster",
|
|
Example: "# Command below will get the ComponentDefinition(or other definitions if exists) of webservice in all namespaces\n" +
|
|
"> vela def get webservice\n" +
|
|
"# Command below will get the TraitDefinition of annotations in namespace vela-system\n" +
|
|
"> vela def get annotations --type trait --namespace vela-system",
|
|
Args: cobra.ExactArgs(1),
|
|
Annotations: map[string]string{
|
|
types.TagCommandType: types.TypeDefManagement,
|
|
types.TagCommandOrder: "2",
|
|
},
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
definitionType, err := cmd.Flags().GetString(FlagType)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", FlagType)
|
|
}
|
|
namespace, err := cmd.Flags().GetString(FlagNamespace)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", Namespace)
|
|
}
|
|
k8sClient, err := c.GetClient()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get k8s client")
|
|
}
|
|
|
|
if listRevisions {
|
|
return printDefRevs(context.Background(), cmd, k8sClient, namespace, definitionType, args[0])
|
|
}
|
|
|
|
var def *pkgdef.Definition
|
|
|
|
// Get history Definition from DefinitionRevisions
|
|
if targetRevision != "" {
|
|
// "v1", "1", both need to work
|
|
targetRevision = strings.TrimPrefix(targetRevision, "v")
|
|
ver, err := strconv.Atoi(targetRevision)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid version: %w", err)
|
|
}
|
|
|
|
// Get the user-specified revision.
|
|
revs, err := getDefRevs(context.Background(), k8sClient, namespace, definitionType, args[0], int64(ver))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(revs) == 0 {
|
|
return fmt.Errorf("no %s with revision %s found in namespace %s", args[0], targetRevision, namespace)
|
|
}
|
|
|
|
// Now we have at least one DefinitionRevision (typically it will only be one).
|
|
// They all fit user's conditions. We will use the first one.
|
|
// Extract Definition from DefinitionRevision that we just got.
|
|
def, err = pkgdef.GetDefinitionFromDefinitionRevision(&revs[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
def, err = getSingleDefinition(cmd, args[0], k8sClient, definitionType, namespace)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
cueString, err := def.ToCUEString()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get cue format definition")
|
|
}
|
|
if _, err = cmd.OutOrStdout().Write([]byte(cueString + "\n")); err != nil {
|
|
return errors.Wrapf(err, "failed to write out cue string")
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
cmd.Flags().StringP(FlagType, "t", "", "Specify which definition type to get. If empty, all types will be searched. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", "))
|
|
cmd.Flags().BoolVarP(&listRevisions, "revisions", "", false, "List revisions of the specified definition.")
|
|
cmd.Flags().StringVarP(&targetRevision, "revision", "r", "", "Get the specified version of a definition.")
|
|
cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
|
|
return cmd
|
|
}
|
|
|
|
// NewDefinitionDocGenCommand create the `vela def doc-gen` command to generate documentation of definitions
|
|
func NewDefinitionDocGenCommand(c common.Args, ioStreams util.IOStreams) *cobra.Command {
|
|
var docPath, location, i18nPath string
|
|
cmd := &cobra.Command{
|
|
Use: "doc-gen NAME",
|
|
Short: "Generate documentation for definitions",
|
|
Long: "Generate documentation for definitions",
|
|
Example: "1. Generate documentation for ComponentDefinition webservice:\n" +
|
|
"> vela def doc-gen webservice -n vela-system\n" +
|
|
"2. Generate documentation for local CUE Definition file webservice.cue:\n" +
|
|
"> vela def doc-gen webservice.cue\n" +
|
|
"3. Generate documentation for local Cloud Resource Definition YAML alibaba-vpc.yaml:\n" +
|
|
"> vela def doc-gen alibaba-vpc.yaml\n",
|
|
Deprecated: "This command has been replaced by 'vela show' or 'vela def show'.",
|
|
Annotations: map[string]string{
|
|
types.TagCommandType: types.TypeDefGeneration,
|
|
types.TagCommandOrder: "1",
|
|
},
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if len(args) == 0 {
|
|
return fmt.Errorf("please specify definition name, cue file or a cloud resource definition yaml")
|
|
}
|
|
namespace, err := cmd.Flags().GetString(FlagNamespace)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", Namespace)
|
|
}
|
|
return ShowReferenceMarkdown(context.Background(), c, ioStreams, args[0], docPath, location, i18nPath, namespace, 0)
|
|
|
|
},
|
|
}
|
|
cmd.Flags().StringVarP(&docPath, "path", "p", "", "Specify the path for of the doc generated from definition.")
|
|
cmd.Flags().StringVarP(&location, "location", "l", "", "specify the location for of the doc generated from definition, now supported options 'zh', 'en'. ")
|
|
cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
|
|
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'. ")
|
|
return cmd
|
|
}
|
|
|
|
// NewDefinitionListCommand create the `vela def list` command to list definition from k8s
|
|
func NewDefinitionListCommand(c common.Args) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "list",
|
|
Short: "List definitions.",
|
|
Long: "List definitions in kubernetes cluster.",
|
|
Example: "# Command below will list all definitions in all namespaces\n" +
|
|
"> vela def list\n" +
|
|
"# Command below will list all definitions in the vela-system namespace\n" +
|
|
"> vela def get annotations --type trait --namespace vela-system",
|
|
Args: cobra.ExactArgs(0),
|
|
Annotations: map[string]string{
|
|
types.TagCommandType: types.TypeDefManagement,
|
|
types.TagCommandOrder: "3",
|
|
},
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
definitionType, err := cmd.Flags().GetString(FlagType)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", FlagType)
|
|
}
|
|
namespace, err := cmd.Flags().GetString(FlagNamespace)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", Namespace)
|
|
}
|
|
addonName, err := cmd.Flags().GetString("from")
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", "from")
|
|
}
|
|
k8sClient, err := c.GetClient()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get k8s client")
|
|
}
|
|
definitions, err := pkgdef.SearchDefinition(k8sClient,
|
|
definitionType,
|
|
namespace,
|
|
filters.ByOwnerAddon(addonName))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(definitions) == 0 {
|
|
cmd.Println("No definition found.")
|
|
return nil
|
|
}
|
|
// Determine if there is a definition in the list from some addons
|
|
// This is used to tell if we want the SOURCE-ADDON column
|
|
showSourceAddon := false
|
|
for _, def := range definitions {
|
|
ownerRef := def.GetOwnerReferences()
|
|
if len(ownerRef) > 0 && strings.HasPrefix(ownerRef[0].Name, addonutil.AddonAppPrefix) {
|
|
showSourceAddon = true
|
|
break
|
|
}
|
|
}
|
|
table := newUITable()
|
|
|
|
// We only include SOURCE-ADDON if there is at least one definition from an addon
|
|
if showSourceAddon {
|
|
table.AddRow("NAME", "TYPE", "NAMESPACE", "SOURCE-ADDON", "DESCRIPTION")
|
|
} else {
|
|
table.AddRow("NAME", "TYPE", "NAMESPACE", "DESCRIPTION")
|
|
}
|
|
|
|
for _, definition := range definitions {
|
|
desc := ""
|
|
if annotations := definition.GetAnnotations(); annotations != nil {
|
|
desc = annotations[pkgdef.DescriptionKey]
|
|
}
|
|
|
|
// Do not show SOURCE-ADDON column
|
|
if !showSourceAddon {
|
|
table.AddRow(definition.GetName(), definition.GetKind(), definition.GetNamespace(), desc)
|
|
continue
|
|
}
|
|
|
|
sourceAddon := ""
|
|
if len(definition.GetOwnerReferences()) > 0 {
|
|
sourceAddon = strings.TrimPrefix(definition.GetOwnerReferences()[0].Name, "addon-")
|
|
}
|
|
table.AddRow(definition.GetName(), definition.GetKind(), definition.GetNamespace(), sourceAddon, desc)
|
|
}
|
|
cmd.Println(table)
|
|
return nil
|
|
},
|
|
}
|
|
cmd.Flags().StringP(FlagType, "t", "", "Specify which definition type to list. If empty, all types will be searched. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", "))
|
|
cmd.Flags().String("from", "", "Filter definitions by which addon installed them.")
|
|
cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
|
|
return cmd
|
|
}
|
|
|
|
// NewDefinitionEditCommand create the `vela def edit` command to help user edit remote definitions
|
|
func NewDefinitionEditCommand(c common.Args) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "edit NAME",
|
|
Short: "Edit X-Definition.",
|
|
Long: "Edit X-Definition in kubernetes. If type and namespace are not specified, the command will automatically search all possible results.\n" +
|
|
"By default, this command will use the vi editor and can be altered by setting EDITOR environment variable.",
|
|
Example: "# Command below will edit the ComponentDefinition (and other definitions if exists) of webservice in kubernetes\n" +
|
|
"> vela def edit webservice\n" +
|
|
"# Command below will edit the TraitDefinition of ingress in vela-system namespace\n" +
|
|
"> vela def edit ingress --type trait --namespace vela-system",
|
|
Args: cobra.ExactArgs(1),
|
|
Annotations: map[string]string{
|
|
types.TagCommandType: types.TypeDefManagement,
|
|
types.TagCommandOrder: "4",
|
|
},
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
definitionType, err := cmd.Flags().GetString(FlagType)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", FlagType)
|
|
}
|
|
namespace, err := cmd.Flags().GetString(FlagNamespace)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", Namespace)
|
|
}
|
|
config, err := c.GetConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
k8sClient, err := c.GetClient()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get k8s client")
|
|
}
|
|
def, err := getSingleDefinition(cmd, args[0], k8sClient, definitionType, namespace)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cueString, err := def.ToCUEString()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get cue format definition")
|
|
}
|
|
cleanup := func(filePath string) {
|
|
if err := os.Remove(filePath); err != nil {
|
|
cmd.PrintErrf("failed to remove file %s: %v", filePath, err)
|
|
}
|
|
}
|
|
filename := fmt.Sprintf("vela-def-%d", time.Now().UnixNano())
|
|
tempFilePath := filepath.Join(os.TempDir(), filename+CUEExtension)
|
|
if err := os.WriteFile(tempFilePath, []byte(cueString), 0600); err != nil {
|
|
return errors.Wrapf(err, "failed to write temporary file")
|
|
}
|
|
defer cleanup(tempFilePath)
|
|
editor := os.Getenv("EDITOR")
|
|
if editor == "" {
|
|
editor = "vi"
|
|
}
|
|
scriptFilePath := filepath.Join(os.TempDir(), filename+".sh")
|
|
if err := os.WriteFile(scriptFilePath, []byte(editor+" "+tempFilePath), 0600); err != nil {
|
|
return errors.Wrapf(err, "failed to write temporary script file")
|
|
}
|
|
defer cleanup(scriptFilePath)
|
|
|
|
editCmd := exec.Command("sh", path.Clean(scriptFilePath)) //nolint:gosec
|
|
editCmd.Stdin = os.Stdin
|
|
editCmd.Stdout = os.Stdout
|
|
editCmd.Stderr = os.Stderr
|
|
if err = editCmd.Run(); err != nil {
|
|
return errors.Wrapf(err, "failed to run editor %s at path %s", editor, scriptFilePath)
|
|
}
|
|
newBuf, err := os.ReadFile(path.Clean(tempFilePath))
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to read temporary file %s", tempFilePath)
|
|
}
|
|
if cueString == string(newBuf) {
|
|
cmd.Printf("definition unchanged\n")
|
|
return nil
|
|
}
|
|
if err := def.FromCUEString(string(newBuf), config); err != nil {
|
|
return errors.Wrapf(err, "failed to load edited cue string")
|
|
}
|
|
if err := k8sClient.Update(context.Background(), def); err != nil {
|
|
return errors.Wrapf(err, "failed to apply changes to kubernetes")
|
|
}
|
|
cmd.Printf("Definition edited successfully.\n")
|
|
return nil
|
|
},
|
|
}
|
|
cmd.Flags().StringP(FlagType, "t", "", "Specify which definition type to get. If empty, all types will be searched. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", "))
|
|
cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
|
|
return cmd
|
|
}
|
|
|
|
func prettyYAMLMarshal(obj map[string]interface{}) (string, error) {
|
|
var b bytes.Buffer
|
|
encoder := yaml.NewEncoder(&b)
|
|
encoder.SetIndent(2)
|
|
err := encoder.Encode(&obj)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return b.String(), nil
|
|
}
|
|
|
|
// FlagFormat is the flag for specifying the output format
|
|
const FlagFormat = "format"
|
|
|
|
// 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
|
|
//
|
|
//nolint:gocyclo // CLI command handling multiple input/output formats and file types
|
|
func NewDefinitionRenderCommand(c common.Args) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "render DEFINITION.cue|DEFINITION.go",
|
|
Short: "Render X-Definition.",
|
|
Long: "Render X-Definition with CUE or Go format into kubernetes YAML format (default) or CUE format. Could be used to check whether the definition is working as expected. " +
|
|
"If a directory is used as input, all CUE and Go definitions in the directory will be rendered.\n\n" +
|
|
"Go definitions use the defkit package to define components, traits, policies, and workflow steps in Go code. " +
|
|
"The Go file must import defkit and export functions that return definition types (e.g., *defkit.ComponentDefinition).\n\n" +
|
|
"Use --format cue to output the raw CUE template instead of YAML. This is useful for inspecting the generated CUE from Go definitions.",
|
|
Example: "# Command below will render my-webservice.cue into YAML format and print it out.\n" +
|
|
"> vela def render my-webservice.cue\n" +
|
|
"# Command below will render a Go definition into YAML format.\n" +
|
|
"> vela def render my-webservice.go\n" +
|
|
"# Command below will render a Go definition into CUE format.\n" +
|
|
"> vela def render my-webservice.go --format cue\n" +
|
|
"# Command below will render my-webservice.cue and save it in my-webservice.yaml.\n" +
|
|
"> vela def render my-webservice.cue -o my-webservice.yaml\n" +
|
|
"# Command below will render all CUE and Go definitions in the ./defs/ directory and save the YAML objects in ./defs/yaml/.\n" +
|
|
"> vela def render ./defs/ -o ./defs/yaml/\n" +
|
|
"# Command below will render all CUE and Go definitions in ./defs/ and save CUE output in ./defs/cue/.\n" +
|
|
"> vela def render ./defs/ -o ./defs/cue/ --format cue",
|
|
Args: cobra.ExactArgs(1),
|
|
Annotations: map[string]string{
|
|
types.TagCommandType: types.TypeDefManagement,
|
|
types.TagCommandOrder: "5",
|
|
},
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
output, err := cmd.Flags().GetString(FlagOutput)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", FlagOutput)
|
|
}
|
|
message, err := cmd.Flags().GetString(FlagMessage)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", FlagMessage)
|
|
}
|
|
outputFormat, err := cmd.Flags().GetString(FlagFormat)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", FlagFormat)
|
|
}
|
|
// Validate output format
|
|
if outputFormat != "yaml" && outputFormat != "cue" {
|
|
return errors.Errorf("invalid format %q, must be 'yaml' or 'cue'", outputFormat)
|
|
}
|
|
|
|
// renderCUE renders a CUE definition file to YAML or CUE output
|
|
renderCUE := func(inputFilename, outputFilename string) error {
|
|
cueBytes, err := utils.ReadRemoteOrLocalPath(inputFilename, false)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get %s", args[0])
|
|
}
|
|
|
|
// If format is cue, just output the raw CUE content
|
|
if outputFormat == "cue" {
|
|
cueStr := string(cueBytes)
|
|
if outputFilename == "" {
|
|
cmd.Printf("--- %s ---\n%s\n", filepath.Base(inputFilename), cueStr)
|
|
} else {
|
|
if message != "" {
|
|
cueStr = "// " + strings.ReplaceAll(message, "{{INPUT_FILENAME}}", filepath.Base(inputFilename)) + "\n" + cueStr
|
|
}
|
|
if err := os.WriteFile(outputFilename, []byte(cueStr), 0600); err != nil {
|
|
return errors.Wrapf(err, "failed to write CUE format definition to file %s", outputFilename)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
config, err := c.GetConfig()
|
|
if err != nil {
|
|
klog.Infof("ignore kubernetes cluster, unable to get kubeconfig: %s", err.Error())
|
|
}
|
|
def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
|
|
if err := def.FromCUEString(string(cueBytes), config); err != nil {
|
|
return errors.Wrapf(err, "failed to parse CUE")
|
|
}
|
|
|
|
helmChartFormatEnv := strings.ToLower(os.Getenv(HelmChartFormatEnvName))
|
|
if helmChartFormatEnv == "true" {
|
|
def.SetNamespace(HelmChartNamespacePlaceholder)
|
|
} else if helmChartFormatEnv == "system" {
|
|
def.SetNamespace(types.DefaultKubeVelaNS)
|
|
}
|
|
if len(def.GetLabels()) == 0 {
|
|
def.SetLabels(nil)
|
|
}
|
|
s, err := prettyYAMLMarshal(def.Object)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to marshal CRD into YAML")
|
|
}
|
|
s = strings.ReplaceAll(s, "'"+HelmChartNamespacePlaceholder+"'", "{{ include \"systemDefinitionNamespace\" . }}") + "\n"
|
|
if outputFilename == "" {
|
|
s = fmt.Sprintf("--- %s ---\n%s", filepath.Base(inputFilename), s)
|
|
cmd.Print(s)
|
|
} else {
|
|
if message != "" {
|
|
s = "# " + strings.ReplaceAll(message, "{{INPUT_FILENAME}}", filepath.Base(inputFilename)) + "\n" + s
|
|
}
|
|
s = "# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.\n" + s
|
|
if err := os.WriteFile(outputFilename, []byte(s), 0600); err != nil {
|
|
return errors.Wrapf(err, "failed to write YAML format definition to file %s", outputFilename)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// renderGo renders a Go definition file to YAML or CUE output
|
|
renderGo := func(inputFilename, outputFilename string) error {
|
|
results, err := goloader.LoadFromFile(inputFilename)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to load Go definition from %s", inputFilename)
|
|
}
|
|
|
|
// If format is cue, output the raw generated CUE content
|
|
if outputFormat == "cue" {
|
|
var allCUE strings.Builder
|
|
for _, result := range results {
|
|
if result.Error != nil {
|
|
return errors.Wrapf(result.Error, "failed to generate CUE for %s", result.Definition.FunctionName)
|
|
}
|
|
if outputFilename == "" {
|
|
allCUE.WriteString(fmt.Sprintf("--- %s (%s) ---\n%s\n", filepath.Base(inputFilename), result.Definition.Name, result.CUE))
|
|
} else {
|
|
allCUE.WriteString(result.CUE)
|
|
if len(results) > 1 {
|
|
allCUE.WriteString("\n---\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
if outputFilename == "" {
|
|
cmd.Print(allCUE.String())
|
|
} else {
|
|
content := allCUE.String()
|
|
if message != "" {
|
|
content = "// " + strings.ReplaceAll(message, "{{INPUT_FILENAME}}", filepath.Base(inputFilename)) + "\n" + content
|
|
}
|
|
content = "// Code generated by KubeVela templates. DO NOT EDIT. Please edit the original Go file.\n" + content
|
|
if err := os.WriteFile(outputFilename, []byte(content), 0600); err != nil {
|
|
return errors.Wrapf(err, "failed to write CUE format definition to file %s", outputFilename)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
config, err := c.GetConfig()
|
|
if err != nil {
|
|
klog.Infof("ignore kubernetes cluster, unable to get kubeconfig: %s", err.Error())
|
|
}
|
|
|
|
var allYAML strings.Builder
|
|
for _, result := range results {
|
|
if result.Error != nil {
|
|
return errors.Wrapf(result.Error, "failed to generate CUE for %s", result.Definition.FunctionName)
|
|
}
|
|
|
|
// Convert generated CUE to Definition
|
|
def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
|
|
if err := def.FromCUEString(result.CUE, config); err != nil {
|
|
return errors.Wrapf(err, "failed to parse generated CUE for %s", result.Definition.FunctionName)
|
|
}
|
|
|
|
helmChartFormatEnv := strings.ToLower(os.Getenv(HelmChartFormatEnvName))
|
|
if helmChartFormatEnv == "true" {
|
|
def.SetNamespace(HelmChartNamespacePlaceholder)
|
|
} else if helmChartFormatEnv == "system" {
|
|
def.SetNamespace(types.DefaultKubeVelaNS)
|
|
}
|
|
if len(def.GetLabels()) == 0 {
|
|
def.SetLabels(nil)
|
|
}
|
|
|
|
s, err := prettyYAMLMarshal(def.Object)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to marshal CRD into YAML for %s", result.Definition.FunctionName)
|
|
}
|
|
s = strings.ReplaceAll(s, "'"+HelmChartNamespacePlaceholder+"'", "{{ include \"systemDefinitionNamespace\" . }}") + "\n"
|
|
|
|
if outputFilename == "" {
|
|
allYAML.WriteString(fmt.Sprintf("--- %s (%s) ---\n%s", filepath.Base(inputFilename), result.Definition.Name, s))
|
|
} else {
|
|
allYAML.WriteString(s)
|
|
if len(results) > 1 {
|
|
allYAML.WriteString("---\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
if outputFilename == "" {
|
|
cmd.Print(allYAML.String())
|
|
} else {
|
|
content := allYAML.String()
|
|
if message != "" {
|
|
content = "# " + strings.ReplaceAll(message, "{{INPUT_FILENAME}}", filepath.Base(inputFilename)) + "\n" + content
|
|
}
|
|
content = "# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original Go file.\n" + content
|
|
if err := os.WriteFile(outputFilename, []byte(content), 0600); err != nil {
|
|
return errors.Wrapf(err, "failed to write YAML format definition to file %s", outputFilename)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// render dispatches to the appropriate renderer based on file extension
|
|
render := func(inputFilename, outputFilename string) error {
|
|
ext := filepath.Ext(inputFilename)
|
|
switch ext {
|
|
case GoExtension:
|
|
// Skip test files
|
|
if strings.HasSuffix(inputFilename, "_test.go") {
|
|
return nil
|
|
}
|
|
return renderGo(inputFilename, outputFilename)
|
|
case CUEExtension:
|
|
return renderCUE(inputFilename, outputFilename)
|
|
default:
|
|
return errors.Errorf("unsupported file extension %s, expected .cue or .go", ext)
|
|
}
|
|
}
|
|
|
|
inputFilenames := []string{args[0]}
|
|
outputFilenames := []string{output}
|
|
fi, err := os.Stat(args[0])
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get input %s", args[0])
|
|
}
|
|
if fi.IsDir() {
|
|
inputFilenames = []string{}
|
|
outputFilenames = []string{}
|
|
err := filepath.Walk(args[0], func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
filename := filepath.Base(path)
|
|
fileSuffix := filepath.Ext(path)
|
|
|
|
// Determine output extension based on format
|
|
outputExt := YAMLExtension
|
|
if outputFormat == "cue" {
|
|
outputExt = CUEExtension
|
|
}
|
|
|
|
// Handle CUE files
|
|
if fileSuffix == CUEExtension {
|
|
inputFilenames = append(inputFilenames, path)
|
|
if output != "" {
|
|
outputFilenames = append(outputFilenames, filepath.Join(output, strings.ReplaceAll(filename, CUEExtension, outputExt)))
|
|
} else {
|
|
outputFilenames = append(outputFilenames, "")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Handle Go files (skip test files)
|
|
if fileSuffix == GoExtension && !strings.HasSuffix(filename, "_test.go") {
|
|
// Check if this is a defkit definition file
|
|
isDefFile, checkErr := goloader.IsGoDefinitionFile(path)
|
|
if checkErr != nil {
|
|
klog.Warningf("failed to check if %s is a Go definition file: %v", path, checkErr)
|
|
return nil
|
|
}
|
|
if isDefFile {
|
|
inputFilenames = append(inputFilenames, path)
|
|
if output != "" {
|
|
outputFilenames = append(outputFilenames, filepath.Join(output, strings.ReplaceAll(filename, GoExtension, outputExt)))
|
|
} else {
|
|
outputFilenames = append(outputFilenames, "")
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to read directory %s", args[0])
|
|
}
|
|
}
|
|
var renderErrs []error
|
|
for i, inputFilename := range inputFilenames {
|
|
renderErr := render(inputFilename, outputFilenames[i])
|
|
if renderErr != nil {
|
|
wrappedRenderErr := errors.Wrapf(renderErr, "failed to render %s", inputFilename)
|
|
if _, writeErr := fmt.Fprintln(cmd.ErrOrStderr(), wrappedRenderErr); writeErr != nil {
|
|
renderErrs = append(renderErrs, errors.Wrapf(writeErr, "failed to write to stderr for %s", inputFilename))
|
|
}
|
|
renderErrs = append(renderErrs, wrappedRenderErr)
|
|
}
|
|
}
|
|
if len(renderErrs) > 0 {
|
|
return fmt.Errorf("rendering failed for %d file(s)", len(renderErrs))
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
cmd.Flags().StringP(FlagOutput, "o", "", "Specify the output path of the rendered definition. 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.")
|
|
cmd.Flags().StringP(FlagMessage, "", "", "Specify the header message of the generated file. For example, declaring author information.")
|
|
cmd.Flags().StringP(FlagFormat, "f", "yaml", "Specify the output format: 'yaml' (default) for Kubernetes YAML, or 'cue' for raw CUE template output.")
|
|
return cmd
|
|
}
|
|
|
|
// NewDefinitionApplyCommand create the `vela def apply` command to help user apply local definitions to k8s
|
|
func NewDefinitionApplyCommand(c common.Args, streams util.IOStreams) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "apply DEFINITION.cue|DEFINITION.go",
|
|
Short: "Apply X-Definition.",
|
|
Long: "Apply X-Definition from local storage to kubernetes cluster. It will apply file to vela-system namespace by default.\n\n" +
|
|
"Supports CUE, YAML, and Go definition files. Go definitions use the defkit package to define components, traits, policies, and workflow steps.",
|
|
Example: "# Command below will apply the local my-webservice.cue file to kubernetes vela-system namespace\n" +
|
|
"> vela def apply my-webservice.cue\n" +
|
|
"# Apply a Go definition file to kubernetes vela-system namespace\n" +
|
|
"> vela def apply my-webservice.go\n" +
|
|
"# Apply the local directory including all files (YAML, CUE, and Go definitions) to kubernetes vela-system namespace\n" +
|
|
"> vela def apply def/\n" +
|
|
"# Command below will apply the ./defs/my-trait.cue file to kubernetes default namespace\n" +
|
|
"> vela def apply ./defs/my-trait.cue --namespace default\n" +
|
|
"# Command below will convert the ./defs/my-trait.cue file to kubernetes CRD object and print it without applying it to kubernetes\n" +
|
|
"> vela def apply ./defs/my-trait.cue --dry-run\n" +
|
|
"# Apply a CUE from URL \n" +
|
|
"> vela def apply https://my-host-to-def/my-trait.cue --dry-run\n" +
|
|
"# Apply a CUE from stdin \n" +
|
|
"> vela def apply -",
|
|
Args: cobra.ExactArgs(1),
|
|
Annotations: map[string]string{
|
|
types.TagCommandType: types.TypeDefManagement,
|
|
types.TagCommandOrder: "6",
|
|
},
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
ctx := context.Background()
|
|
dryRun, err := cmd.Flags().GetBool(FlagDryRun)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", FlagDryRun)
|
|
}
|
|
namespace, err := cmd.Flags().GetString(FlagNamespace)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", Namespace)
|
|
}
|
|
if len(args) < 1 {
|
|
return errors.New("you must specify the definition path, directory or URL")
|
|
}
|
|
return defApplyAll(ctx, c, streams, namespace, args[0], dryRun)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().BoolP(FlagDryRun, "", false, "only build definition from CUE into CRB object without applying it to kubernetes clusters")
|
|
cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
|
|
return cmd
|
|
}
|
|
|
|
// isDefinitionFile checks if the path is a definition file (JSON, YAML, CUE, or Go)
|
|
func isDefinitionFile(path string) bool {
|
|
// Check for standard definition file types
|
|
if utils.IsJSONYAMLorCUEFile(path) {
|
|
return true
|
|
}
|
|
// Check for Go definition files (skip test files)
|
|
if strings.HasSuffix(path, GoExtension) && !strings.HasSuffix(path, "_test.go") {
|
|
isDefFile, err := goloader.IsGoDefinitionFile(path)
|
|
if err != nil {
|
|
klog.Warningf("failed to check if %s is a Go definition file: %v", path, err)
|
|
return false
|
|
}
|
|
return isDefFile
|
|
}
|
|
return false
|
|
}
|
|
|
|
func defApplyAll(ctx context.Context, c common.Args, io util.IOStreams, namespace, path string, dryRun bool) error {
|
|
files, err := utils.LoadDataFromPath(ctx, path, isDefinitionFile)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get from %s", path)
|
|
}
|
|
for _, f := range files {
|
|
results, err := defApplyOne(ctx, c, namespace, f.Path, f.Data, dryRun)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, result := range results {
|
|
io.Infonln(result)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func defApplyOne(ctx context.Context, c common.Args, namespace, defpath string, defBytes []byte, dryRun bool) ([]string, error) {
|
|
config, err := c.GetConfig()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
k8sClient, err := c.GetClient()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get k8s client")
|
|
}
|
|
|
|
// Handle Go definition files
|
|
if strings.HasSuffix(defpath, GoExtension) {
|
|
return defApplyGoFile(ctx, c, k8sClient, config, namespace, defpath, dryRun)
|
|
}
|
|
|
|
def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
|
|
|
|
switch {
|
|
case strings.HasSuffix(defpath, YAMLExtension) || strings.HasSuffix(defpath, YMLExtension):
|
|
// In this case, it's not in cue format, it's a yaml
|
|
if err = def.FromYAML(defBytes); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to parse YAML to definition")
|
|
}
|
|
if dryRun {
|
|
return nil, errors.New("dry-run will render CUE to YAML, while the input is already in yaml")
|
|
}
|
|
// YAML won't validate or format CUE schematic
|
|
op, err := utils.CreateOrUpdate(ctx, k8sClient, &def)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []string{fmt.Sprintf("%s %s in namespace %s %s.\n", def.GetKind(), def.GetName(), def.GetNamespace(), op)}, nil
|
|
default:
|
|
if err := def.FromCUEString(string(defBytes), config); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to parse CUE for definition")
|
|
}
|
|
def.SetNamespace(namespace)
|
|
}
|
|
|
|
if dryRun {
|
|
s, err := prettyYAMLMarshal(def.Object)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to marshal CRD into YAML")
|
|
}
|
|
return []string{s}, nil
|
|
}
|
|
|
|
oldDef := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
|
|
oldDef.SetGroupVersionKind(def.GroupVersionKind())
|
|
err = k8sClient.Get(ctx, types2.NamespacedName{
|
|
Namespace: def.GetNamespace(),
|
|
Name: def.GetName(),
|
|
}, &oldDef)
|
|
if err != nil {
|
|
if errors2.IsNotFound(err) {
|
|
kind := def.GetKind()
|
|
if err = k8sClient.Create(ctx, &def); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to create new definition in kubernetes")
|
|
}
|
|
return []string{fmt.Sprintf("%s %s created in namespace %s.\n", kind, def.GetName(), def.GetNamespace())}, nil
|
|
}
|
|
return nil, errors.Wrapf(err, "failed to check existence of target definition in kubernetes")
|
|
}
|
|
if err := oldDef.FromCUEString(string(defBytes), config); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to merge with existing definition")
|
|
}
|
|
if err = k8sClient.Update(ctx, &oldDef); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to update existing definition in kubernetes")
|
|
}
|
|
return []string{fmt.Sprintf("%s %s in namespace %s updated.\n", oldDef.GetKind(), oldDef.GetName(), oldDef.GetNamespace())}, nil
|
|
}
|
|
|
|
// defApplyGoFile handles applying Go definition files
|
|
// Args is kept for future use (e.g., Schema for type registration, DiscoveryClient for API discovery)
|
|
func defApplyGoFile(ctx context.Context, _ common.Args, k8sClient client.Client, config *rest.Config, namespace, defpath string, dryRun bool) ([]string, error) {
|
|
results, err := goloader.LoadFromFile(defpath)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to load Go definition from %s", defpath)
|
|
}
|
|
|
|
var outputs []string
|
|
for _, result := range results {
|
|
if result.Error != nil {
|
|
return nil, errors.Wrapf(result.Error, "failed to generate CUE for %s", result.Definition.FunctionName)
|
|
}
|
|
|
|
def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
|
|
if err := def.FromCUEString(result.CUE, config); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to parse generated CUE for %s", result.Definition.FunctionName)
|
|
}
|
|
def.SetNamespace(namespace)
|
|
|
|
if dryRun {
|
|
s, err := prettyYAMLMarshal(def.Object)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to marshal CRD into YAML for %s", result.Definition.FunctionName)
|
|
}
|
|
outputs = append(outputs, s)
|
|
continue
|
|
}
|
|
|
|
oldDef := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
|
|
oldDef.SetGroupVersionKind(def.GroupVersionKind())
|
|
err = k8sClient.Get(ctx, types2.NamespacedName{
|
|
Namespace: def.GetNamespace(),
|
|
Name: def.GetName(),
|
|
}, &oldDef)
|
|
if err != nil {
|
|
if errors2.IsNotFound(err) {
|
|
kind := def.GetKind()
|
|
if err = k8sClient.Create(ctx, &def); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to create new definition in kubernetes")
|
|
}
|
|
outputs = append(outputs, fmt.Sprintf("%s %s created in namespace %s.\n", kind, def.GetName(), def.GetNamespace()))
|
|
continue
|
|
}
|
|
return nil, errors.Wrapf(err, "failed to check existence of target definition in kubernetes")
|
|
}
|
|
if err := oldDef.FromCUEString(result.CUE, config); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to merge with existing definition")
|
|
}
|
|
if err = k8sClient.Update(ctx, &oldDef); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to update existing definition in kubernetes")
|
|
}
|
|
outputs = append(outputs, fmt.Sprintf("%s %s in namespace %s updated.\n", oldDef.GetKind(), oldDef.GetName(), oldDef.GetNamespace()))
|
|
}
|
|
return outputs, nil
|
|
}
|
|
|
|
// NewDefinitionDelCommand create the `vela def del` command to help user delete existing definitions conveniently
|
|
func NewDefinitionDelCommand(c common.Args) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "del DEFINITION_NAME",
|
|
Short: "Delete X-Definition.",
|
|
Long: "Delete X-Definition in kubernetes cluster.",
|
|
Example: "# Command below will delete TraitDefinition of annotations in default namespace\n" +
|
|
"> vela def del annotations -t trait -n default",
|
|
Args: cobra.ExactArgs(1),
|
|
Annotations: map[string]string{
|
|
types.TagCommandType: types.TypeDefManagement,
|
|
types.TagCommandOrder: "7",
|
|
},
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
definitionType, err := cmd.Flags().GetString(FlagType)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", FlagType)
|
|
}
|
|
namespace, err := cmd.Flags().GetString(FlagNamespace)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get `%s`", Namespace)
|
|
}
|
|
k8sClient, err := c.GetClient()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get k8s client")
|
|
}
|
|
def, err := getSingleDefinition(cmd, args[0], k8sClient, definitionType, namespace)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
desc := def.GetAnnotations()[pkgdef.DescriptionKey]
|
|
toDelete := false
|
|
_, err = getPrompt(cmd, bufio.NewReader(cmd.InOrStdin()),
|
|
fmt.Sprintf("Are you sure to delete the following definition in namespace %s?\n", def.GetNamespace())+
|
|
fmt.Sprintf("%s %s: %s\n", def.GetKind(), def.GetName(), desc),
|
|
"[yes|no] > ",
|
|
func(resp string) error {
|
|
switch strings.ToLower(resp) {
|
|
case "yes":
|
|
toDelete = true
|
|
case "y":
|
|
toDelete = true
|
|
case "no":
|
|
toDelete = false
|
|
case "n":
|
|
toDelete = false
|
|
default:
|
|
return errors.New("invalid input")
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !toDelete {
|
|
return nil
|
|
}
|
|
if err := k8sClient.Delete(context.Background(), def); err != nil {
|
|
return errors.Wrapf(err, "failed to delete %s %s in namespace %s", def.GetKind(), def.GetName(), def.GetNamespace())
|
|
}
|
|
cmd.Printf("%s %s in namespace %s deleted.\n", def.GetKind(), def.GetName(), def.GetNamespace())
|
|
return nil
|
|
},
|
|
}
|
|
cmd.Flags().StringP(FlagType, "t", "", "Specify the definition type of target. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", "))
|
|
cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
|
|
return cmd
|
|
}
|
|
|
|
// isCUEorGoDefinitionFile checks if a file is a CUE file or a Go definition file
|
|
func isCUEorGoDefinitionFile(path string) bool {
|
|
if utils.IsCUEFile(path) {
|
|
return true
|
|
}
|
|
if strings.HasSuffix(path, GoExtension) && !strings.HasSuffix(path, "_test.go") {
|
|
isDefFile, err := goloader.IsGoDefinitionFile(path)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return isDefFile
|
|
}
|
|
return false
|
|
}
|
|
|
|
func NewDefinitionValidateCommand(c common.Args) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "vet DEFINITION.cue|DEFINITION.go",
|
|
Short: "Validate X-Definition.",
|
|
Long: "Validate definition file by checking whether it has the valid CUE or Go format with fields set correctly.\n" +
|
|
"Supports both CUE files and Go definition files using the defkit package.\n" +
|
|
"* For CUE files, this command checks the CUE syntax and validates the definition structure.\n" +
|
|
"* For Go files, it verifies the Go syntax and ensures the generated CUE is valid.",
|
|
Example: "# Command below will validate the my-def.cue file.\n" +
|
|
"> vela def vet my-def.cue\n" +
|
|
"# Validate a Go definition file.\n" +
|
|
"> vela def vet my-def.go\n" +
|
|
"# Validate every CUE and Go definition file provided\n" +
|
|
"> vela def vet my-def1.cue my-def2.go my-def3.cue\n" +
|
|
"# Validate every CUE and Go definition file in the specified directories\n" +
|
|
"> vela def vet ./test1/ ./test2/",
|
|
Args: cobra.MinimumNArgs(1),
|
|
Annotations: map[string]string{
|
|
types.TagCommandType: types.TypeDefManagement,
|
|
types.TagCommandOrder: "8",
|
|
},
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
for _, arg := range args {
|
|
files, err := utils.LoadDataFromPath(cmd.Context(), arg, isCUEorGoDefinitionFile)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get file from %s", arg)
|
|
}
|
|
for _, file := range files {
|
|
validateRes, err := validateDefinitionFile(file.Path, file.Data, c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(cmd.OutOrStdout(), "%s", validateRes)
|
|
}
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
return cmd
|
|
}
|
|
|
|
func validateDefinitionFile(fileName string, fileData []byte, c common.Args) (string, error) {
|
|
// Handle Go definition files
|
|
if strings.HasSuffix(fileName, GoExtension) {
|
|
return validateGoDefinitionFile(fileName, c)
|
|
}
|
|
// Handle CUE files
|
|
return validateCueFile(fileName, fileData, c)
|
|
}
|
|
|
|
func validateCueFile(fileName string, fileData []byte, c common.Args) (string, error) {
|
|
def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
|
|
config, err := c.GetConfig()
|
|
if err != nil {
|
|
klog.Infof("ignore kubernetes cluster, unable to get kubeconfig: %s", err.Error())
|
|
}
|
|
if err := def.FromCUEString(string(fileData), config); err != nil {
|
|
return "", errors.Wrapf(err, "failed to parse CUE: %s", fileName)
|
|
}
|
|
return fmt.Sprintf("Validation %s succeed.\n", fileName), nil
|
|
}
|
|
|
|
func validateGoDefinitionFile(fileName string, c common.Args) (string, error) {
|
|
// Load and generate CUE from the Go file
|
|
results, err := goloader.LoadFromFile(fileName)
|
|
if err != nil {
|
|
return "", errors.Wrapf(err, "failed to load Go definition: %s", fileName)
|
|
}
|
|
|
|
config, err := c.GetConfig()
|
|
if err != nil {
|
|
klog.Infof("ignore kubernetes cluster, unable to get kubeconfig: %s", err.Error())
|
|
}
|
|
|
|
var validatedDefs []string
|
|
for _, result := range results {
|
|
if result.Error != nil {
|
|
return "", errors.Wrapf(result.Error, "failed to generate CUE for %s in %s", result.Definition.FunctionName, fileName)
|
|
}
|
|
|
|
// Validate the generated CUE
|
|
def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
|
|
if err := def.FromCUEString(result.CUE, config); err != nil {
|
|
return "", errors.Wrapf(err, "generated CUE is invalid for %s in %s", result.Definition.FunctionName, fileName)
|
|
}
|
|
validatedDefs = append(validatedDefs, result.Definition.Name)
|
|
}
|
|
|
|
if len(validatedDefs) == 1 {
|
|
return fmt.Sprintf("Validation %s succeed (definition: %s).\n", fileName, validatedDefs[0]), nil
|
|
}
|
|
return fmt.Sprintf("Validation %s succeed (definitions: %s).\n", fileName, strings.Join(validatedDefs, ", ")), nil
|
|
}
|
|
|
|
// NewDefinitionGenAPICommand create the `vela def gen-api` command to help user generate Go code from the definition
|
|
func NewDefinitionGenAPICommand(c common.Args) *cobra.Command {
|
|
meta := gen_sdk.GenMeta{}
|
|
var languageArgs []string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "gen-api DEFINITION.cue",
|
|
Short: "Generate SDK from X-Definition.",
|
|
Long: "Generate SDK from X-definition file.\n" +
|
|
"* This command leverage openapi-generator project. Therefore demands \"docker\" exist in PATH\n" +
|
|
"* Currently, this function is still working in progress and not all formats of parameter in X-definition are supported yet.",
|
|
Example: "# Generate SDK for golang with scaffold initialized\n" +
|
|
"> vela def gen-api --init --language go -f /path/to/def -o /path/to/sdk\n" +
|
|
"# Generate incremental definition files to existing sdk directory\n" +
|
|
"> vela def gen-api --language go -f /path/to/def -o /path/to/sdk\n" +
|
|
"# Generate definitions to a sub-module\n" +
|
|
"> 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",
|
|
Annotations: map[string]string{
|
|
types.TagCommandType: types.TypeDefGeneration,
|
|
types.TagCommandOrder: "2",
|
|
},
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
err := meta.Init(c, languageArgs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = meta.CreateScaffold()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = meta.PrepareGeneratorAndTemplate()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = meta.Run(cmd.Context())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVarP(&meta.Output, "output", "o", "./apis", "Output directory path")
|
|
cmd.Flags().StringVar(&meta.APIDirectory, "api-dir", "", "API directory path to put definition API files, relative to output directory. Default value: go: pkg/apis")
|
|
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")
|
|
cmd.Flags().StringVarP(&meta.Package, "package", "p", gen_sdk.PackagePlaceHolder, "Package name of generated code")
|
|
cmd.Flags().StringVarP(&meta.Lang, "language", "g", "go", "Language to generate code. Valid languages: go")
|
|
cmd.Flags().StringVarP(&meta.Template, "template", "t", "", "Template file path, if not specified, the default template will be used")
|
|
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")
|
|
cmd.Flags().BoolVar(&meta.InitSDK, "init", false, "Init the whole SDK project, if not set, only the API file will be generated")
|
|
cmd.Flags().BoolVarP(&meta.Verbose, "verbose", "v", false, "Print verbose logs")
|
|
var langArgsDescStr string
|
|
for lang, args := range gen_sdk.LangArgsRegistry {
|
|
langArgsDescStr += lang + ": \n"
|
|
for key, arg := range args {
|
|
langArgsDescStr += fmt.Sprintf("\t%s: %s(default: %s)\n", key, arg.Name, arg.Default)
|
|
}
|
|
}
|
|
cmd.Flags().StringSliceVar(&languageArgs, "language-args", []string{},
|
|
fmt.Sprintf("language-specific arguments to pass to the go generator, available options: \n%s", langArgsDescStr),
|
|
)
|
|
|
|
return cmd
|
|
}
|
|
|
|
const (
|
|
genTypeProvider = "provider"
|
|
)
|
|
|
|
// NewDefinitionGenCUECommand create the `vela def gen-cue` command to help user generate CUE schema from the go code
|
|
func NewDefinitionGenCUECommand(_ common.Args, streams util.IOStreams) *cobra.Command {
|
|
var (
|
|
typ string
|
|
typeMap map[string]string
|
|
nullable bool
|
|
)
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "gen-cue [flags] SOURCE.go",
|
|
Args: cobra.ExactArgs(1),
|
|
Short: "Generate CUE schema from Go code.",
|
|
Long: "Generate CUE schema from Go code.\n" +
|
|
"* This command provide a way to generate CUE schema from Go code,\n" +
|
|
"* Which can be used to keep consistency between Go code and CUE schema automatically.\n",
|
|
Example: "# Generate CUE schema for provider type\n" +
|
|
"> vela def gen-cue -t provider /path/to/myprovider.go > /path/to/myprovider.cue\n" +
|
|
"# Generate CUE schema for provider type with custom types\n" +
|
|
"> 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",
|
|
Annotations: map[string]string{
|
|
types.TagCommandType: types.TypeDefGeneration,
|
|
types.TagCommandOrder: "3",
|
|
},
|
|
RunE: func(cmd *cobra.Command, args []string) (rerr error) {
|
|
// convert map[string]string to map[string]cuegen.Type
|
|
newTypeMap := make(map[string]cuegen.Type, len(typeMap))
|
|
for k, v := range typeMap {
|
|
newTypeMap[k] = cuegen.Type(v)
|
|
}
|
|
|
|
file := args[0]
|
|
if !strings.HasSuffix(file, ".go") {
|
|
return fmt.Errorf("invalid file %s, must be a go file", file)
|
|
}
|
|
|
|
switch typ {
|
|
case genTypeProvider:
|
|
return providergen.Generate(providergen.Options{
|
|
File: file,
|
|
Writer: streams.Out,
|
|
Types: newTypeMap,
|
|
Nullable: nullable,
|
|
})
|
|
default:
|
|
return fmt.Errorf("invalid type %s", typ)
|
|
}
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVarP(&typ, "type", "t", "", "Type of the definition to generate. Valid types: [provider]")
|
|
cmd.Flags().BoolVar(&nullable, "nullable", false, "Whether to generate null enum for pointer type")
|
|
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")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// NewDefinitionGenDocCommand create the `vela def gen-doc` command to generate documentation of definitions
|
|
func NewDefinitionGenDocCommand(_ common.Args, streams util.IOStreams) *cobra.Command {
|
|
var typ string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "gen-doc [flags] SOURCE.cue...",
|
|
Args: cobra.MinimumNArgs(1),
|
|
Short: "Generate documentation for non component, trait, policy and workflow definitions",
|
|
Long: "Generate documentation for non component, trait, policy and workflow definitions",
|
|
Example: "1. Generate documentation for provider definitions\n" +
|
|
"> vela def gen-doc -t provider provider1.cue provider2.cue > provider.md",
|
|
Annotations: map[string]string{
|
|
types.TagCommandType: types.TypeDefGeneration,
|
|
types.TagCommandOrder: "4",
|
|
},
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
readers := make([]io.Reader, 0, len(args))
|
|
|
|
for _, arg := range args {
|
|
if !strings.HasSuffix(arg, CUEExtension) {
|
|
return fmt.Errorf("invalid file %s, must be a cue file", arg)
|
|
}
|
|
|
|
f, err := os.ReadFile(filepath.Clean(arg))
|
|
if err != nil {
|
|
return fmt.Errorf("read file %s: %w", arg, err)
|
|
}
|
|
|
|
readers = append(readers, bytes.NewReader(f))
|
|
}
|
|
|
|
switch typ {
|
|
case genTypeProvider:
|
|
return docgen.GenerateProvidersMarkdown(cmd.Context(), readers, streams.Out)
|
|
default:
|
|
return fmt.Errorf("invalid type %s", typ)
|
|
}
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVarP(&typ, "type", "t", "", "Type of the definition to generate. Valid types: [provider]")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// NewDefinitionUpgradeCommand create the `vela def upgrade` command to help user upgrade CUE templates for version compatibility
|
|
func NewDefinitionUpgradeCommand(c common.Args, ioStreams util.IOStreams) *cobra.Command {
|
|
var (
|
|
outputFile string
|
|
targetVersion string
|
|
checkOnly bool
|
|
quiet bool
|
|
)
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "upgrade DEFINITION_FILE",
|
|
Short: "Upgrade CUE definition for version compatibility",
|
|
Long: "Upgrade CUE definition files to be compatible with a specific KubeVela version.\n" +
|
|
"This command automatically applies all necessary upgrades to ensure your definitions work with the target KubeVela version.\n" +
|
|
"If no version is specified, upgrades to the current CLI version.\n\n" +
|
|
"Currently supported upgrades:\n" +
|
|
"• List concatenation syntax compatibility\n" +
|
|
"• Import statement management\n" +
|
|
"• Template syntax modernization",
|
|
Example: "# Validate if definition needs upgrading (exit code 1 if upgrade needed)\n" +
|
|
"vela def upgrade my-definition.cue --validate\n\n" +
|
|
"# Silent validation for scripting (only exit code)\n" +
|
|
"vela def upgrade my-definition.cue --validate --quiet\n\n" +
|
|
"# Upgrade definition for current KubeVela version\n" +
|
|
"vela def upgrade my-definition.cue\n\n" +
|
|
"# Upgrade and save to specific file\n" +
|
|
"vela def upgrade my-definition.cue -o upgraded-definition.cue\n\n" +
|
|
"# Upgrade for specific KubeVela version\n" +
|
|
"vela def upgrade my-definition.cue --target-version=v1.11",
|
|
Annotations: map[string]string{
|
|
types.TagCommandType: types.TypeDefGeneration,
|
|
types.TagCommandOrder: "5",
|
|
},
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
sourceFile := args[0]
|
|
|
|
// Read the source file
|
|
content, err := os.ReadFile(sourceFile) //nolint:gosec
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read source file %s: %w", sourceFile, err)
|
|
}
|
|
|
|
// Prepare target version (strip 'v' prefix if present for consistency)
|
|
version := strings.TrimPrefix(targetVersion, "v")
|
|
|
|
// Check-only mode
|
|
if checkOnly {
|
|
var needsUpgrade bool
|
|
var reasons []string
|
|
|
|
if version != "" {
|
|
needsUpgrade, reasons, err = upgrade.RequiresUpgrade(string(content), version)
|
|
} else {
|
|
needsUpgrade, reasons, err = upgrade.RequiresUpgrade(string(content))
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check upgrade requirements: %w", err)
|
|
}
|
|
|
|
if needsUpgrade {
|
|
if !quiet {
|
|
fmt.Fprintf(ioStreams.Out, "✗ Definition %s requires upgrade:\n", sourceFile)
|
|
for _, reason := range reasons {
|
|
fmt.Fprintf(ioStreams.Out, " - %s\n", reason)
|
|
}
|
|
}
|
|
os.Exit(1) // Non-zero exit code for scripts
|
|
} else if !quiet {
|
|
fmt.Fprintf(ioStreams.Out, "✓ Definition %s is up to date\n", sourceFile)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Apply upgrades
|
|
var upgradedContent string
|
|
if version != "" {
|
|
upgradedContent, err = upgrade.Upgrade(string(content), version)
|
|
} else {
|
|
// Use default version (current KubeVela)
|
|
upgradedContent, err = upgrade.Upgrade(string(content))
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to upgrade CUE template: %w", err)
|
|
}
|
|
|
|
// Determine output destination
|
|
if outputFile != "" {
|
|
// Write to specified output file
|
|
if err := os.WriteFile(outputFile, []byte(upgradedContent), 0600); err != nil { //nolint:gosec
|
|
return fmt.Errorf("failed to write output file %s: %w", outputFile, err)
|
|
}
|
|
fmt.Fprintf(ioStreams.Out, "Successfully upgraded %s and saved to %s\n", sourceFile, outputFile)
|
|
} else {
|
|
// Write to stdout
|
|
fmt.Fprint(ioStreams.Out, upgradedContent)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output file path. If not specified, outputs to stdout.")
|
|
cmd.Flags().StringVar(&targetVersion, "target-version", "", "Target KubeVela version (e.g., --target-version=v1.11). If not specified, uses current CLI version.")
|
|
cmd.Flags().BoolVar(&checkOnly, "validate", false, "Validate if definition needs upgrading without making changes (exit code 1 if upgrade required)")
|
|
cmd.Flags().BoolVarP(&quiet, "quiet", "q", false, "Suppress output in validate mode, only return exit code")
|
|
|
|
return cmd
|
|
}
|