From 08292530132d99a9a1d361ca5ba7b0986d5a8826 Mon Sep 17 00:00:00 2001 From: Liao PengFei <136953902+hdbdn77@users.noreply.github.com> Date: Fri, 24 Nov 2023 15:30:39 +0800 Subject: [PATCH] Quickly start developing curves based on cobra Signed-off-by: Liao PengFei <136953902+hdbdn77@users.noreply.github.com> --- cli_cn.md | 258 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ cli_en.md | 258 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 516 insertions(+) create mode 100644 cli_cn.md create mode 100644 cli_en.md diff --git a/cli_cn.md b/cli_cn.md new file mode 100644 index 000000000..52d4cbeb4 --- /dev/null +++ b/cli_cn.md @@ -0,0 +1,258 @@ +# Curveadm CLI 开发 + +## Cobra库 + +Curveadm CLI是基于[Cobra](https://github.com/spf13/cobra)库(一个用于创建CLI命令行程序的Go语言库)开发的。 + +### Cobra的基本使用 + +使用Cobra创建一个根命令(在命令行打印`root command`): +``` +package main + +import ( + "fmt" + "os" + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "hugo", + Short: "Hugo is a very fast static site generator", + Long: `A Fast and Flexible Static Site Generator built with + love by spf13 and friends in Go. + Complete documentation is available at https://gohugo.io/documentation/`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("root command") + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func main() { + rootCmd.Execute() +} +``` +- Use字段设置命令的名字 +- Short字段设置简短描述 +- Long字段设置详细描述 +- Run字段设置执行该命令时的函数 + +更多`Command`对象字段及用法详见[Cobra库--command.go](https://github.com/spf13/cobra/blob/main/command.go)。 + +### flag使用 + +Cobra支持自定义参数的解析: +``` +cmd.PersistentFlags().BoolP("help", "h", false, "Print usage") +cmd.Flags().BoolVarP(&options.debug, "debug", "d", false, "Print debug information") +``` +PersistentFlags()用于全局flag,可以被当前命令及其子命令使用。 + +Flags()则用于定义本地flag,仅用于当前命令。 + +更多`flag`函数及用法详见[Cobra库--command.go](https://github.com/spf13/cobra/blob/main/command.go)和[pflag库](https://github.com/spf13/pflag)。 + +### hook函数 + +cobra的Command对象支持自定义hook函数(PreRun和PostRun字段),在`run`命令执行前后运行hook函数。如下所示: +``` +cmd := &cobra.Command{ + Use: "root [sub]", + Short: "My root command", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args) + }, + PreRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PreRun with args: %v\n", args) + }, + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd Run with args: %v\n", args) + }, + PostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PostRun with args: %v\n", args) + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args) + }, +} +``` +hook函数会按(`PersistentPreRun`、`PreRun`、`Run`、`PostRun`、`PersistentPostRun`)的顺序依次执行。注意:如果子命令没有设置`Persistent*Run`,则会自动继承父命令的函数定义。 + +### 子命令 +cobra允许嵌套命令,通过当前命令对象的`AddCommand`函数。如下所示: +``` +rootCmd.AddCommand(versionCmd) +``` +推荐的层级命令嵌套结构如下: +``` +├── cmd +│ ├── root.go +│ └── sub1 +│ ├── sub1.go +│ └── sub2 +│ ├── leafA.go +│ └── leafB.go +└── main.go +``` +将 leafA.go 和 leafB.go 中定义的命令添加到 sub2 命令中。 + +将 sub2.go 中定义的命令添加到 sub1 命令中。 + +将 sub1.go 中定义的命令添加到 root 命令中。 + +最终的命令调用的主体结构如下: +``` +root options +root sub1 options +root sub1 sub2 options +root sub1 sub2 leafA options +root sub1 sub2 leafB options +``` + + +## curveadm cli项目结构 +``` +cli +├── cli +│ ├── cli.go +│ └── version.go +├── command +│ ├── audit.go +│ ├── clean.go +│ ├── client +│ ├── cluster +│ ... +└── curveadm.go +``` +cli 文件夹的`cli.go`定义了`curveadm`对象及相关方法,贯穿所有curveadm cli的命令开发。 +``` +type CurveAdm struct { + rootDir string + dataDir string + ... +} +func NewCurveAdm() (*CurveAdm, error) { + curveadm := &CurveAdm{ + rootDir: rootDir, + ... + } + ... + return curveadm, nil +} +``` + +command 目录中存放各层级命令实现。 +``` +├── audit.go +├── client +│ ├── cmd.go +│ ├── enter.go +│ └── unmap.go +├── cluster +│ ├── add.go +│ ├── cmd.go +├── cmd.go +├── deploy.go +``` +在curveadm cli中,每层的根命令都在`cmd.go`定义。根命令只负责注册子命令以及提供帮助信息,并不参与实际工作操作。 +``` +cli\command\cmd.go + +func addSubCommands(cmd *cobra.Command, curveadm *cli.CurveAdm) { + cmd.AddCommand( + client.NewClientCommand(curveadm), // curveadm client + ... + ) +} +func NewCurveAdmCommand(curveadm *cli.CurveAdm) *cobra.Command { + ... + cmd := &cobra.Command{ + Use: "curveadm [OPTIONS] COMMAND [ARGS...]", + ... + } + ... + addSubCommands(cmd, curveadm) + return cmd +} +################################################################ +cli\command\client\cmd.go + +func NewClientCommand(curveadm *cli.CurveAdm) *cobra.Command { + cmd := &cobra.Command{ + Use: "client", + ... + } + cmd.AddCommand( + NewMapCommand(curveadm), + ... + ) + ... +} +################################################################ +cli\command\client\enter.go + +func NewEnterCommand(curveadm *cli.CurveAdm) *cobra.Command { + ... + cmd := &cobra.Command{ + Use: "enter ID", + ... + } + ... +} +``` +最终enter命令的调用结构如下: +``` +curveadm client enter ID +``` + +curveadm.go 定义了`curveadm` 根命令的执行函数同时执行相关审计工作。 +``` +func Execute() { + curveadm, err := cli.NewCurveAdm() + ... + id := curveadm.PreAudit(time.Now(), os.Args[1:]) + cmd := command.NewCurveAdmCommand(curveadm) + err = cmd.Execute() + curveadm.PostAudit(id, err) + if err != nil { + os.Exit(1) + } +} +``` + +`curveadm` 主程序的入口则是在[curveadm文件夹下](https://github.com/opencurve/curveadm/tree/develop/cmd/curveadm),可以在该目录下执行`curveadm`的运行及编译 +``` +func main() { + cli.Execute() +} +``` + +### curveadm 通用工具 +对于curveadm cli的命令开发,curveadm 提供了通用工具,如: + +- cliutil.NoArgs:用于判断命令是否不包含参数 + +- cliutil.ShowHelp:用于在命令运行时展示帮助信息 + +在[curveadm/internal目录下](https://github.com/opencurve/curveadm/tree/develop/internal)。如下所示: +``` +import ( + cliutil "github.com/opencurve/curveadm/internal/utils" + ... +) + +cmd := &cobra.Command{ + Use: "client", + Args: cliutil.NoArgs, + RunE: cliutil.ShowHelp(curveadm.Err()), +} +``` +`cliutil.NoArgs`指明`curveadm client`命令不包含任何参数(子命令除外);`cliutil.ShowHelp`函数在直接运行`curveadm client`命令时展示定义的帮助选项。 + +更多通用命令及用法请参考[internal文件夹](https://github.com/opencurve/curveadm/tree/develop/internal)。 \ No newline at end of file diff --git a/cli_en.md b/cli_en.md new file mode 100644 index 000000000..bd0201366 --- /dev/null +++ b/cli_en.md @@ -0,0 +1,258 @@ +# Curveadm CLI Development + +## Cobra library + +Curveadm CLI is developed based on the [Cobra](https://github.com/spf13/cobra) library, a Go language library for creating CLI command line programs. + +### Basic use of Cobra + +Create a root command using Cobra (print `root command` on the command line): +``` +package main + +import ( + "fmt" + "os" + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "hugo", + Short: "Hugo is a very fast static site generator", + Long: `A Fast and Flexible Static Site Generator built with + love by spf13 and friends in Go. + Complete documentation is available at https://gohugo.io/documentation/`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("root command") + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func main() { + rootCmd.Execute() +} +``` +- Use field sets the name of the command +- Short field sets a short description +- Long field setting detailed description +- The Run field sets the function when executing the command + +For more details on `Command` object fields and usage, see [Cobra library--command.go](https://github.com/spf13/cobra/blob/main/command.go). + +### flag usage + +Cobra supports parsing of custom parameters: +``` +cmd.PersistentFlags().BoolP("help", "h", false, "Print usage") +cmd.Flags().BoolVarP(&options.debug, "debug", "d", false, "Print debug information") +``` +PersistentFlags() is used for global flags and can be used by the current command and its subcommands. + +Flags() is used to define local flags, only for the current command. + +For more details on the `flag` function and usage, see [Cobra library--command.go](https://github.com/spf13/cobra/blob/main/command.go) and [pflag library](https:// github.com/spf13/pflag). + +### hook function + +Cobra's Command object supports custom hook functions (PreRun and PostRun fields), and the hook function is run before and after the `run` command is executed. As follows: +``` +cmd := &cobra.Command{ + Use: "root [sub]", + Short: "My root command", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args) + }, + PreRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PreRun with args: %v\n", args) + }, + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd Run with args: %v\n", args) + }, + PostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PostRun with args: %v\n", args) + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args) + }, +} +``` +The hook function will be executed in the order of (`PersistentPreRun`, `PreRun`, `Run`, `PostRun`, `PersistentPostRun`). Note: If the subcommand does not set `Persistent*Run`, it will automatically inherit the function definition of the parent command. + +### Subcommands +cobra allows nested commands, through the `AddCommand` function of the current command object. As follows: +``` +rootCmd.AddCommand(versionCmd) +``` +The recommended hierarchical command nesting structure is as follows: +``` +├── cmd +│ ├── root.go +│ └── sub1 +│ ├── sub1.go +│ └── sub2 +│ ├── leafA.go +│ └── leafB.go +└── main.go +``` +Add the commands defined in leafA.go and leafB.go to the sub2 command. + +Add the commands defined in sub2.go to the sub1 command. + +Add the commands defined in sub1.go to the root command. + +The main structure of the final command call is as follows: +``` +root options +root sub1 options +root sub1 sub2 options +root sub1 sub2 leafA options +root sub1 sub2 leafB options +``` + + +## curveadm cli project structure +``` +cli +├── cli +│ ├── cli.go +│ └── version.go +├── command +│ ├── audit.go +│ ├── clean.go +│ ├── client +│ ├── cluster +│ ... +└── curveadm.go +``` +The `cli.go` in the cli folder defines the `curveadm` object and related methods, which run through all curveadm cli command development. +``` +type CurveAdm struct { + rootDir string + dataDir string + ... +} +func NewCurveAdm() (*CurveAdm, error) { + curveadm := &CurveAdm{ + rootDir: rootDir, + ... + } + ... + return curveadm, nil +} +``` + +The command directory stores command implementations at each level. +``` +├── audit.go +├── client +│ ├── cmd.go +│ ├── enter.go +│ └── unmap.go +├── cluster +│ ├── add.go +│ ├── cmd.go +├── cmd.go +├── deploy.go +``` +In curveadm cli, the root command of each layer is defined in `cmd.go`. The root command is only responsible for registering subcommands and providing help information, and does not participate in actual work operations. +``` +cli\command\cmd.go + +func addSubCommands(cmd *cobra.Command, curveadm *cli.CurveAdm) { + cmd.AddCommand( + client.NewClientCommand(curveadm), // curveadm client + ... + ) +} +func NewCurveAdmCommand(curveadm *cli.CurveAdm) *cobra.Command { + ... + cmd := &cobra.Command{ + Use: "curveadm [OPTIONS] COMMAND [ARGS...]", + ... + } + ... + addSubCommands(cmd, curveadm) + return cmd +} +################################################ ############## +cli\command\client\cmd.go + +func NewClientCommand(curveadm *cli.CurveAdm) *cobra.Command { + cmd := &cobra.Command{ + Use: "client", + ... + } + cmd.AddCommand( + NewMapCommand(curveadm), + ... + ) + ... +} +################################################ ############## +cli\command\client\enter.go + +func NewEnterCommand(curveadm *cli.CurveAdm) *cobra.Command { + ... + cmd := &cobra.Command{ + Use: "enter ID", + ... + } + ... +} +``` +The final call structure of the enter command is as follows: +``` +curveadm client enter ID +``` + +curveadm.go defines the execution function of the `curveadm` root command and performs related audit work. +``` +func Execute() { + curveadm, err := cli.NewCurveAdm() + ... + id := curveadm.PreAudit(time.Now(), os.Args[1:]) + cmd := command.NewCurveAdmCommand(curveadm) + err = cmd.Execute() + curveadm.PostAudit(id, err) + if err != nil { + os.Exit(1) + } +} +``` + +The entrance to the `curveadm` main program is under the [curveadm folder](https://github.com/opencurve/curveadm/tree/develop/cmd/curveadm). You can execute the operation and execution of `curveadm` in this directory. compile +``` +func main() { + cli.Execute() +} +``` + +### curveadm general tools +For command development of curveadm cli, curveadm provides general tools, such as: + +- cliutil.NoArgs: used to determine whether the command does not contain parameters + +- cliutil.ShowHelp: used to display help information when the command is run + +In the [curveadm/internal directory](https://github.com/opencurve/curveadm/tree/develop/internal). As follows: +``` +import ( + cliutil "github.com/opencurve/curveadm/internal/utils" + ... +) + +cmd := &cobra.Command{ + Use: "client", + Args: cliutil.NoArgs, + RunE: cliutil.ShowHelp(curveadm.Err()), +} +``` +`cliutil.NoArgs` specifies that the `curveadm client` command does not contain any arguments (except subcommands); the `cliutil.ShowHelp` function displays the defined help options when running the `curveadm client` command directly. + +For more common commands and usage, please refer to [internal folder](https://github.com/opencurve/curveadm/tree/develop/internal). \ No newline at end of file