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,会发现非常熟悉