cobra golang好用的CLI开发工具

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,295评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,928评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,682评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,209评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,237评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,965评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,586评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,487评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,016评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,136评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,271评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,948评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,619评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,139评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,252评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,598评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,267评论 2 358

推荐阅读更多精彩内容