Skip to content

Commit

Permalink
feat(cli): add completion cmd and remove unused flag (#647)
Browse files Browse the repository at this point in the history
  • Loading branch information
lizzy-0323 authored Nov 27, 2024
1 parent 63e837c commit 06ff15b
Show file tree
Hide file tree
Showing 18 changed files with 309 additions and 19 deletions.
6 changes: 5 additions & 1 deletion internal/cli/backup/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,16 @@ func (o *CreateOptions) AddFlags(cmd *cobra.Command) {
o.AddDaysFieldFlags(cmd)
o.AddScheduleFlags(cmd)
o.AddAccessFlags(cmd)
o.SetRequiredFlags(cmd)
}

func (o *CreateOptions) SetRequiredFlags(cmd *cobra.Command) {
_ = cmd.MarkFlagRequired(FLAG_FULL)
}

// AddBaseFlags adds the base flags for the create command
func (o *CreateOptions) AddBaseFlags(cmd *cobra.Command) {
baseFlags := cmd.Flags()
baseFlags.StringVar(&o.Name, FLAG_NAME, "", "The name of the ob tenant")
baseFlags.StringVar(&o.Namespace, FLAG_NAMESPACE, DEFAULT_NAMESPACE, "The namespace of the ob tenant")
baseFlags.StringVar(&o.DestType, FLAG_DEST_TYPE, DEFAULT_DEST_TYPE, "The destination type of the backup policy, currently support OSS or NFS")
baseFlags.StringVar(&o.ArchivePath, FLAG_ARCHIVE_PATH, "", "The archive path of the backup policy")
Expand Down
1 change: 0 additions & 1 deletion internal/cli/backup/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ func DeleteTenantBackupPolicy(ctx context.Context, o *DeleteOptions) error {

// AddFlags add basic flags for tenant management
func (o *DeleteOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Name, FLAG_NAME, "", "The name of the ob tenant")
cmd.Flags().StringVar(&o.Namespace, FLAG_NAMESPACE, DEFAULT_NAMESPACE, "The namespace of the ob tenant")
cmd.Flags().BoolVarP(&o.force, FLAG_FORCE, "f", DEFAULT_FORCE, "Force delete the ob tenant backup policy")
}
1 change: 0 additions & 1 deletion internal/cli/backup/enter.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const (

// Base flags
FLAG_NAMESPACE = "namespace"
FLAG_NAME = "name"
FLAG_SCHEDULE_TYPE = "schedule-type"
FLAG_DEST_TYPE = "dest-type"
FLAG_ARCHIVE_PATH = "archive-path"
Expand Down
1 change: 0 additions & 1 deletion internal/cli/backup/pause.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,5 @@ func NewPauseOptions() *PauseOptions {
}

func (o *PauseOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Name, FLAG_NAME, "", "The name of the tenant")
cmd.Flags().StringVar(&o.Namespace, FLAG_NAMESPACE, DEFAULT_NAMESPACE, "The namespace of the tenant")
}
1 change: 0 additions & 1 deletion internal/cli/backup/resume.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,5 @@ func NewResumeOptions() *ResumeOptions {
}

func (o *ResumeOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Name, FLAG_NAME, "", "The name of the tenant")
cmd.Flags().StringVar(&o.Namespace, FLAG_NAMESPACE, DEFAULT_NAMESPACE, "The namespace of the tenant")
}
1 change: 0 additions & 1 deletion internal/cli/backup/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ func (o *UpdateOptions) AddFlags(cmd *cobra.Command) {
// AddBaseFlags adds the base flags for the create command
func (o *UpdateOptions) AddBaseFlags(cmd *cobra.Command) {
baseFlags := cmd.Flags()
baseFlags.StringVar(&o.Name, FLAG_NAME, "", "The name of the ob tenant")
baseFlags.StringVar(&o.Namespace, FLAG_NAMESPACE, DEFAULT_NAMESPACE, "The namespace of the ob tenant")
baseFlags.IntVar(&o.JobKeepDays, FLAG_JOB_KEEP_DAYS, DEFAULT_JOB_KEEP_DAYS, "The number of days to keep the backup job")
baseFlags.IntVar(&o.RecoveryDays, FLAG_RECOVERY_DAYS, DEFAULT_RECOVERY_DAYS, "The number of days to keep the backup recovery")
Expand Down
2 changes: 2 additions & 0 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

"github.com/oceanbase/ob-operator/internal/cli/cmd/backup"
"github.com/oceanbase/ob-operator/internal/cli/cmd/cluster"
"github.com/oceanbase/ob-operator/internal/cli/cmd/completion"
"github.com/oceanbase/ob-operator/internal/cli/cmd/demo"
"github.com/oceanbase/ob-operator/internal/cli/cmd/install"
"github.com/oceanbase/ob-operator/internal/cli/cmd/tenant"
Expand Down Expand Up @@ -47,6 +48,7 @@ func NewCliCmd() *cobra.Command {
cmd.AddCommand(install.NewCmd())
cmd.AddCommand(update.NewCmd())
cmd.AddCommand(demo.NewCmd())
cmd.AddCommand(completion.NewCmd(cmd.OutOrStdout(), ""))
cmd.Flags().BoolP("version", "v", false, "Print the version of oceanbase cli")
return cmd
}
4 changes: 4 additions & 0 deletions internal/cli/cluster/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,10 @@ func (o *CreateOptions) AddFlags(cmd *cobra.Command) {
o.AddBackupVolumeFlags(cmd)
}

func (o *CreateOptions) SetRequiredFlags(cmd *cobra.Command) {
_ = cmd.MarkFlagRequired(FLAG_ZONES)
}

// AddZoneFlags adds the zone-related flags to the command.
func (o *CreateOptions) AddZoneFlags(cmd *cobra.Command) {
zoneFlags := pflag.NewFlagSet(FLAGSET_ZONE, pflag.ContinueOnError)
Expand Down
3 changes: 2 additions & 1 deletion internal/cli/cluster/scale.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,5 +168,6 @@ func (o *ScaleOptions) Validate() error {
// AddFlags for scale options
func (o *ScaleOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Namespace, FLAG_NAMESPACE, DEFAULT_NAMESPACE, "namespace of ob cluster")
cmd.Flags().StringToStringVar(&o.Zones, FLAG_ZONES, nil, "The zone of the cluster, e.g. '--zones=<zone>=<replica>', set replicas to 0 to delete the zone, only one operation of adding, deleting or modifying is allowd at a time.")
cmd.Flags().StringToStringVar(&o.Zones, FLAG_ZONES, nil, "The zone of the cluster, e.g. '--zones=<zone>=<replica>', set replicas to 0 to delete the zone, only one operation of adding, deleting or modifying is allowd at a time, required")
_ = cmd.MarkFlagRequired(FLAG_ZONES)
}
1 change: 1 addition & 0 deletions internal/cli/cluster/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,5 @@ func (o *UpgradeOptions) Validate() error {
func (o *UpgradeOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Namespace, FLAG_NAMESPACE, DEFAULT_NAMESPACE, "namespace of ob cluster")
cmd.Flags().StringVar(&o.Image, FLAG_OBSERVER_IMAGE, "", "The image of observer") // set image to null, avoid image downgrade
_ = cmd.MarkFlagRequired(FLAG_OBSERVER_IMAGE)
}
172 changes: 172 additions & 0 deletions internal/cli/cmd/completion/completion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
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 completion

import (
"errors"
"fmt"
"io"

"github.com/spf13/cobra"

"github.com/oceanbase/ob-operator/internal/cli/utils"
)

const defaultBoilerPlate = `
# 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.
`

var completionLong = `To load completions:
Bash:
$ source <(obocli completion bash)
# To load completions for each session, execute once:
# Linux:
$ obocli completion bash > /etc/bash_completion.d/obocli
# macOS:
$ obocli completion bash > /usr/local/etc/bash_completion.d/obocli
Zsh:
# If shell completion is not already enabled in your environment,
# you will need to enable it. You can execute the following once:
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
To load completions in your current shell session:
$ source <(obocli completion zsh)
# To load completions for each session, execute once:
$ obocli completion zsh > "${fpath[1]}/_obocli"
# You will need to start a new shell for this setup to take effect.
fish:
$ obocli completion fish | source
# To load completions for each session, execute once:
$ obocli completion fish > ~/.config/fish/completions/obocli.fish
PowerShell:
PS> obocli completion powershell | Out-String | Invoke-Expression
# To load completions for every new session, run:
PS> obocli completion powershell > obocli.ps1
# and source this file from your PowerShell profile.
`
var (
completionShells = map[string]func(out io.Writer, boilerPlate string, cmd *cobra.Command) error{
"bash": runCompletionBash,
"zsh": runCompletionZsh,
"fish": runCompletionFish,
"powershell": runCompletionPwsh,
}
)

// NewCmd creates the instruction command for the completion of commands
func NewCmd(out io.Writer, boilerPlate string) *cobra.Command {
logger := utils.GetDefaultLoggerInstance()
shells := make([]string, 0, len(completionShells))
for shell := range completionShells {
shells = append(shells, shell)
}
cmd := &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Generate completion script for the specified shell (bash, zsh, fish, powershell)",
Long: completionLong,
DisableFlagsInUseLine: true,
ValidArgs: shells,
Args: cobra.OnlyValidArgs,
Run: func(cmd *cobra.Command, args []string) {
if err := RunCompletionE(out, boilerPlate, cmd, args); err != nil {
logger.Fatalln(err)
}
},
}
return cmd
}

// RunCompletionE is the entry point for the completion command
func RunCompletionE(out io.Writer, boilerPlate string, cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("shell not specified. See 'obocli completion -h' for help and examples")
}
if len(args) > 1 {
return errors.New("too many arguments. Expected only the shell type. See 'obocli completion -h' for help and examples")
}
run, found := completionShells[args[0]]
if !found {
return fmt.Errorf("unsupported shell type %q", args[0])
}
return run(out, boilerPlate, cmd.Parent())
}

func runCompletionBash(out io.Writer, boilerPlate string, cmd *cobra.Command) error {
if len(boilerPlate) == 0 {
boilerPlate = defaultBoilerPlate
}
if _, err := out.Write([]byte(boilerPlate)); err != nil {
return err
}
return cmd.GenBashCompletionV2(out, true)
}

func runCompletionZsh(out io.Writer, boilerPlate string, cmd *cobra.Command) error {
// Add zsh completion header to specify the command to complete for zsh
zshHead := fmt.Sprintf("#compdef %[1]s\ncompdef _%[1]s %[1]s\n", cmd.Name())
if _, err := out.Write([]byte(zshHead)); err != nil {
return err
}

if len(boilerPlate) == 0 {
boilerPlate = defaultBoilerPlate
}
if _, err := out.Write([]byte(boilerPlate)); err != nil {
return err
}
return cmd.GenZshCompletion(out)
}

func runCompletionFish(out io.Writer, boilerPlate string, cmd *cobra.Command) error {
if len(boilerPlate) == 0 {
boilerPlate = defaultBoilerPlate
}
if _, err := out.Write([]byte(boilerPlate)); err != nil {
return err
}
return cmd.GenFishCompletion(out, true)
}

func runCompletionPwsh(out io.Writer, boilerPlate string, cmd *cobra.Command) error {
if len(boilerPlate) == 0 {
boilerPlate = defaultBoilerPlate
}
if _, err := out.Write([]byte(boilerPlate)); err != nil {
return err
}
return cmd.GenPowerShellCompletionWithDesc(out)
}
92 changes: 92 additions & 0 deletions internal/cli/cmd/completion/completion_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
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 completion

import (
"bytes"
"strings"
"testing"

"github.com/spf13/cobra"
)

func TestCompletions(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
}{
{
name: "bash",
args: []string{"bash"},
},
{
name: "zsh",
args: []string{"zsh"},
},
{
name: "fish",
args: []string{"fish"},
},
{
name: "powershell",
args: []string{"powershell"},
},
{
name: "no args",
args: []string{},
expectedError: "shell not specified. See 'obocli completion -h' for help and examples",
},
{
name: "too many args",
args: []string{"bash", "zsh"},
expectedError: "too many arguments. Expected only the shell type. See 'obocli completion -h' for help and examples",
},
{
name: "unsupported shell",
args: []string{"foo"},
expectedError: (`unsupported shell type "foo"`),
},
}

for _, tc := range testCases {
t.Run(tc.name, func(tt *testing.T) {
parentCmd := &cobra.Command{
Use: "obocli",
}
out := new(bytes.Buffer)
cmd := NewCmd(out, defaultBoilerPlate)
parentCmd.AddCommand(cmd)
err := RunCompletionE(out, defaultBoilerPlate, cmd, tc.args)
if tc.expectedError == "" {
if err != nil {
tt.Fatalf("Unexpected error: %v", err)
}
if out.Len() == 0 {
tt.Fatalf("Output was not written")
}
if !strings.Contains(out.String(), defaultBoilerPlate) {
tt.Fatalf("Output does not contain boilerplate:\n%s", out.String())
}
} else {
if err == nil {
tt.Fatalf("An error was expected but no error was returned")
}
if err.Error() != tc.expectedError {
tt.Fatalf("unexpected error: %v\n expected: %v\n", err, tc.expectedError)
}
}
})
}
}
13 changes: 9 additions & 4 deletions internal/cli/cmd/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,25 @@ import (
func NewCmd() *cobra.Command {
o := install.NewInstallOptions()
logger := utils.GetDefaultLoggerInstance()
componentList := []string{}
for component := range o.Components {
componentList = append(componentList, component)
}
cmd := &cobra.Command{
Use: "install <component>",
Short: "Command for ob-operator and other components installation",
Long: `Command for ob-operator and other components installation.
Currently support:
- ob-operator: A Kubernetes operator that simplifies the deployment and management of OceanBase cluster and related resources on Kubernetes.
- ob-operator: A Kubernetes operator that simplifies the deployment and management of OceanBase cluster and related resources on Kubernetes, support stable and develop version.
- ob-dashboard: A web application that provides resource management capabilities.
- local-path-provisioner: Provides a way for the Kubernetes users to utilize the local storage in each node, Storage of OceanBase cluster relies on it, which should be installed beforehand.
- local-path-provisioner: Provides a way for the Kubernetes users to utilize the local storage in each node, Storage of OceanBase cluster relies on it, which should be installed beforehand, support stable and develop version.
- cert-manager: Creates TLS certificates for workloads in Kubernetes and renews the certificates before they expire, ob-operator relies on it for certificate management, which should be installed beforehand.
if not specified, install ob-operator and ob-dashboard by default, and cert-manager if it is not found in cluster.`,
PreRunE: o.Parse,
Args: cobra.MaximumNArgs(1),
PreRunE: o.Parse,
ValidArgs: componentList,
Args: cobra.MatchAll(cobra.MaximumNArgs(1), cobra.OnlyValidArgs),
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
logger.Println("Install ob-operator and ob-dashboard by default")
Expand Down
Loading

0 comments on commit 06ff15b

Please sign in to comment.