likes
comments
collection
share

Golang 开发之Cobra初探

作者站长头像
站长
· 阅读数 13

一 背景

在云原生如日中天的当下,相信很多人对Kubernetes/etcd等都有所听闻,当我们看其源码或对其进行二次开发的时候,可以发现其均使用了一个命令行程序库Cobra,其是一个用来编写命令行的神器,提供了一个脚手架,用于快速生成基于Cobra应用程序框架。

其作者是非常有名的spf13,相信大家对vim都有所了解,可以去使用vim的终极终端spf13-vim,可以一键配置,非常的方便,其还有作品viper是一个完整的配置解决方案,支持JSON/YAML/TOML/HCL/envFile等配置文件,还可以热加载,配置保存等,hugo也是其的作品。

我们可以利用Cobra快速的去开发出我们想要的命令行工具,非常的方便快捷。

二 功能特性

  • 简易的子命令行模式,如 app server, app fetch等等
  • 完全兼容posix命令行模式
  • 嵌套子命令subcommand
  • 支持全局,局部,串联flags
  • 使用Cobra很容易的生成应用程序和命令,使用cobra create appname和cobra add cmdname
  • 如果命令输入错误,将提供智能建议,如 app srver,将提示srver没有,是否是app server
  • 自动生成commands和flags的帮助信息
  • 自动生成详细的help信息,如app help
  • 自动识别-h,--help帮助flag
  • 自动生成应用程序在bash下命令自动完成功能
  • 自动生成应用程序的man手册
  • 命令行别名
  • 自定义help和usage信息
  • 可选的紧密集成的viper apps

三 使用Cobra

3.1 安装

Cobra安装非常简单,可以使用go get获取即可,安装完成后,打开GOPATH目录,bin目录下应该有已经编译好的cobra,当然也可以使用源码编译安装。

在使用cobra之前需要了解三个概念,其也是命令行的三部分内容,command、flag和args

  • 命令自身的一些基本信息,用command表示,具体对象是 cobra.Command
  • 命令的一些标致或者选项,用flag表示,具体对象是 flag.FlagSet
  • 最后的参数,用args表示,通常是[]string

对应如下例子:

go get -u test.com/a/b

这里get 就是commond(这里比较特殊), -u 就是flag, test.com/a/b 就是args

3.2 生成应用程序

$ /Users/xuel/workspace/goworkspace/bin/cobra init --pkg-name smartant-cli
Your Cobra application is ready at
/Users/xuel/workspace/goworkspace/src/github.com/kaliarch/smartant-cli
$ ls
LICENSE cmd     go.mod  go.sum  main.go
$ tree
.
├── LICENSE
├── cmd
│   └── root.go
├── go.mod
├── go.sum
└── main.go

1 directory, 5 files

3.3 设计cls程序

在smartant-cli 目录下创建imp目录,器重编写utils.go文件,内入如下

package utils

import "fmt"

func Show(name string, age int) {
	fmt.Printf("name is %s, age is %d", name, age)
}

  • main.go
package main

import "github.com/kaliarch/smartant-cli/cmd"

func main() {
	cmd.Execute()
}

可以看出main函数执行cmd包,所以我们只需要在cmd包内调用utils包就能实现smartant-cli程序的需求。接着打开root.go文件查看:

  • root.go
