Cobra でコマンドが動く流れを調べてみた

概要

cobraとは

cobra は go CLI のコマンドを作るpackage. コマンド引数を受け取って動作を変えることができる。

CLI 作成用packge は他にもある。cobraは代表的なpackageの一つで、k8sで使われている。

他との比較については調査対象外。

調査

cobra-cli を使えば、0から boilerplateを生成してくれるので、流れが追いやすい。

cobra-cli init

また、生成されたコードの中にはコメントでこれでもかというくらい説明がされているので、特に困ることはなさそう

// main.go
func main() {
    cmd.Execute()
}
// root.go

var rootCmd = &cobra.Command{
    Use:   "testCobra",
    Short: "A brief description of your application",
    Long: `<ommitted>`

// 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() {
    err := rootCmd.Execute()
    if err != nil {
        os.Exit(1)
    }
}

This is called by main.main(). と書いてあるが、main.goのコードをみてもそれがわかる。

コマンド追加

ためしにserve というコマンドを追加してみる

cobra-cli add serve
// serve.go  generated

// serveCmd represents the serve command
var serveCmd = &cobra.Command{
    Use:   "serve",
    Short: "A brief description of your command",
    Long: `omitted.`,
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("serve called")
    },
}

func init() {
    rootCmd.AddCommand(serveCmd)
}

serve.go からは root.goで定義されている rootCmd に対して cobra.Command タイプのinstanceを追加している。

Run に関数を渡しているので、後で c.Run(cmd, args) みたいによばれるのだろう、と予想して cobraの¥ソースコードを眺めると、それっぽいところが見つかった

https://github.com/spf13/cobra/blob/main/command.go#L876

func (c *Command) execute(a []string) (err error) {
        
        ....
        
    if c.RunE != nil {
        if err := c.RunE(c, argWoFlags); err != nil {
            return err
        }
    } else {
        c.Run(c, argWoFlags)
    }
        ...

これくらい分かれば迷いなく扱えるだろう

おわりに

ここはgoを初めて1月目によくわからなかった。

たまたま思い出したので調べてみた。さらっとわかってよかった。