Cobra development of Golang

Posted by BigJohn on Mon, 17 Jan 2022 00:54:55 +0100

One background

At the moment when the cloud is in its prime, I believe many people have heard of Kubernetes/etcd. When we look at its source code or carry out secondary development, we can find that it uses a command journey library cobra, which is an artifact used to write command lines and provides a scaffold for quickly generating Cobra based application frameworks.

The author is a very famous spf13. I believe everyone knows something about vim. You can use the ultimate terminal spf13 VIM of vim, which can be configured with one click. It is very convenient. The work viper is a complete configuration solution, supports JSON/YAML/TOML/HCL/envFile and other configuration files, and can also be hot loaded and saved. hugo is also his work.

We can use Cobra to quickly develop the command-line tools we want, which is very convenient and fast.

II. Functional characteristics

  • Simple subcommand line modes, such as app server, app fetch, etc
  • Fully compatible with posix command line mode
  • Nested subcommand subcommand
  • Support global, local and concatenated flags
  • Use cobra to easily generate applications and commands. Use cobra create appname and cobra add cmdname
  • If the command is entered incorrectly, intelligent suggestions will be provided. For example, app srver will prompt whether srver is not an app server or not
  • Automatically generate help information for commands and flags
  • Automatically generate detailed help information, such as app help
  • Auto identify - h, – help flag
  • Automatic generation of application program and automatic completion of command under bash
  • Manual for automatically generating applications
  • Command line alias
  • Customize help and usage information
  • Optional tightly integrated viper apps

III. use Cobra

3.1 installation

Cobra installation is very simple. You can use go get to get it. After installation, open the GOPATH directory. There should be compiled cobra in the bin directory. Of course, you can also use the source code to compile and install.
Before using cobra, you need to understand three concepts, which are also three parts of the command line: command, flag and args

  • Some basic information of the command itself is represented by command, and the specific object is Cobra Command
  • Some beautiful or options of the command are represented by flag. The specific object is flag FlagSet
  • The last parameter, represented by args, is usually [] string

The corresponding examples are as follows:

go get -u test.com/a/b

Here, get is common (special here), - u is flag, test COM / A / B is args

3.2 generating applications

$ /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 design cls procedure

Create the imp directory under the smart cli directory and re write utils Go file, enter as follows

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()
}

It can be seen that the main function executes the cmd package, so we only need to call the utils package in the cmd package to meet the requirements of the smart cli program. Then open root Go file view:

  • 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())
	}
}


From the source code, the cmd package performs some initialization operations and provides the Execute interface. Very simple. viper is the library for reading cobra integrated configuration files. It does not need to be used here. We can comment it out (the application that cannot be annotated is about 10M. It is best to comment it out if it is not used here). All cobra commands are through cobra Command is implemented by this structure. In order to implement the smart cli function, it is obvious that we need to modify RootCmd. The modified code is as follows:

/*
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 execution

# compile
$ 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%                                              

Fourth, implement cli with subcommands

Executing Cobra After exe init demo, continue to use cobra to add the subcommand test for the demo:

4.1 generate sysinfo subcommand

$ /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 viewing subcommands

$ 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 writing subcommands

  • 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
	// Log in with user name and password
	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))
}
  • Perform test
$ ./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

V. others

Cobra is very powerful and can help us quickly create command-line tools. However, if we just write a very simple command-line tool directly, there are very few flag options, and the flag Library of golang build in is enough. Of course, depending on personal choice, cobra is more suitable for complex command-line tools.

golang / cloud native / Docker/DevOps/K8S / continuous integration / distributed / etcd/ipfs learning group: 960994558

Topics: Go