hubble的命令行利用cobra包编写。
cobra作为go语言开发命令行程序最流行的包,应用于多个知名项目,包括:docker,Kubernetes,Hugo,rkt,etcd,Moby,OpenShift等。
Cobra是一个库,提供了一个简单的接口来创建类似于git&go工具的强大的现代CLI接口。Cobra也是一个应用程序,它将生成应用程序脚手架以快速开发基于Cobra的应用程序。
Cobra提供:
cobra init appname
和cobra add cmdname
轻松生成应用程序和命令app srver...
拼写错误时会提示为app server
)-h
、-help
等帮助标志。viper
结合,来开发12-factor程序。如果参考代码学习cobra可以参考码云上的简单例子cobra-example
Cobra是建立在命令(Commands)、参数(Args)和标志(Flags)之上的。命令表示动作,参数是事物,标志是这些动作的修饰符。最好的命令行应用程序在使用时读起来像句子。用户将知道如何使用该应用程序,因为他们将了解如何使用它。
模式遵从APPNAME-动词-名词-形容词,或者APPNAME-命令-ARG-标志。
例如,hugo中这样使用,server
是命令,port
是标志:
hugo server --port=1313
git中bare的方式进行克隆,形如“APPNAME-动词-名词-形容词”:
git clone URL --bare
命令是应用程序入口。应用程序支持的每个交互都将包含在命令中。命令可以有子命令,也可以运行操作。
例如,cobra-example
下的root.go
代码下的RootCmd
。
标志是修改命令行为的一种方式。Cobra支持完全符合POSIX的标志以及Go标志包。Cobra命令可以定义持续到子命令的标志,以及仅对该命令可用的标志。
在上面的示例中,port
就是标志。
标志功能是由pflag
库提供的,pflag
库是标志标准库的一个分支,在添加POSIX兼容性的同时维护相同的接口。
如果需要支持cobra命令自动生成代码,可以使用
go get github.com/spf13/cobra/cobra
将cobra
添加到$GOROOT/bin
下作为命令使用,这时也可以在程序中引入使用
import "github.com/spf13/cobra"
如果不需要代码生成只需要在项目中使用,比如hubble使用的cobra库版本是v0.0.5,可以在go.mod
下增加
github.com/spf13/cobra v0.0.5
引入同上边描述。
命令的使用方式请参考官方文档,可以试着按官方文档使用命令生成脚手架程序。
参考cobra-example程序,入口在main.go中。
func main() {
Execute()
}
Execute()
在root.go中定义,通过这个方法调用主命令RootCmd
的Execute()
来启动命令行。
RootCmd的定义:
var RootCmd = &cobra.Command{
Use: "cobra-example",
Short: "cobra例子",
Long: `cobra-example是一个使用cobra工具包的例子程序。
Cobra是一个开发Go语言CLI端程序的库,现已应用于docker,Kubernetes,Hugo,etcd等多个知名的大型Go语言项目中。`,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}
一般在init()中绑定flags,flags分为两类,一种LocalFlag,一种PersistentFlag。
PersistentFlag
相当于全局flag,LocalFlag
一般只应用于本命令。
例如:
RootCmd.PersistentFlags().StringVar(&cfgFile, "config",
"", "config file (default is $HOME/.cobra-example.yaml)")
RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
这样定义的flag在程序的所有命令中都可用。
在运行其他命令时会输出:
Global Flags:
--config string config file (default is $HOME/.cobra-example.yaml)
而toogle
这时看不到。
标志的使用可以参考start.go,
startCmd.Flags().BoolVarP(&backend, "backend", "b", false, "后端启动")
这样在命令行输入--backend=true
时,定义的backend
就能获取值了。
命令嵌套使用AddCommand
,例如hubble中:
hubbleCmd.AddCommand(
startCmd,
startSingleNodeCmd,
initCmd,
certCmd,
quitCmd,
sqlShellCmd,
userCmd,
nodeCmd,
dumpCmd,
demoCmd,
genCmd,
versionCmd,
DebugCmd,
sqlfmtCmd,
workloadcli.WorkloadCmd(true /* userFacing */),
systemBenchCmd,
)
这段代码将start
等子命令添加到hubble
命令下。
每个命令都是先执行init进行flag绑定,run进行实际操作的。其他详细使用方式请参考例子。
例子中包含了一个标准的使用配置文件的方法。更详细的viper包使用请参考官方文档
由init中定义cobra.OnInitialize
来加载配置文件,在使用时将其与flags进行绑定。
但实际开发中遇到了其中存在的问题。cobra.OnInitialize虽然在代码之初定义,但实际运行是在所有init之后。看代码的话发现AddCommand和OnInitialize虽然都是调用append
,但实际上
OnInitialize源码定义
initializers = append(initializers, y...)
AddCommand中定义
c.commands = append(c.commands, x)
可能规则是按字母顺序进行加载(存疑)。
为什么OnInitialize后加载呢,OnInitialize相当于添加额外的初始化,而这部分应该在绑定flag之后,否则也无法在OnInitialize中读取到flag的参数。
如
./cobra-example start --config .cobra-example.yaml
先获取config的参数文件名,然后在读取文件中的配置。
逻辑上是正确的,但如果想在init中使用文件中的配置这种方式就会失败。而hubble中这种方式就存在问题,原因是所有参数都是要在init时获取的。
所以,目前hubble中的读取实现是采用的直接定义配置文件的方式,但这样做不够自定义化。在后续重构代码时可以考虑将此处作为一个待修改点。