diff --git a/api/types/args.go b/api/types/args.go new file mode 100644 index 000000000..fdd74a4bb --- /dev/null +++ b/api/types/args.go @@ -0,0 +1,11 @@ +package types + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" +) + +type Args struct { + Config *rest.Config + Schema *runtime.Scheme +} diff --git a/api/types/template_types.go b/api/types/template_types.go index 83cf5cd67..83ae87f0a 100644 --- a/api/types/template_types.go +++ b/api/types/template_types.go @@ -34,6 +34,10 @@ type Template struct { Template string `json:"template,omitempty"` Parameters []Parameter `json:"parameters,omitempty"` DefinitionPath string `json:"definition"` + CrdName string `json:"crdName,omitempty"` + + //trait only + AppliesTo []string `json:"appliesTo,omitempty"` } type DefinitionType string @@ -72,13 +76,35 @@ func ConvertTemplateJson2Object(in *runtime.RawExtension) (Template, error) { func SetFlagBy(cmd *cobra.Command, v Parameter) { switch v.Type { case cue.IntKind: - cmd.Flags().Int64P(v.Name, v.Short, v.Default.(int64), v.Usage) + var vv int64 + switch val := v.Default.(type) { + case int64: + vv = val + case json.Number: + vv, _ = val.Int64() + case int: + vv = int64(val) + case float64: + vv = int64(val) + } + cmd.Flags().Int64P(v.Name, v.Short, vv, v.Usage) case cue.StringKind: cmd.Flags().StringP(v.Name, v.Short, v.Default.(string), v.Usage) case cue.BoolKind: cmd.Flags().BoolP(v.Name, v.Short, v.Default.(bool), v.Usage) case cue.NumberKind, cue.FloatKind: - cmd.Flags().Float64P(v.Name, v.Short, v.Default.(float64), v.Usage) + var vv float64 + switch val := v.Default.(type) { + case int64: + vv = float64(val) + case json.Number: + vv, _ = val.Float64() + case int: + vv = float64(val) + case float64: + vv = val + } + cmd.Flags().Float64P(v.Name, v.Short, vv, v.Usage) } if v.Required && v.Name != "name" { cmd.MarkFlagRequired(v.Name) diff --git a/cmd/vela/main.go b/cmd/vela/main.go index fc142c0ee..6ccd1afd3 100644 --- a/cmd/vela/main.go +++ b/cmd/vela/main.go @@ -1,15 +1,14 @@ package main import ( - "context" "fmt" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "math/rand" "os" "runtime" "time" + "github.com/cloud-native-application/rudrx/api/types" + "sigs.k8s.io/controller-runtime/pkg/client/config" "github.com/cloud-native-application/rudrx/pkg/utils/system" @@ -18,7 +17,6 @@ import ( "github.com/spf13/cobra" k8sruntime "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client" "github.com/cloud-native-application/rudrx/pkg/cmd" cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util" @@ -71,34 +69,17 @@ func newCommand() *cobra.Command { return nil, cobra.ShellCompDirectiveNoFileComp }, } - restConf, err := config.GetConfig() if err != nil { fmt.Println("get kubeconfig err", err) os.Exit(1) } - newClient, err := client.New(restConf, client.Options{Scheme: scheme}) - err = cmds.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - // Choose a long enough timeout that the user notices somethings is not working - // but short enough that the user is not made to wait very long - to := int64(3) - listOpt := &client.ListOptions{ - Raw: &metav1.ListOptions{TimeoutSeconds: &to}, - } - nsNames := []string{} - namespaces := v1.NamespaceList{} - if err = newClient.List(context.Background(), &namespaces, listOpt); err == nil { - for _, ns := range namespaces.Items { - nsNames = append(nsNames, ns.Name) - } - return nsNames, cobra.ShellCompDirectiveNoFileComp - } - return nil, cobra.ShellCompDirectiveDefault - }) - if err != nil { - fmt.Println("create client from kubeconfig err", err) - os.Exit(1) + + commandArgs := types.Args{ + Config: restConf, + Schema: scheme, } + if err := system.InitApplicationDir(); err != nil { fmt.Println("InitApplicationDir err", err) os.Exit(1) @@ -109,31 +90,37 @@ func newCommand() *cobra.Command { } cmds.AddCommand( - cmd.NewTraitsCommand(newClient, ioStream, []string{}), - cmd.NewWorkloadsCommand(newClient, ioStream, os.Args[1:]), - cmd.NewAdminInitCommand(newClient, ioStream), + cmd.NewAdminInitCommand(commandArgs, ioStream), cmd.NewAdminInfoCommand(VelaVersion, ioStream), - cmd.NewDeleteCommand(newClient, ioStream, os.Args[1:]), - cmd.NewAppsCommand(newClient, ioStream), - cmd.NewEnvInitCommand(newClient, ioStream), + + cmd.NewTraitsCommand(ioStream), + cmd.NewWorkloadsCommand(ioStream), + cmd.NewRefreshCommand(commandArgs, ioStream), + + cmd.NewDeleteCommand(commandArgs, ioStream, os.Args[1:]), + cmd.NewAppsCommand(commandArgs, ioStream), + cmd.NewAppStatusCommand(commandArgs, ioStream), + + cmd.NewEnvInitCommand(commandArgs, ioStream), cmd.NewEnvSwitchCommand(ioStream), cmd.NewEnvDeleteCommand(ioStream), cmd.NewEnvCommand(ioStream), - NewVersionCommand(), - cmd.NewAppStatusCommand(newClient, ioStream), + cmd.NewAddonConfigCommand(ioStream), - cmd.NewAddonListCommand(newClient, ioStream), + cmd.NewAddonListCommand(commandArgs, ioStream), + cmd.NewCompletionCommand(), + NewVersionCommand(), ) - if err = cmd.AddWorkloadPlugins(cmds, newClient, ioStream); err != nil { + if err = cmd.AddWorkloadPlugins(cmds, commandArgs, ioStream); err != nil { fmt.Println("Add plugins from workloadDefinition err", err) os.Exit(1) } - if err = cmd.AddTraitPlugins(cmds, newClient, ioStream); err != nil { + if err = cmd.AddTraitPlugins(cmds, commandArgs, ioStream); err != nil { fmt.Println("Add plugins from traitDefinition err", err) os.Exit(1) } - if err = cmd.DetachTraitPlugins(cmds, newClient, ioStream); err != nil { + if err = cmd.DetachTraitPlugins(cmds, commandArgs, ioStream); err != nil { fmt.Println("Add plugins from traitDefinition err", err) os.Exit(1) } diff --git a/pkg/cmd/addon.go b/pkg/cmd/addon.go index 545fb7b60..ec5744f2e 100644 --- a/pkg/cmd/addon.go +++ b/pkg/cmd/addon.go @@ -82,24 +82,27 @@ func NewAddonConfigCommand(ioStreams cmdutil.IOStreams) *cobra.Command { return cmd } -func NewAddonListCommand(c client.Client, ioStreams cmdutil.IOStreams) *cobra.Command { +func NewAddonListCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command { ctx := context.Background() cmd := &cobra.Command{ Use: "addon:ls", Short: "List addons", Long: "List addons of workloads and traits", Example: `vela addon:ls`, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { env, err := GetEnv() if err != nil { - ioStreams.Errorf("Failed to get Env information:%s", err) - os.Exit(1) + return err } - err = retrievePlugins(ctx, c, ioStreams, env.Namespace) + newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema}) if err != nil { - ioStreams.Errorf("Failed to list Addons:%s", err) - os.Exit(1) + return err } + err = retrievePlugins(ctx, newClient, ioStreams, env.Namespace) + if err != nil { + return err + } + return nil }, } return cmd diff --git a/pkg/cmd/delete.go b/pkg/cmd/delete.go index c853eace3..48e6647cc 100644 --- a/pkg/cmd/delete.go +++ b/pkg/cmd/delete.go @@ -37,14 +37,19 @@ func newDeleteCommand() *cobra.Command { } // NewDeleteCommand init new command -func NewDeleteCommand(c client.Client, ioStreams cmdutil.IOStreams, args []string) *cobra.Command { +func NewDeleteCommand(c types.Args, ioStreams cmdutil.IOStreams, args []string) *cobra.Command { cmd := newDeleteCommand() cmd.SetArgs(args) cmd.SetOut(ioStreams.Out) o := newDeleteOptions(ioStreams) - o.client = c + o.Env, _ = GetEnv() cmd.RunE = func(cmd *cobra.Command, args []string) error { + newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema}) + if err != nil { + return err + } + o.client = newClient if err := o.Complete(cmd, args); err != nil { return err } diff --git a/pkg/cmd/env.go b/pkg/cmd/env.go index e0cea1b40..647d850bb 100644 --- a/pkg/cmd/env.go +++ b/pkg/cmd/env.go @@ -36,7 +36,7 @@ func NewEnvCommand(ioStreams cmdutil.IOStreams) *cobra.Command { cmd.SetOut(ioStreams.Out) cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { cmdutil.PrintUsageIntroduce(cmd, "Prepare environments for applications") - subcmds := []*cobra.Command{cmd, NewEnvInitCommand(nil, ioStreams), NewEnvSwitchCommand(ioStreams), NewEnvDeleteCommand(ioStreams)} + subcmds := []*cobra.Command{cmd, NewEnvInitCommand(types.Args{}, ioStreams), NewEnvSwitchCommand(ioStreams), NewEnvDeleteCommand(ioStreams)} cmdutil.PrintUsage(cmd, subcmds) cmdutil.PrintExample(cmd, subcmds) cmdutil.PrintFlags(cmd, subcmds) @@ -44,7 +44,7 @@ func NewEnvCommand(ioStreams cmdutil.IOStreams) *cobra.Command { return cmd } -func NewEnvInitCommand(c client.Client, ioStreams cmdutil.IOStreams) *cobra.Command { +func NewEnvInitCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command { var envArgs types.EnvMeta ctx := context.Background() cmd := &cobra.Command{ @@ -54,7 +54,11 @@ func NewEnvInitCommand(c client.Client, ioStreams cmdutil.IOStreams) *cobra.Comm Long: "Create environment and switch to it", Example: `vela env:init test --namespace test`, RunE: func(cmd *cobra.Command, args []string) error { - return CreateOrUpdateEnv(ctx, c, &envArgs, args, ioStreams) + newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema}) + if err != nil { + return err + } + return CreateOrUpdateEnv(ctx, newClient, &envArgs, args, ioStreams) }, } cmd.SetOut(ioStreams.Out) diff --git a/pkg/cmd/init.go b/pkg/cmd/init.go index 7e73aa0a4..df7fdbcc6 100644 --- a/pkg/cmd/init.go +++ b/pkg/cmd/init.go @@ -18,7 +18,6 @@ import ( oamv1 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" "github.com/spf13/cobra" - "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/pkg/errors" @@ -51,7 +50,6 @@ type initCmd struct { namespace string out io.Writer client client.Client - config *rest.Config version string } @@ -92,7 +90,6 @@ func NewAdminInfoCommand(version string, ioStreams cmdutil.IOStreams) *cobra.Com return i.run(version, ioStreams) }, } - return cmd } @@ -109,7 +106,7 @@ func (i *infoCmd) run(version string, ioStreams cmdutil.IOStreams) error { return nil } -func NewAdminInitCommand(c client.Client, ioStreams cmdutil.IOStreams) *cobra.Command { +func NewAdminInitCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command { i := &initCmd{out: ioStreams.Out} @@ -118,7 +115,11 @@ func NewAdminInitCommand(c client.Client, ioStreams cmdutil.IOStreams) *cobra.Co Short: "Initialize Vela on both client and server", Long: initDesc, RunE: func(cmd *cobra.Command, args []string) error { - i.client = c + newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema}) + if err != nil { + return err + } + i.client = newClient i.namespace = types.DefaultOAMNS return i.run(ioStreams) }, diff --git a/pkg/cmd/ls.go b/pkg/cmd/ls.go index 318c6ed60..7b68bcc1a 100644 --- a/pkg/cmd/ls.go +++ b/pkg/cmd/ls.go @@ -3,16 +3,16 @@ package cmd import ( "context" "fmt" - "os" "strings" + "github.com/cloud-native-application/rudrx/api/types" cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util" "github.com/gosuri/uitable" "github.com/spf13/cobra" "sigs.k8s.io/controller-runtime/pkg/client" ) -func NewAppsCommand(c client.Client, ioStreams cmdutil.IOStreams) *cobra.Command { +func NewAppsCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command { ctx := context.Background() cmd := &cobra.Command{ Use: "app:ls", @@ -20,13 +20,17 @@ func NewAppsCommand(c client.Client, ioStreams cmdutil.IOStreams) *cobra.Command Short: "List applications", Long: "List applications with workloads, traits, status and created time", Example: `vela ls`, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { env, err := GetEnv() if err != nil { - ioStreams.Errorf("Failed to get Env information:%s", err) - os.Exit(1) + return err } - printApplicationList(ctx, c, "", env.Namespace) + newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema}) + if err != nil { + return err + } + printApplicationList(ctx, newClient, "", env.Namespace) + return nil }, } diff --git a/pkg/cmd/refresh.go b/pkg/cmd/refresh.go new file mode 100644 index 000000000..8cfcc6582 --- /dev/null +++ b/pkg/cmd/refresh.go @@ -0,0 +1,67 @@ +package cmd + +import ( + "context" + "os" + "path/filepath" + + "github.com/cloud-native-application/rudrx/api/types" + cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util" + "github.com/cloud-native-application/rudrx/pkg/plugins" + "github.com/cloud-native-application/rudrx/pkg/utils/system" + "github.com/spf13/cobra" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func NewRefreshCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command { + ctx := context.Background() + cmd := &cobra.Command{ + Use: "refresh", + DisableFlagsInUseLine: true, + Short: "Sync definition from cluster", + Long: "Refresh and sync definition files from cluster", + Example: `vela refresh`, + RunE: func(cmd *cobra.Command, args []string) error { + newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema}) + if err != nil { + return err + } + return RefreshDefinitions(ctx, newClient, ioStreams) + }, + } + cmd.SetOut(ioStreams.Out) + return cmd +} + +func StatOrCreate(dir string) { + if _, err := os.Stat(dir); os.IsNotExist(err) { + os.MkdirAll(dir, 0755) + } +} + +func RefreshDefinitions(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams) error { + dir, _ := system.GetDefinitionDir() + + ioStreams.Info("syncing workload definitions from cluster...") + templates, err := plugins.GetWorkloadsFromCluster(ctx, types.DefaultOAMNS, c, dir, nil) + if err != nil { + return err + } + workloadDir := filepath.Join(dir, "workloads") + StatOrCreate(workloadDir) + ioStreams.Infof("get %d workload definitions from cluster, syncing to %s...", len(templates), workloadDir) + successNum := plugins.SinkTemp2Local(templates, workloadDir) + ioStreams.Infof("%d workload definitions successfully synced\n", successNum) + + ioStreams.Info("syncing trait definitions from cluster...") + templates, err = plugins.GetTraitsFromCluster(ctx, types.DefaultOAMNS, c, dir, nil) + if err != nil { + return err + } + traitDir := filepath.Join(dir, "traits") + StatOrCreate(traitDir) + ioStreams.Infof("get %d trait definitions from cluster, syncing to %s...", len(templates), traitDir) + successNum = plugins.SinkTemp2Local(templates, traitDir) + ioStreams.Infof("%d trait definitions successfully synced\n", successNum) + return nil +} diff --git a/pkg/cmd/status.go b/pkg/cmd/status.go index 4857157d0..9b83727b6 100644 --- a/pkg/cmd/status.go +++ b/pkg/cmd/status.go @@ -4,6 +4,8 @@ import ( "context" "os" + "github.com/cloud-native-application/rudrx/api/types" + cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util" "github.com/ghodss/yaml" @@ -11,7 +13,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -func NewAppStatusCommand(c client.Client, ioStreams cmdutil.IOStreams) *cobra.Command { +func NewAppStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command { ctx := context.Background() cmd := &cobra.Command{ Use: "app:status", @@ -30,9 +32,12 @@ func NewAppStatusCommand(c client.Client, ioStreams cmdutil.IOStreams) *cobra.Co ioStreams.Errorf("Error: failed to get Env: %s", err) return err } - namespace := env.Namespace - return printApplicationStatus(ctx, c, ioStreams, appName, namespace) + newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema}) + if err != nil { + return err + } + return printApplicationStatus(ctx, newClient, ioStreams, appName, namespace) }, } cmd.SetOut(ioStreams.Out) diff --git a/pkg/cmd/trait_bind.go b/pkg/cmd/trait_bind.go index a00e0606a..1e7de9639 100644 --- a/pkg/cmd/trait_bind.go +++ b/pkg/cmd/trait_bind.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "path/filepath" "strconv" "strings" @@ -43,9 +44,9 @@ func NewCommandOptions(ioStreams cmdutil.IOStreams) *commandOptions { return &commandOptions{IOStreams: ioStreams} } -func AddTraitPlugins(parentCmd *cobra.Command, c client.Client, ioStreams cmdutil.IOStreams) error { +func AddTraitPlugins(parentCmd *cobra.Command, c types.Args, ioStreams cmdutil.IOStreams) error { dir, _ := system.GetDefinitionDir() - templates, err := plugins.GetTraitsFromCluster(context.TODO(), types.DefaultOAMNS, c, dir, nil) + templates, err := plugins.LoadTempFromLocal(filepath.Join(dir, "traits")) if err != nil { return err } @@ -53,7 +54,6 @@ func AddTraitPlugins(parentCmd *cobra.Command, c client.Client, ioStreams cmduti for _, tmp := range templates { var name = tmp.Name o := NewCommandOptions(ioStreams) - o.Client = c o.Env, _ = GetEnv() pluginCmd := &cobra.Command{ Use: name + " [args]", @@ -62,6 +62,11 @@ func AddTraitPlugins(parentCmd *cobra.Command, c client.Client, ioStreams cmduti Long: "Attach " + name + " trait to an app", Example: `vela scale frontend --max=5`, RunE: func(cmd *cobra.Command, args []string) error { + newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema}) + if err != nil { + return err + } + o.Client = newClient if err := o.Complete(cmd, args, ctx); err != nil { return err } @@ -163,9 +168,9 @@ func (o *commandOptions) Complete(cmd *cobra.Command, args []string, ctx context return nil } -func DetachTraitPlugins(parentCmd *cobra.Command, c client.Client, ioStreams cmdutil.IOStreams) error { +func DetachTraitPlugins(parentCmd *cobra.Command, c types.Args, ioStreams cmdutil.IOStreams) error { dir, _ := system.GetDefinitionDir() - templates, err := plugins.GetTraitsFromCluster(context.TODO(), types.DefaultOAMNS, c, dir, nil) + templates, err := plugins.LoadTempFromLocal(filepath.Join(dir, "traits")) if err != nil { return err } @@ -173,7 +178,6 @@ func DetachTraitPlugins(parentCmd *cobra.Command, c client.Client, ioStreams cmd for _, tmp := range templates { var name = tmp.Name o := NewCommandOptions(ioStreams) - o.Client = c o.Env, _ = GetEnv() pluginCmd := &cobra.Command{ Use: name + ":detach ", @@ -182,6 +186,11 @@ func DetachTraitPlugins(parentCmd *cobra.Command, c client.Client, ioStreams cmd Long: "Detach " + name + " trait from an app", Example: `vela scale:detach frontend`, RunE: func(cmd *cobra.Command, args []string) error { + newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema}) + if err != nil { + return err + } + o.Client = newClient if err := o.DetachTrait(cmd, args, ctx); err != nil { return err } diff --git a/pkg/cmd/trait_test.go b/pkg/cmd/trait_test.go deleted file mode 100644 index ce4e27603..000000000 --- a/pkg/cmd/trait_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package cmd - -import ( - "testing" - - "k8s.io/apimachinery/pkg/runtime" - - "github.com/cloud-native-application/rudrx/pkg/test" -) - -func TestNewTraitCommand(t *testing.T) { - TraitsNotApply := traitDefinitionExample.DeepCopy() - TraitsNotApply.Spec.AppliesToWorkloads = []string{} - - cases := map[string]*test.CliTestCase{ - "PrintTraits": { - Resources: test.InitResources{ - Create: []runtime.Object{ - traitDefinitionExample.DeepCopy(), - }, - }, - ExpectedString: "manualscalertrait.core.oam.dev", - Args: []string{}, - }, - "TraitsNotApply": { - Resources: test.InitResources{ - Create: []runtime.Object{ - TraitsNotApply, - }, - }, - ExpectedOutput: "NAME ALIAS DEFINITION APPLIES TO STATUS\n", - Args: []string{}, - }, - } - - test.NewCliTest(t, scheme, NewTraitsCommand, cases).Run() -} diff --git a/pkg/cmd/traits.go b/pkg/cmd/traits.go index dcf987596..c41046c0c 100644 --- a/pkg/cmd/traits.go +++ b/pkg/cmd/traits.go @@ -1,19 +1,20 @@ package cmd import ( - "context" - "fmt" + "path/filepath" "strings" + "github.com/cloud-native-application/rudrx/api/types" + + "github.com/cloud-native-application/rudrx/pkg/plugins" + "github.com/cloud-native-application/rudrx/pkg/utils/system" + cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util" - corev1alpha2 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" "github.com/gosuri/uitable" "github.com/spf13/cobra" - "sigs.k8s.io/controller-runtime/pkg/client" ) -func NewTraitsCommand(c client.Client, ioStreams cmdutil.IOStreams, args []string) *cobra.Command { - ctx := context.Background() +func NewTraitsCommand(ioStreams cmdutil.IOStreams) *cobra.Command { var workloadName string cmd := &cobra.Command{ Use: "traits [--apply-to WORKLOADNAME]", @@ -22,7 +23,16 @@ func NewTraitsCommand(c client.Client, ioStreams cmdutil.IOStreams, args []strin Long: "List traits", Example: `vela traits`, RunE: func(cmd *cobra.Command, args []string) error { - return printTraitList(ctx, c, &workloadName, ioStreams) + dir, _ := system.GetDefinitionDir() + templates, err := plugins.LoadTempFromLocal(filepath.Join(dir, "traits")) + if err != nil { + return err + } + workloads, err := plugins.LoadTempFromLocal(filepath.Join(dir, "workloads")) + if err != nil { + return err + } + return printTraitList(templates, workloads, &workloadName, ioStreams) }, } @@ -31,32 +41,29 @@ func NewTraitsCommand(c client.Client, ioStreams cmdutil.IOStreams, args []strin return cmd } -func printTraitList(ctx context.Context, c client.Client, workloadName *string, ioStreams cmdutil.IOStreams) error { - traitList, err := RetrieveTraitsByWorkload(ctx, c, "", *workloadName) - +func printTraitList(traits, workloads []types.Template, workloadName *string, ioStreams cmdutil.IOStreams) error { table := uitable.New() table.MaxColWidth = 60 - if err != nil { - return fmt.Errorf("Listing Trait DefinitionPath hit an issue: %s", err) - } - - table.AddRow("NAME", "ALIAS", "DEFINITION", "APPLIES TO", "STATUS") - for _, r := range traitList { - wdList := strings.Split(r.AppliesTo, ",") - if len(wdList) > 1 { - isFirst := true - for _, wd := range wdList { - wd = strings.Trim(wd, " ") - if isFirst { - table.AddRow(r.Name, r.Short, r.Definition, wd, r.Status) - isFirst = false + table.AddRow("NAME", "DEFINITION", "APPLIES TO") + for _, r := range traits { + convertedApplyTo := ConvertApplyTo(r.AppliesTo, workloads) + if *workloadName != "" { + if !In(convertedApplyTo, *workloadName) { + continue + } + convertedApplyTo = []string{*workloadName} + } + if len(convertedApplyTo) > 1 && *workloadName == "" { + for i, wd := range convertedApplyTo { + if i > 0 { + table.AddRow("", "", wd) } else { - table.AddRow("", "", "", wd, "") + table.AddRow(r.Name, r.CrdName, wd) } } } else { - table.AddRow(r.Name, r.Short, r.Definition, r.AppliesTo, r.Status) + table.AddRow(r.Name, r.CrdName, strings.Join(convertedApplyTo, "")) } } ioStreams.Info(table.String()) @@ -64,53 +71,32 @@ func printTraitList(ctx context.Context, c client.Client, workloadName *string, return nil } -type TraitMeta struct { - Name string `json:"name"` - Short string `json:"shot"` - Definition string `json:"definition,omitempty"` - AppliesTo string `json:"appliesTo,omitempty"` - Status string `json:"status,omitempty"` -} - -// RetrieveTraitsByWorkload Get trait list by optional filter `workloadName` -func RetrieveTraitsByWorkload(ctx context.Context, c client.Client, namespace string, workloadName string) ([]TraitMeta, error) { - var traitList []TraitMeta - var traitDefinitionList corev1alpha2.TraitDefinitionList - if namespace == "" { - namespace = "default" - } - err := c.List(ctx, &traitDefinitionList, client.InNamespace(namespace)) - - for _, r := range traitDefinitionList.Items { - var appliesTo string - if workloadName == "" { - appliesTo = strings.Join(r.Spec.AppliesToWorkloads, ", ") - if appliesTo == "" { - continue - } - } else { - flag := false - for _, w := range r.Spec.AppliesToWorkloads { - if workloadName == w { - flag = true - break - } - } - if !flag { - continue - } - appliesTo = workloadName +func ConvertApplyTo(applyTo []string, workloads []types.Template) []string { + var converted []string + for _, v := range applyTo { + newName, exist := check(v, workloads) + if !exist { + continue } - - // TODO(zzxwill) `Status` might not be proper as I'd like to describe where the trait is, in cluster or in registry - traitList = append(traitList, TraitMeta{ - Name: r.Name, - Short: r.ObjectMeta.Annotations["short"], - Definition: r.Spec.Reference.Name, - AppliesTo: appliesTo, - Status: "-", - }) + converted = append(converted, newName) } - - return traitList, err + return converted +} + +func check(crdname string, workloads []types.Template) (string, bool) { + for _, v := range workloads { + if crdname == v.CrdName { + return v.Name, true + } + } + return "", false +} + +func In(l []string, v string) bool { + for _, ll := range l { + if ll == v { + return true + } + } + return false } diff --git a/pkg/cmd/traits_test.go b/pkg/cmd/traits_test.go new file mode 100644 index 000000000..e6bd324c9 --- /dev/null +++ b/pkg/cmd/traits_test.go @@ -0,0 +1,88 @@ +package cmd + +import ( + "bytes" + "testing" + + "github.com/gosuri/uitable" + + "gotest.tools/assert" + + "github.com/cloud-native-application/rudrx/api/types" + cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util" +) + +func Test_printTraitList(t *testing.T) { + traits := []types.Template{ + { + Name: "route", + CrdName: "routes.oam.dev", + AppliesTo: []string{"deployments.apps", "clonsets.alibaba"}, + }, + { + Name: "scaler", + CrdName: "scaler.oam.dev", + AppliesTo: []string{"deployments.apps"}, + }, + } + workloads := []types.Template{ + { + Name: "deployment", + CrdName: "deployments.apps", + }, + { + Name: "clonset", + CrdName: "clonsets.alibaba", + }, + } + newTable := func() *uitable.Table { + table := uitable.New() + table.MaxColWidth = 60 + table.AddRow("NAME", "DEFINITION", "APPLIES TO") + return table + } + tb1 := newTable() + tb1.AddRow("route", "routes.oam.dev", "deployment") + tb1.AddRow("", "", "clonset") + tb1.AddRow("scaler", "scaler.oam.dev", "deployment") + + tb2 := newTable() + tb2.AddRow("route", "routes.oam.dev", "deployment") + tb2.AddRow("scaler", "scaler.oam.dev", "deployment") + + tb3 := newTable() + tb3.AddRow("route", "routes.oam.dev", "clonset") + + cases := map[string]struct { + traits []types.Template + workloads []types.Template + workloadName string + iostream cmdutil.IOStreams + ExpectedString string + }{ + "All Workloads": { + traits: traits, + workloads: workloads, + ExpectedString: tb1.String() + "\n", + }, + "Specify Workload Name deployment": { + traits: traits, + workloads: workloads, + workloadName: "deployment", + ExpectedString: tb2.String() + "\n", + }, + "Specify Workload Name clonset": { + traits: traits, + workloads: workloads, + workloadName: "clonset", + ExpectedString: tb3.String() + "\n", + }, + } + for cname, c := range cases { + b := bytes.Buffer{} + iostream := cmdutil.IOStreams{Out: &b} + nn := c.workloadName + printTraitList(c.traits, c.workloads, &nn, iostream) + assert.Equal(t, c.ExpectedString, b.String(), cname) + } +} diff --git a/pkg/cmd/workload_run.go b/pkg/cmd/workload_run.go index 36c9997b2..ed686189c 100644 --- a/pkg/cmd/workload_run.go +++ b/pkg/cmd/workload_run.go @@ -47,9 +47,9 @@ func newRunOptions(ioStreams cmdutil.IOStreams) *runOptions { return &runOptions{IOStreams: ioStreams} } -func AddWorkloadPlugins(parentCmd *cobra.Command, c client.Client, ioStreams cmdutil.IOStreams) error { +func AddWorkloadPlugins(parentCmd *cobra.Command, c types.Args, ioStreams cmdutil.IOStreams) error { dir, _ := system.GetDefinitionDir() - templates, err := plugins.GetWorkloadsFromCluster(context.TODO(), types.DefaultOAMNS, c, dir, nil) + templates, err := plugins.LoadTempFromLocal(filepath.Join(dir, "workloads")) if err != nil { return err } @@ -57,7 +57,6 @@ func AddWorkloadPlugins(parentCmd *cobra.Command, c client.Client, ioStreams cmd for _, tmp := range templates { var name = tmp.Name o := newRunOptions(ioStreams) - o.client = c o.Env, _ = GetEnv() pluginCmd := &cobra.Command{ Use: name + ":run [args]", @@ -66,6 +65,11 @@ func AddWorkloadPlugins(parentCmd *cobra.Command, c client.Client, ioStreams cmd Long: "Run " + name + " workloads", Example: `vela deployment:run frontend -i nginx:latest`, RunE: func(cmd *cobra.Command, args []string) error { + newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema}) + if err != nil { + return err + } + o.client = newClient if err := o.Complete(cmd, args, context.TODO()); err != nil { return err } diff --git a/pkg/cmd/workloads.go b/pkg/cmd/workloads.go index 81c8d0ca7..a64eb460f 100644 --- a/pkg/cmd/workloads.go +++ b/pkg/cmd/workloads.go @@ -1,18 +1,19 @@ package cmd import ( - "context" - "fmt" + "path/filepath" + + "github.com/cloud-native-application/rudrx/api/types" + + "github.com/cloud-native-application/rudrx/pkg/plugins" + "github.com/cloud-native-application/rudrx/pkg/utils/system" cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util" - corev1alpha2 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" "github.com/gosuri/uitable" "github.com/spf13/cobra" - "sigs.k8s.io/controller-runtime/pkg/client" ) -func NewWorkloadsCommand(c client.Client, ioStreams cmdutil.IOStreams, args []string) *cobra.Command { - ctx := context.Background() +func NewWorkloadsCommand(ioStreams cmdutil.IOStreams) *cobra.Command { cmd := &cobra.Command{ Use: "workloads", DisableFlagsInUseLine: true, @@ -20,52 +21,25 @@ func NewWorkloadsCommand(c client.Client, ioStreams cmdutil.IOStreams, args []st Long: "List workloads", Example: `vela workloads`, RunE: func(cmd *cobra.Command, args []string) error { - return printWorkloadList(ctx, c, ioStreams) + dir, _ := system.GetDefinitionDir() + workloads, err := plugins.LoadTempFromLocal(filepath.Join(dir, "workloads")) + if err != nil { + return err + } + return printWorkloadList(workloads, ioStreams) }, } - cmd.SetOut(ioStreams.Out) return cmd } -func printWorkloadList(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams) error { - workloadList, err := ListWorkloads(ctx, c) - +func printWorkloadList(workloadList []types.Template, ioStreams cmdutil.IOStreams) error { table := uitable.New() table.MaxColWidth = 60 - - if err != nil { - return fmt.Errorf("Listing Trait DefinitionPath hit an issue: %s", err) - } - - table.AddRow("NAME", "SHORT", "DEFINITION") + table.AddRow("NAME", "DEFINITION") for _, r := range workloadList { - table.AddRow(r.Name, r.Short, r.Definition) + table.AddRow(r.Name, r.CrdName) } ioStreams.Info(table.String()) - return nil } - -type WorkloadData struct { - Name string `json:"name"` - Short string `json:"shot"` - Definition string `json:"definition,omitempty"` -} - -func ListWorkloads(ctx context.Context, c client.Client) ([]WorkloadData, error) { - var workloadList []WorkloadData - var workloadDefinitionList corev1alpha2.WorkloadDefinitionList - err := c.List(ctx, &workloadDefinitionList) - - for _, r := range workloadDefinitionList.Items { - - workloadList = append(workloadList, WorkloadData{ - Name: r.Name, - Short: r.ObjectMeta.Annotations["short"], - Definition: r.Spec.Reference.Name, - }) - } - - return workloadList, err -} diff --git a/pkg/plugins/cluster.go b/pkg/plugins/cluster.go index f6b1739df..111d0a41e 100644 --- a/pkg/plugins/cluster.go +++ b/pkg/plugins/cluster.go @@ -48,6 +48,7 @@ func GetWorkloadsFromCluster(ctx context.Context, namespace string, c client.Cli continue } tmp.Type = types.TypeWorkload + tmp.CrdName = wd.Spec.Reference.Name templates = append(templates, tmp) } return templates, nil @@ -69,6 +70,8 @@ func GetTraitsFromCluster(ctx context.Context, namespace string, c client.Client continue } tmp.Type = types.TypeTrait + tmp.AppliesTo = td.Spec.AppliesToWorkloads + tmp.CrdName = td.Spec.Reference.Name templates = append(templates, tmp) } return templates, nil diff --git a/pkg/plugins/cluster_test.go b/pkg/plugins/cluster_test.go index a19021a97..f105ab5f7 100644 --- a/pkg/plugins/cluster_test.go +++ b/pkg/plugins/cluster_test.go @@ -30,10 +30,12 @@ var _ = Describe("DefinitionFiles", func() { Type: cue.StringKind, }, }, + CrdName: "routes.test", } deployment := types.Template{ - Name: "deployment", - Type: types.TypeWorkload, + Name: "deployment", + Type: types.TypeWorkload, + CrdName: "deployments.testapps", Parameters: []types.Parameter{ { Name: "name", diff --git a/pkg/plugins/local.go b/pkg/plugins/local.go index 5b184bfbd..f4a8810cb 100644 --- a/pkg/plugins/local.go +++ b/pkg/plugins/local.go @@ -1,9 +1,13 @@ package plugins import ( + "bytes" "encoding/json" + "fmt" "io/ioutil" + "os" "path/filepath" + "strings" "github.com/cloud-native-application/rudrx/api/types" ) @@ -23,39 +27,57 @@ func GetDefFromLocal(dir string, defType types.DefinitionType) ([]types.Template return defs, nil } -func SinkTemp2Local(templates []types.Template, dir string) error { +func SinkTemp2Local(templates []types.Template, dir string) int { + success := 0 for _, tmp := range templates { data, err := json.Marshal(tmp) if err != nil { - return err + fmt.Printf("sync %s err: %v\n", tmp.Name, err) + continue } err = ioutil.WriteFile(filepath.Join(dir, tmp.Name), data, 0644) if err != nil { - return err + fmt.Printf("sync %s err: %v\n", tmp.Name, err) + continue } + success++ } - return nil + return success } func LoadTempFromLocal(dir string) ([]types.Template, error) { var tmps []types.Template files, err := ioutil.ReadDir(dir) if err != nil { + if os.IsNotExist(err) { + fmt.Println("\"no definition files found, use 'vela refresh' to sync from cluster\"") + return nil, nil + } return nil, err } for _, f := range files { if f.IsDir() { continue } + if strings.HasSuffix(f.Name(), ".cue") { + continue + } data, err := ioutil.ReadFile(filepath.Join(dir, f.Name())) if err != nil { - return nil, err + fmt.Printf("read file %s err %v\n", f.Name(), err) + continue } var tmp types.Template - if err = json.Unmarshal(data, &tmp); err != nil { - return nil, err + decoder := json.NewDecoder(bytes.NewBuffer(data)) + decoder.UseNumber() + if err = decoder.Decode(&tmp); err != nil { + fmt.Printf("ignore invalid format file: %s\n", f.Name()) + continue } tmps = append(tmps, tmp) } + if len(tmps) == 0 { + fmt.Println("\"no definition files found, use 'vela refresh' to sync from cluster\"") + } return tmps, nil } diff --git a/pkg/plugins/local_test.go b/pkg/plugins/local_test.go index e04379237..f8effec2f 100644 --- a/pkg/plugins/local_test.go +++ b/pkg/plugins/local_test.go @@ -49,6 +49,7 @@ func TestLocalSink(t *testing.T) { tmps []types.Template Type types.DefinitionType expDef []types.Template + err error }{ "Test No Templates": { dir: "vela-test1", @@ -86,18 +87,22 @@ func TestLocalSink(t *testing.T) { }, } for name, c := range cases { - testInDir(t, name, c.dir, c.tmps, c.expDef, c.Type) + testInDir(t, name, c.dir, c.tmps, c.expDef, c.Type, c.err) } } -func testInDir(t *testing.T, casename, dir string, tmps, defexp []types.Template, Type types.DefinitionType) { +func testInDir(t *testing.T, casename, dir string, tmps, defexp []types.Template, Type types.DefinitionType, err1 error) { err := os.MkdirAll(dir, 0755) assert.NoError(t, err, casename) defer os.RemoveAll(dir) - err = SinkTemp2Local(tmps, dir) - assert.NoError(t, err, casename) + number := SinkTemp2Local(tmps, dir) + assert.Equal(t, len(tmps), number) gottmps, err := LoadTempFromLocal(dir) - assert.NoError(t, err, casename) + if err1 != nil { + assert.Equal(t, err1, err) + } else { + assert.NoError(t, err, casename) + } assert.Equal(t, tmps, gottmps, casename) if Type != "" { gotDef, err := GetDefFromLocal(dir, Type)