Cobra
每个好的开源项目都会有很多好用的开源库的诞生,之前学openstack的时候就对openstack的oslo系列工具组用的非常多,现在学习k8s后发现同样在go下也有很多类似的开源库,比如Cobra 就是一个用来创建命令行的 golang 库,同时也是一个用于生成应用和命令行文件的程序, 包括docker,k8s 都用的类似方式去实现,用于实现CLI非常好用,我的理解他有点类似openstack里的oslo.config。
概念:
Cobra 结构由三部分组成:命令 (commands)、参数 (arguments)、标志 (flags)。基本模型如下
比如git的命令或者kube-scheduler命令:
#git
git clone url --bare
# kube-scheduler
kube-scheduler --address=127.0.0.1 --leader-elect=true --kubeconfig=/etc/kubernetes/scheduler.conf
- git :根命令
- clone: 子命令
- url: 参数args
- --bare : flag,用于修饰这条命令的一些描述或者约束
安装
安装前请指定后$GOBIN路径,不然安装会失败
go get -v github.com/spf13/cobra/cobra
PS:安装失败一般是熟悉的网络问题,请先cd到$GOPATH/src/golang.org/x目录下用 git clone 下载 sys 和 text 项目
git clone https://github.com/golang/text
git clone https://github.com/golang/sys
然后执行go install github.com/spf13/cobra/cobra, 安装后在 $GOBIN 下出现了 cobra 可执行程序
使用
- 初始化项目:
cobra init --pkg-name /root/go/src/study/cobrademo
新版本必须加参数 --pkg-name 。老版本直接cobra init projectname即可
当前的目录结构:
[root@dev001 cobrademo]# pwd
/root/go/src/study/cobrademo
[root@dev001 cobrademo]# tree
.
├── cmd
│ ├── root.go
├── LICENSE
└── main.go
看以下main.go的代码, 就是调用cmd,然后执行execute方法
package main
import "study/cobrademo/cmd"
func main() {
cmd.Execute()
}
看下cmd下的root.go:
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"os"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
)
var cfgFile string
// 根命令,cobrademo的结构体
var rootCmd = &cobra.Command{
Use: "cobrademo",
Short: "A brief description of your application", // 短描述
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`, // 长描述
Run: func(cmd *cobra.Command, args []string) { // 执行方法
fmt.Println("start fabrademo project\n")
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobrademo.yaml)")
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
func initConfig() {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
viper.AddConfigPath(home)
viper.SetConfigName(".cobrademo")
}
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
}
代码也很简单,可以看到具体执行的代码块就在Run: func(***) 这里, 后续添加command只要在rootcmd下建立子集即可
- Commands
默认建的commands都在在根command下的同级command,需要手动修改所属关系
root.go 标记的是项目的根命令,接下来添加2个子命令
cobra add create
cobra add delete
在cmd 下面可以看到有个新文件create.go delete.go
修改create.go 代码如下
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var createCmd = &cobra.Command{
Use: "create",
Short: "Short Desc For Create",
Long: `Long Desc For Create`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("create called")
},
}
func init() {
rootCmd.AddCommand(createCmd) // 这里表示优先级,即createcmd是rootcmd的子集
}
执行main.go:
go run main.go -h
A Long Brief
Usage:
cobrademo [flags]
cobrademo [command]
Available Commands:
create A brief description of your command
delete A brief description of your command
help Help about any command
Flags:
--config string config file (default is $HOME/.cobrademo.yaml)
-h, --help help for cobrademo
-t, --toggle Help message for toggle
Use "cobrademo [command] --help" for more information about a command.
可以看到Available Commands 多了2个子命令集,且这2个命令是同级的
在create 里在添加一个子命令:
cobra add role
修改role.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var roleCmd = &cobra.Command{
Use: "role",
Short: "short role",
Long: `long role`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("role called")
},
}
func init() {
createCmd.AddCommand(roleCmd)
}
将role 命令作为create命令的子命令,即在init下面修改为createCmd
运行main.go:
go run main.go create -h
A longer description that spans multiple lines and likely contains examples
Usage:
cobrademo create [flags]
cobrademo create [command]
Available Commands:
role short role
Flags:
-h, --help help for create
Global Flags:
--config string config file (default is $HOME/.cobrademo.yaml)
Use "cobrademo create [command] --help" for more information about a command.
go run main.go create role
role called
- Arguments
在command下进行参数配置,一般写cli对于参数配置无非用if ... len(args)判断下参数数量,cobra内置了几个验证的方法,内置的验证方法如下:
- NoArgs:如果有任何参数,命令行将会报错
- ArbitraryArgs: 命令行将会接收任何参数
- OnlyValidArgs: 如果有如何参数不属于 Command 的 ValidArgs 字段,命令行将会报错
- MinimumNArgs(int): 如果参数个数少于 N 个,命令行将会报错
- MaximumNArgs(int): 如果参数个数多于 N 个,命令行将会报错
- ExactArgs(int): 如果参数个数不等于 N 个,命令行将会报错
- RangeArgs(min, max): 如果参数个数不在 min 和 max 之间, 命令行将会报错
修改role.go:
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var roleCmd = &cobra.Command{
Use: "role",
Short: "short role",
Long: `long role`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("create role name: %s", args[0])
},
}
func init() {
createCmd.AddCommand(roleCmd)
}
执行main对应参数,并尝试参数数量修改:
go run main.go create role barney
create role name: barney
go run main.go create role barney ken
Error: accepts 1 arg(s), received 2
Usage:
cobrademo create role [flags]
Flags:
-h, --help help for role
Global Flags:
--config string config file (default is $HOME/.cobrademo.yaml)
accepts 1 arg(s), received 2
exit status 1
- Flag
但在实际使用中,直接加arg的方式并不好用,args的作为数组的方式传入,对传入顺序也有要求,没有办法像map那样做匹配,所以可以使用flag进行匹配,flag分为2类:
- persistent
该flag是一个全局flag,常见的比如verbose,在任何command(无论是根command还是子command下)都可以使用- localflag
该flag 直接作用于单个command下
/* persistent flag */
// 定义变量
var verbose bool
// 参数分别表示变量取地址,flag名,缩写flag名,默认值,帮助
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
/* local flag */
var name string
roleCmd.Flags().StringVarP(&name, "name", "n", "", "role name")
一般情况下都是使用StringVarP 用来接收类型为字符串变量的标志. 相较StringVar, StringVarP 支持标志短写. 以我们的 CLI 为例:在指定标志时可以用 --name,也可以使用短写 -n
修改后的role.go:
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// 添加变量 name
var name string
var roleCmd = &cobra.Command{
Use: "role",
Short: "short role",
Long: `long role`,
//Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
// 如果没有flag输入的情况下
if len(name) == 0 {
cmd.Help()
return
}
fmt.Printf("create role name: %s \n", name)
},
}
func init() {
createCmd.AddCommand(roleCmd)
// 添加本地标志
roleCmd.Flags().StringVarP(&name, "name", "n", "", "role name")
}
执行main.go:
go run main.go create role
Usage:
cobrademo create role [flags]
Flags:
-h, --help help for role
-n, --name string role name
Global Flags:
--config string config file (default is $HOME/.cobrademo.yaml)
# 可以看到分了flag 和默认的global flag,globalflag因为用的是rootCmd.PersistentFlags().StringVar ,所以没有缩写
go run main.go create role --name=barney
create role name: barney
最后的global是cobra创建project后自带的,默认使用的yaml的配置文件方式,默认位置也在$HOME/.cobrademo.yaml 下,看到这里第一反应就是kubectl的~/.kube/config配置文件,所以看完了cobra再去看kubernetes,会发现非常熟悉