diff --git a/internal/cli/cluster/enter.go b/internal/cli/cluster/enter.go index 8e5ec3c02..624112849 100644 --- a/internal/cli/cluster/enter.go +++ b/internal/cli/cluster/enter.go @@ -77,7 +77,6 @@ const ( DEFAULT_MONITOR_CPU = 1 DEFAULT_MONITOR_MEMORY = 1 DEFAULT_NAME = "test" - DEFAULT_NAME = "test" // Default values for Parameter flag DEFAULT_MIN_FULL_RESOURCE_POOL_MEMORY = "2147483648" @@ -86,6 +85,7 @@ const ( // Default cluster type for easier cluster creation const ( - SINGLE_NODE = "single-node" - THREE_NODE = "three-node" + CLUSTER_TYPE = "cluster-type" + SINGLE_NODE = "single-node" + THREE_NODE = "three-node" ) diff --git a/internal/cli/cmd/backup/show.go b/internal/cli/cmd/backup/show.go index 05d0f9d52..b8ad54c29 100644 --- a/internal/cli/cmd/backup/show.go +++ b/internal/cli/cmd/backup/show.go @@ -44,7 +44,7 @@ func NewShowCmd() *cobra.Command { if err != nil { logger.Fatalln(err, "failed to get backup policy") } - if backupPolicy == nil { + if backupPolicy == nil || backupPolicy.Name == "" { logger.Fatalln("no backup policy found") } backupJobList, err := backup.ListBackupJobs(cmd.Context(), backupPolicy.Name, o) diff --git a/internal/cli/cmd/demo/demo.go b/internal/cli/cmd/demo/demo.go index 39f2b270e..08efeffc9 100644 --- a/internal/cli/cmd/demo/demo.go +++ b/internal/cli/cmd/demo/demo.go @@ -13,16 +13,66 @@ See the Mulan PSL v2 for more details. */ package demo -import "github.com/spf13/cobra" +import ( + "github.com/spf13/cobra" + + "github.com/oceanbase/ob-operator/internal/cli/cluster" + cmdUtil "github.com/oceanbase/ob-operator/internal/cli/cmd/util" + "github.com/oceanbase/ob-operator/internal/clients" +) // NewCmd create demo command for cluster creation func NewCmd() *cobra.Command { + o := cluster.NewCreateOptions() + logger := cmdUtil.GetDefaultLoggerInstance() + pf := NewPromptFactory() cmd := &cobra.Command{ Use: "demo ", Short: "deploy demo ob cluster in easier way", Long: `deploy demo ob cluster in easier way, currently support single node and three node cluster`, + Run: func(cmd *cobra.Command, args []string) { + var err error + var clusterType string + prompt := pf.CreatePrompt(cluster.FLAG_NAME) + if o.Name, err = pf.RunPromptE(prompt); err != nil { + logger.Fatalln(err) + } + prompt = pf.CreatePrompt(cluster.FLAG_NAMESPACE) + if o.Namespace, err = pf.RunPromptE(prompt); err != nil { + logger.Fatalln(err) + } + prompt = pf.CreatePrompt(cluster.CLUSTER_TYPE) + if clusterType, err = pf.RunPromptE(prompt); err != nil { + logger.Fatalln(err) + } + prompt = pf.CreatePrompt(cluster.FLAG_ROOT_PASSWORD) + if o.RootPassword, err = pf.RunPromptE(prompt); err != nil { + logger.Fatalln(err) + } + prompt = pf.CreatePrompt(cluster.FLAG_BACKUP_ADDRESS) + if o.BackupVolume.Address, err = pf.RunPromptE(prompt); err != nil { + logger.Fatalln(err) + } + prompt = pf.CreatePrompt(cluster.FLAG_BACKUP_PATH) + if o.BackupVolume.Path, err = pf.RunPromptE(prompt); err != nil { + logger.Fatalln(err) + } + if err := o.Complete(); err != nil { + logger.Fatalln(err) + } + if err := o.SetDefaultConfig(clusterType); err != nil { + logger.Fatalln(err) + } + obcluster := cluster.CreateOBClusterInstance(o) + if err := clients.CreateSecretsForOBCluster(cmd.Context(), obcluster, o.RootPassword); err != nil { + logger.Fatalf("failed to create secrets for ob cluster: %v", err) + } + if _, err := clients.CreateOBCluster(cmd.Context(), obcluster); err != nil { + logger.Fatalln(err) + } + logger.Printf("Create OBCluster instance: %s", o.ClusterName) + logger.Printf("Run `echo $(kubectl get secret %s -o jsonpath='{.data.password}'|base64 --decode)` to get the secrets", obcluster.Spec.UserSecrets.Root) + }, } - cmd.AddCommand(NewSingleNodeCmd()) - cmd.AddCommand(NewThreeNodeCmd()) return cmd } diff --git a/internal/cli/cmd/demo/prompt.go b/internal/cli/cmd/demo/prompt.go index 8972abc38..7449673b6 100644 --- a/internal/cli/cmd/demo/prompt.go +++ b/internal/cli/cmd/demo/prompt.go @@ -18,97 +18,97 @@ import ( "fmt" "github.com/manifoldco/promptui" + "github.com/oceanbase/ob-operator/internal/cli/cluster" "github.com/oceanbase/ob-operator/internal/cli/utils" ) -var tepl *promptui.PromptTemplates = &promptui.PromptTemplates{ - Prompt: "{{ . }} ", - Valid: "{{ . | green }} ", - Invalid: "{{ . | red }} ", - Success: "{{ . | bold }} ", -} +var ( + promptTepl = &promptui.PromptTemplates{ + Prompt: "{{ . }} ", + Valid: "{{ . | green }} ", + Invalid: "{{ . | red }} ", + Success: "{{ . | bold }} ", + } + selectTepl = &promptui.SelectTemplates{ + Label: "{{ . }} ", + Active: "\U0001F336 {{ . | cyan }}", + Inactive: " {{ . | cyan }}", + Selected: "\U0001F336 {{ . | green | cyan }}", + } +) type PromptFactory struct { - template *promptui.PromptTemplates + promptTepl *promptui.PromptTemplates + selectTepl *promptui.SelectTemplates } +// NewPromptFactory creates a new prompt factory func NewPromptFactory() *PromptFactory { return &PromptFactory{ - template: tepl, + promptTepl: promptTepl, + selectTepl: selectTepl, } } -func RunPromptsForCluster(pf *PromptFactory, o *cluster.CreateOptions) (err error) { - prompt := pf.CreatePrompt(cluster.FLAG_NAME) - if o.Name, err = pf.RunPromptE(prompt); err != nil { - return err - } - prompt = pf.CreatePrompt(cluster.FLAG_NAMESPACE) - if o.Namespace, err = pf.RunPromptE(prompt); err != nil { - return err - } - prompt = pf.CreatePrompt(cluster.FLAG_ROOT_PASSWORD) - if o.RootPassword, err = pf.RunPromptE(prompt); err != nil { - return err - } - prompt = pf.CreatePrompt(cluster.FLAG_BACKUP_ADDRESS) - if o.BackupVolume.Address, err = pf.RunPromptE(prompt); err != nil { - return err - } - prompt = pf.CreatePrompt(cluster.FLAG_BACKUP_PATH) - if o.BackupVolume.Path, err = pf.RunPromptE(prompt); err != nil { - return err - } - if err := o.Complete(); err != nil { - return err - } - if err := o.SetDefaultConfig(cluster.SINGLE_NODE); err != nil { - return err - } - return nil -} - -func (pf *PromptFactory) RunPromptE(p *promptui.Prompt) (result string, err error) { - if result, err = p.Run(); err != nil { - if err == promptui.ErrInterrupt { - return "", errors.New("interrupted by user") +// RunPromptE runs the prompt and returns the result or error +func (pf *PromptFactory) RunPromptE(p any) (result string, err error) { + switch v := p.(type) { + case *promptui.Prompt: + if result, err = v.Run(); err != nil { + if err == promptui.ErrInterrupt { + return "", errors.New("interrupted by user") + } + return "", fmt.Errorf("failed to create cluster: %v", err) + } + return result, nil + case *promptui.Select: + if _, result, err = v.Run(); err != nil { + if err == promptui.ErrInterrupt { + return "", errors.New("interrupted by user") + } + return "", fmt.Errorf("failed to create cluster: %v", err) } - return "", fmt.Errorf("failed to create cluster: %v", err) + return result, nil + default: + return "", errors.New("invalid prompt type") } - return result, nil } -func (pf *PromptFactory) CreatePrompt(promptType string) *promptui.Prompt { +// CreatePrompt creates a prompt by prompt factory, based on the prompt type +func (pf *PromptFactory) CreatePrompt(promptType string) any { switch promptType { case cluster.FLAG_NAME: return &promptui.Prompt{ - Label: "Please input the cluster name (Default `test`): ", - Templates: pf.template, + Label: "Please input the cluster name, press `enter` to use the default name `test`: ", + Templates: pf.promptTepl, Validate: func(input string) error { if !utils.CheckResourceName(input) { return errors.New("invalid cluster name") } return nil }, - Default: cluster.DEFAULT_NAME, + AllowEdit: true, + Default: cluster.DEFAULT_NAME, } case cluster.FLAG_NAMESPACE: return &promptui.Prompt{ - Label: "Please input the namespace (Default `default`): ", - Templates: pf.template, + Label: "Please input the namespace, press `enter` to use default namespace: ", + Templates: pf.promptTepl, Validate: func(input string) error { if input == "" { return errors.New("namespace can not be empty") } return nil }, - Default: cluster.DEFAULT_NAMESPACE, + AllowEdit: true, + Default: cluster.DEFAULT_NAMESPACE, } case cluster.FLAG_ROOT_PASSWORD: return &promptui.Prompt{ - Label: "Please input the root password (if not used, generate random password): ", - Templates: pf.template, + Label: "Please input the root password, press `enter` to generate a random password: ", + Templates: pf.promptTepl, + Mask: '*', // mask the input Validate: func(input string) error { if input == "" { return nil @@ -118,19 +118,28 @@ func (pf *PromptFactory) CreatePrompt(promptType string) *promptui.Prompt { } return nil }, - Default: utils.GenerateRandomPassword(8, 32), + AllowEdit: true, } case cluster.FLAG_BACKUP_ADDRESS: return &promptui.Prompt{ - Label: "Please input the backup address (if not set, it will be empty): ", - Templates: pf.template, + Label: "Please input the backup address, if press `enter`, cluster will not support backup: ", + Templates: pf.promptTepl, Default: "", + AllowEdit: true, } case cluster.FLAG_BACKUP_PATH: return &promptui.Prompt{ - Label: "Please input the backup path (if not set, it will be empty): ", - Templates: pf.template, + Label: "Please input the backup path, if press `enter`, cluster will not support backup: ", + Templates: pf.promptTepl, Default: "", + AllowEdit: true, + } + case cluster.CLUSTER_TYPE: + return &promptui.Select{ + Label: "Please select the cluster type: ", + Items: []string{cluster.SINGLE_NODE, cluster.THREE_NODE}, + Templates: pf.selectTepl, + HideSelected: true, } default: return nil diff --git a/internal/cli/cmd/demo/single-node.go b/internal/cli/cmd/demo/single-node.go deleted file mode 100644 index d3b7a9215..000000000 --- a/internal/cli/cmd/demo/single-node.go +++ /dev/null @@ -1,54 +0,0 @@ -/* -Copyright (c) 2024 OceanBase -ob-operator is licensed under Mulan PSL v2. -You can use this software according to the terms and conditions of the Mulan PSL v2. -You may obtain a copy of Mulan PSL v2 at: - - http://license.coscl.org.cn/MulanPSL2 - -THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, -EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, -MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -See the Mulan PSL v2 for more details. -*/ -package demo - -import ( - "github.com/oceanbase/ob-operator/internal/cli/cluster" - cmdUtil "github.com/oceanbase/ob-operator/internal/cli/cmd/util" - "github.com/oceanbase/ob-operator/internal/clients" - "github.com/spf13/cobra" -) - -// NewSingleNodeCmd create a single node cluster in a easier way -func NewSingleNodeCmd() *cobra.Command { - o := cluster.NewCreateOptions() - logger := cmdUtil.GetDefaultLoggerInstance() - pf := NewPromptFactory() - cmd := &cobra.Command{ - Use: cluster.SINGLE_NODE, - Short: "deploy a single node ob cluster", - Long: `deploy a single node ob cluster`, - Run: func(cmd *cobra.Command, args []string) { - if err := RunPromptsForCluster(pf, o); err != nil { - logger.Fatalln(err) - } - if err := o.Complete(); err != nil { - logger.Fatalln(err) - } - if err := o.SetDefaultConfig(cluster.SINGLE_NODE); err != nil { - logger.Fatalln(err) - } - obcluster := cluster.CreateOBClusterInstance(o) - if err := clients.CreateSecretsForOBCluster(cmd.Context(), obcluster, o.RootPassword); err != nil { - logger.Fatalf("failed to create secrets for ob cluster: %v", err) - } - if _, err := clients.CreateOBCluster(cmd.Context(), obcluster); err != nil { - logger.Fatalln(err) - } - logger.Printf("Create single-node OBCluster instance: %s", o.ClusterName) - logger.Printf("Run `echo $(kubectl get secret %s -o jsonpath='{.data.password}'|base64 --decode)` to get the secrets", obcluster.Spec.UserSecrets.Root) - }, - } - return cmd -} diff --git a/internal/cli/cmd/demo/three-node.go b/internal/cli/cmd/demo/three-node.go deleted file mode 100644 index 5b3d0ce68..000000000 --- a/internal/cli/cmd/demo/three-node.go +++ /dev/null @@ -1,54 +0,0 @@ -/* -Copyright (c) 2024 OceanBase -ob-operator is licensed under Mulan PSL v2. -You can use this software according to the terms and conditions of the Mulan PSL v2. -You may obtain a copy of Mulan PSL v2 at: - - http://license.coscl.org.cn/MulanPSL2 - -THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, -EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, -MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -See the Mulan PSL v2 for more details. -*/ -package demo - -import ( - "github.com/oceanbase/ob-operator/internal/cli/cluster" - cmdUtil "github.com/oceanbase/ob-operator/internal/cli/cmd/util" - "github.com/oceanbase/ob-operator/internal/clients" - "github.com/spf13/cobra" -) - -// NewThreeNodeCmd create a 1-1-1 three node cluster in a easier way -func NewThreeNodeCmd() *cobra.Command { - o := cluster.NewCreateOptions() - logger := cmdUtil.GetDefaultLoggerInstance() - pf := NewPromptFactory() - cmd := &cobra.Command{ - Use: cluster.THREE_NODE, - Short: "deploy a three node ob cluster", - Long: "deploy a 1-1-1 three node ob cluster", - Run: func(cmd *cobra.Command, args []string) { - if err := RunPromptsForCluster(pf, o); err != nil { - logger.Fatalln(err) - } - if err := o.Complete(); err != nil { - logger.Fatalln(err) - } - if err := o.SetDefaultConfig(cluster.THREE_NODE); err != nil { - logger.Fatalln(err) - } - obcluster := cluster.CreateOBClusterInstance(o) - if err := clients.CreateSecretsForOBCluster(cmd.Context(), obcluster, o.RootPassword); err != nil { - logger.Fatalf("failed to create secrets for ob cluster: %v", err) - } - if _, err := clients.CreateOBCluster(cmd.Context(), obcluster); err != nil { - logger.Fatalln(err) - } - logger.Printf("Create three-node OBCluster instance: %s", o.ClusterName) - logger.Printf("Run `echo $(kubectl get secret %s -o jsonpath='{.data.password}'|base64 --decode)` to get the secrets", obcluster.Spec.UserSecrets.Root) - }, - } - return cmd -} diff --git a/internal/cli/generated/bindata/bindata.go b/internal/cli/generated/bindata/bindata.go index 82f6b5dbe..b13a76b40 100644 --- a/internal/cli/generated/bindata/bindata.go +++ b/internal/cli/generated/bindata/bindata.go @@ -92,7 +92,7 @@ func internalAssetsCliTemplatesComponent_configYaml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "internal/assets/cli-templates/component_config.yaml", size: 219, mode: os.FileMode(420), modTime: time.Unix(1727331319, 0)} + info := bindataFileInfo{name: "internal/assets/cli-templates/component_config.yaml", size: 219, mode: os.FileMode(420), modTime: time.Unix(1728986385, 0)} a := &asset{bytes: bytes, info: info} return a, nil }