From ac5fbbb07fa7d7bd69316bb508c3aa217bb1e5a6 Mon Sep 17 00:00:00 2001 From: healthjyk Date: Tue, 19 Mar 2024 14:30:39 +0800 Subject: [PATCH] feat: add workspace switch cmd --- pkg/cmd/workspace/cmd.go | 4 +- pkg/cmd/workspace/switch/cmd.go | 45 ++++++++++ pkg/cmd/workspace/switch/cmd_test.go | 24 ++++++ pkg/cmd/workspace/switch/options.go | 46 ++++++++++ pkg/cmd/workspace/switch/options_test.go | 104 +++++++++++++++++++++++ 5 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 pkg/cmd/workspace/switch/cmd.go create mode 100644 pkg/cmd/workspace/switch/cmd_test.go create mode 100644 pkg/cmd/workspace/switch/options.go create mode 100644 pkg/cmd/workspace/switch/options_test.go diff --git a/pkg/cmd/workspace/cmd.go b/pkg/cmd/workspace/cmd.go index b4ad8348..cfbfb6ce 100644 --- a/pkg/cmd/workspace/cmd.go +++ b/pkg/cmd/workspace/cmd.go @@ -8,6 +8,7 @@ import ( "kusionstack.io/kusion/pkg/cmd/workspace/del" "kusionstack.io/kusion/pkg/cmd/workspace/list" "kusionstack.io/kusion/pkg/cmd/workspace/show" + cmdswitch "kusionstack.io/kusion/pkg/cmd/workspace/switch" "kusionstack.io/kusion/pkg/cmd/workspace/update" "kusionstack.io/kusion/pkg/util/i18n" ) @@ -37,7 +38,8 @@ func NewCmd() *cobra.Command { showCmd := show.NewCmd() listCmd := list.NewCmd() delCmd := del.NewCmd() - cmd.AddCommand(createCmd, updateCmd, showCmd, listCmd, delCmd) + switchCmd := cmdswitch.NewCmd() + cmd.AddCommand(createCmd, updateCmd, showCmd, listCmd, delCmd, switchCmd) return cmd } diff --git a/pkg/cmd/workspace/switch/cmd.go b/pkg/cmd/workspace/switch/cmd.go new file mode 100644 index 00000000..f189752e --- /dev/null +++ b/pkg/cmd/workspace/switch/cmd.go @@ -0,0 +1,45 @@ +package cmdswitch + +import ( + "github.com/spf13/cobra" + "k8s.io/kubectl/pkg/util/templates" + + "kusionstack.io/kusion/pkg/cmd/util" + "kusionstack.io/kusion/pkg/util/i18n" +) + +func NewCmd() *cobra.Command { + var ( + short = i18n.T(`Switch the current workspace`) + + long = i18n.T(` + This command switches the workspace, where the workspace must be created.`) + + example = i18n.T(` + # Switch the current workspace + kusion workspace switch dev + + # Switch the current workspace in a specified backend + kusion workspace switch prod --backend oss-prod +`) + ) + + o := NewOptions() + cmd := &cobra.Command{ + Use: "switch", + Short: short, + Long: templates.LongDesc(long), + Example: templates.Examples(example), + DisableFlagsInUseLine: true, + RunE: func(cmd *cobra.Command, args []string) (err error) { + defer util.RecoverErr(&err) + util.CheckErr(o.Complete(args)) + util.CheckErr(o.Validate()) + util.CheckErr(o.Run()) + return + }, + } + + cmd.Flags().StringVarP(&o.Backend, "backend", "", "", i18n.T("the backend name")) + return cmd +} diff --git a/pkg/cmd/workspace/switch/cmd_test.go b/pkg/cmd/workspace/switch/cmd_test.go new file mode 100644 index 00000000..12f6dbcb --- /dev/null +++ b/pkg/cmd/workspace/switch/cmd_test.go @@ -0,0 +1,24 @@ +package cmdswitch + +import ( + "testing" + + "github.com/bytedance/mockey" + "github.com/stretchr/testify/assert" +) + +func TestNewCmd(t *testing.T) { + t.Run("successfully switch workspace", func(t *testing.T) { + mockey.PatchConvey("mock cmd", t, func() { + mockey.Mock((*Options).Complete).To(func(o *Options, args []string) error { + o.Name = "dev" + return nil + }).Build() + mockey.Mock((*Options).Run).Return(nil).Build() + + cmd := NewCmd() + err := cmd.Execute() + assert.Nil(t, err) + }) + }) +} diff --git a/pkg/cmd/workspace/switch/options.go b/pkg/cmd/workspace/switch/options.go new file mode 100644 index 00000000..91d0a5fd --- /dev/null +++ b/pkg/cmd/workspace/switch/options.go @@ -0,0 +1,46 @@ +package cmdswitch + +import ( + "fmt" + + "kusionstack.io/kusion/pkg/backend" + "kusionstack.io/kusion/pkg/cmd/workspace/util" +) + +type Options struct { + Name string + Backend string +} + +func NewOptions() *Options { + return &Options{} +} + +func (o *Options) Complete(args []string) error { + name, err := util.GetNameFromArgs(args) + if err != nil { + return err + } + o.Name = name + return nil +} + +func (o *Options) Validate() error { + if err := util.ValidateName(o.Name); err != nil { + return err + } + return nil +} + +func (o *Options) Run() error { + storage, err := backend.NewWorkspaceStorage(o.Backend) + if err != nil { + return err + } + + if err = storage.SetCurrent(o.Name); err != nil { + return err + } + fmt.Printf("switch current workspace to %s successfully\n", o.Name) + return nil +} diff --git a/pkg/cmd/workspace/switch/options_test.go b/pkg/cmd/workspace/switch/options_test.go new file mode 100644 index 00000000..a4a56757 --- /dev/null +++ b/pkg/cmd/workspace/switch/options_test.go @@ -0,0 +1,104 @@ +package cmdswitch + +import ( + "reflect" + "testing" + + "github.com/bytedance/mockey" + "github.com/stretchr/testify/assert" + + v1 "kusionstack.io/kusion/pkg/apis/core/v1" + "kusionstack.io/kusion/pkg/backend" + "kusionstack.io/kusion/pkg/cmd/workspace/util" + workspacestorages "kusionstack.io/kusion/pkg/workspace/storages" +) + +func TestOptions_Complete(t *testing.T) { + testcases := []struct { + name string + args []string + success bool + expectedOpts *Options + }{ + { + name: "successfully complete options", + args: []string{"dev"}, + success: true, + expectedOpts: &Options{Name: "dev"}, + }, + { + name: "complete field invalid args", + args: []string{"dev", "prod"}, + success: false, + expectedOpts: nil, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + opts := NewOptions() + err := opts.Complete(tc.args) + assert.Equal(t, tc.success, err == nil) + if tc.success { + assert.True(t, reflect.DeepEqual(opts, tc.expectedOpts)) + } + }) + } +} + +func TestOptions_Validate(t *testing.T) { + testcases := []struct { + name string + opts *Options + success bool + }{ + { + name: "valid options", + opts: &Options{ + Name: "dev", + }, + success: true, + }, + { + name: "invalid options empty name", + opts: &Options{}, + success: false, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + err := tc.opts.Validate() + assert.Equal(t, tc.success, err == nil) + }) + } +} + +func TestOptions_Run(t *testing.T) { + testcases := []struct { + name string + opts *Options + success bool + }{ + { + name: "successfully run", + opts: &Options{ + Name: "dev", + }, + success: true, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + mockey.PatchConvey("mock switch workspace", t, func() { + mockey.Mock(backend.NewWorkspaceStorage).Return(&workspacestorages.LocalStorage{}, nil).Build() + mockey.Mock((*workspacestorages.LocalStorage).SetCurrent).Return(nil).Build() + mockey.Mock(util.GetValidWorkspaceFromFile).Return(&v1.Workspace{Name: "dev"}, nil).Build() + + err := tc.opts.Run() + assert.Equal(t, tc.success, err == nil) + }) + }) + } +}