/*
Copyright © 2021 NAME HERE <EMAIL ADDRESS>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd

import (
	"fmt"
	"github.com/spf13/cobra"
	"os"

	homedir "github.com/mitchellh/go-homedir"
	"github.com/spf13/viper"
)

var cfgFile string

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
	Use:   "smartant-cli",
	Short: "SmartAnt linux agent cli",
	Long: `
smartant-cli is a CLI for SmartAnt applications.
This application is a tool to migrations linux system.`,
	// Uncomment the following line if your bare application
	// has an action associated with it:
	//	Run: func(cmd *cobra.Command, args []string) { },
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func init() {
	cobra.OnInitialize(initConfig)

	// Here you will define your flags and configuration settings.
	// Cobra supports persistent flags, which, if defined here,
	// will be global for your application.

	rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.smartant-cli.yaml)")

	// Cobra also supports local flags, which will only run
	// when this action is called directly.
	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

// initConfig reads in config file and ENV variables if set.
func initConfig() {
	if cfgFile != "" {
		// Use config file from the flag.
		viper.SetConfigFile(cfgFile)
	} else {
		// Find home directory.
		home, err := homedir.Dir()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		// Search config in home directory with name ".smartant-cli" (without extension).
		viper.AddConfigPath(home)
		viper.SetConfigName(".smartant-cli")
	}

	viper.AutomaticEnv() // read in environment variables that match

	// If a config file is found, read it in.
	if err := viper.ReadInConfig(); err == nil {
		fmt.Println("Using config file:", viper.ConfigFileUsed())
	}
}

从源代码来看cmd包进行了一些初始化操作并提供了Execute接口。十分简单,其中viper是cobra集成的配置文件读取的库,这里不需要使用,我们可以注释掉(不注释可能生成的应用程序很大约10M,这里没哟用到最好是注释掉)。cobra的所有命令都是通过cobra.Command这个结构体实现的。为了实现smartant-cli功能,显然我们需要修改RootCmd。修改后的代码如下:

/*
Copyright © 2021 NAME HERE <EMAIL ADDRESS>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd

import (
	"fmt"
	"github.com/spf13/cobra"
	//"github.com/spf13/viper"
	"github.com/kaliarch/cobra-demo/utils"
	"os"
)

var cfgFile string

//var name string
//var age int
var command string

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
	Use:   "cobra-demo",
	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.`,
	// Uncomment the following line if your bare application
	// has an action associated with it:
	//	Run: func(cmd *cobra.Command, args []string) { },
	Run: func(cmd *cobra.Command, args []string) {
		//if len(name) == 0 {
		//	cmd.Help()
		//	return
		//}
		//imp.Show(name, age)
		if len(command) == 0 {
			cmd.Help()
			return
		}
		utils.Cmd(command)

	},
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}
}

func init() {
	//cobra.OnInitialize(initConfig)

	// Here you will define your flags and configuration settings.
	// Cobra supports persistent flags, which, if defined here,
	// will be global for your application.

	rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.smartant-agent.yaml)")

	// Cobra also supports local flags, which will only run
	// when this action is called directly.
	//rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
	//rootCmd.PersistentFlags().StringVarP(&name, "name", "n", "", "person name")
	//rootCmd.PersistentFlags().IntVarP(&age, "age", "a", 0, "person age")
	rootCmd.PersistentFlags().StringVarP(&command, "command", "o", "", "execute command context")

}

// initConfig reads in config file and ENV variables if set.
//func initConfig() {
//	if cfgFile != "" {
//		// Use config file from the flag.
//		viper.SetConfigFile(cfgFile)
//	} else {
//		// Find home directory.
//		home, err := homedir.Dir()
//		if err != nil {
//			fmt.Println(err)
//			os.Exit(1)
//		}
//
//		// Search config in home directory with name ".cobra-demo" (without extension).
//		viper.AddConfigPath(home)
//		viper.SetConfigName(".cobra-demo")
//	}
//
//	viper.AutomaticEnv() // read in environment variables that match
//
//	// If a config file is found, read it in.
//	if err := viper.ReadInConfig(); err == nil {
//		fmt.Println("Using config file:", viper.ConfigFileUsed())
//	}
//}

3.4 执行

# 编译
$ go build -o smartant-cli
$ ./smartant-cli 

smartant-cli is a CLI for SmartAnt applications.
This application is a tool to migrations linux system.

Usage:
  smartant-cli [flags]

Flags:
  -a, --age int       persons age
  -h, --help          help for smartant-cli
  -n, --name string   persons name
$ ./smartant-cli -a 11 -n "xuel"
name is xuel, age is 11%                                              

四 实现带有子命令的clis

在执行cobra.exe init demo之后,继续使用cobra为demo添加子命令test:

4.1 生成sysinfo子命令

$ /Users/xuel/workspace/goworkspace/bin/cobra add sysinfo
sysinfo created at /Users/xuel/workspace/goworkspace/src/github.com/kaliarch/smartant-cli
$ tree
.
├── LICENSE
├── cmd
│   ├── root.go
│   └── sysinfo.go
├── go.mod
├── go.sum
├── main.go
├── smartant-cli
└── utils
    └── utils.go

4.2 查看子命令

$ go build -o smartant-cli 
$ ./smartant-cli 

smartant-cli is a CLI for SmartAnt applications.
This application is a tool to migrations linux system.

Usage:
  smartant-cli [flags]
  smartant-cli [command]

Available Commands:
  help        Help about any command
  sysinfo     A brief description of your command

Flags:
  -a, --age int       persons age
  -h, --help          help for smartant-cli
  -n, --name string   persons name

Use "smartant-cli [command] --help" for more information about a command.
$ ./smartant-cli sysinfo -h
A longer description that spans multiple lines and likely contains examples
and usage of using your command. 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.

Usage:
  smartant-cli sysinfo [flags]

Flags:
  -h, --help   help for sysinfo

4.3 编写子命令

  • sysinfo.go
/*
Copyright © 2021 NAME HERE <EMAIL ADDRESS>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd

import (
	"fmt"
	"github.com/kaliarch/smartant-cli/utils"

	"github.com/spf13/cobra"
)

var (
	host, pwd, username string
	port                int
	command             string
)

// sysinfoCmd represents the sysinfo command
var sysinfoCmd = &cobra.Command{
	Use:   "sysinfo",
	Short: "check sys info message",
	Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. 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) {
		if len(host) == 0 || len(pwd) == 0 {
			cmd.Help()
			return
		}
		fmt.Println("sysinfo called")
		utils.Sysinfo(host, pwd, username, port, command)
		fmt.Println("sysinfo called commpled")
	},
}

func init() {
	rootCmd.AddCommand(sysinfoCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// sysinfoCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// sysinfoCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
	sysinfoCmd.Flags().StringVarP(&host, "host", "i", "", "host ip addr")
	sysinfoCmd.Flags().StringVarP(&username, "username", "u", "", "host username")
	sysinfoCmd.Flags().StringVarP(&command, "command", "c", "", "command")
	sysinfoCmd.Flags().StringVarP(&pwd, "pwd", "p", "", "host password")
	sysinfoCmd.Flags().IntVarP(&port, "port", "P", 0, "host port")
}
  • utils.go
package utils

import (
	"bytes"
	"fmt"
	"golang.org/x/crypto/ssh"
	"net"
	"strings"
	//"strconv"
	"log"
)

// smartant-cli
func Show(name string, age int) {
	fmt.Printf("name is %s, age is %d", name, age)
}

func sshConnect(user, pwd, host string, port int) (*ssh.Session, error) {
	var (
		auth         []ssh.AuthMethod
		addr         string
		clientConfig *ssh.ClientConfig
		client       *ssh.Client
		session      *ssh.Session
		err          error
	)
	// get auth method
	auth = make([]ssh.AuthMethod, 0)
	auth = append(auth, ssh.Password(pwd))

	// host key callbk
	hostKeyCallbk := func(host string, remote net.Addr, key ssh.PublicKey) error {
		return nil
	}
	clientConfig = &ssh.ClientConfig{
		User:            user,
		Auth:            auth,
		HostKeyCallback: hostKeyCallbk,
		BannerCallback:  nil,
		//ClientVersion:     "",
		//HostKeyAlgorithms: nil,
		//Timeout: 10000000,
	}

	// connet to ssh
	addr = fmt.Sprintf("%s:%d", host, port)

	if client, err = ssh.Dial("tcp", addr, clientConfig); err != nil {
		return nil, err
	}

	// create session
	if session, err = client.NewSession(); err != nil {
		return nil, err
	}
	return session, nil
}

func Sysinfo(host, pwd, username string, port int, cmd string) {
	var stdOut, stdErr bytes.Buffer
	// 使用用户名,密码登陆
	session, err := sshConnect(username, pwd, host, port)
	if err != nil {
		log.Fatal(err)
	}
	defer session.Close()

	session.Stdout = &stdOut
	session.Stderr = &stdErr

	session.Run(cmd)
	fmt.Println(strings.Replace(stdOut.String(), "\n", " ", -1))
}

  • 执行测试
$ ./smartant-cli sysinfo
A longer description that spans multiple lines and likely contains examples
and usage of using your command. 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.

Usage:
  smartant-cli sysinfo [flags]

Flags:
  -c, --command string    command
  -h, --help              help for sysinfo
  -i, --host string       host ip addr
  -P, --port int          host port
  -p, --pwd string        host password
  -u, --username string   host username

$ ./smartant-cli sysinfo -i 121.3.10.55 -u root -P 22 -p xxxxxxx -c "cat /etc/hosts"
sysinfo called
::1     localhost       localhost.localdomain   localhost6      localhost6.localdomain6  127.0.0.1      localhost       localhost.localdomain   localhost4      localhost4.localdomain4 127.0.0.1   localhost        localhost 127.0.0.1     hw-server       hw-server  
sysinfo called commpled

五 其他

Cobra非常强大,能够帮助我们快速创建命令行工具,但是如果直接仅仅写一个非常简单的命令行工具,flag选项非常少,golang built-in的flag库够了。当然使用看个人选择,Cobra更适合复杂的命令行工具。

参考链接