同僚のおすすめでコマンドラインツール作成にCobra
を使いました。ものすごく簡単につくれるのですが、それでも少しハマったところがあったので、コマンドラインツールを作るまでの流れを書いておきます。
前提
- go version go1.14.2 darwin/amd64
- 利用ライブラリ:https://github.com/spf13/cobra
- 本記事のサンプルコード:https://github.com/taisa831/sandbox-cobra
Index
雛形を作成する
雛形作成にはgenerator
を使います。go get
してcobra
コマンドを利用可能にします。
go get -u github.com/spf13/cobra/cobra
cobra
コマンドを実行すると以下のようなusage
が表示されます。
$ cobra 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: cobra [command] Available Commands: add Add a command to a Cobra Application help Help about any command init Initialize a Cobra Application Flags: -a, --author string author name for copyright attribution (default "YOUR NAME") --config string config file (default is $HOME/.cobra.yaml) -h, --help help for cobra -l, --license string name of license for the project --viper use Viper for configuration (default true) Use "cobra [command] --help" for more information about a command.
cobra init
コマンドを実行するとpkg-name
が必須だと怒られます。
$ cobra init Error: required flag(s) "pkg-name" not set Usage: cobra init [name] [flags] # --- 省略 ---
cobra init
に --pkg-name
を指定して雛形を作成します。--pkg-name
にはgithub.com
から始まるパッケージへのパスを指定します。ここではsandbox-cobra
という名前で作っていきます。
$ cobra init --pkg-name github.com/taisa831/sandbox-cobra sandbox-cobra
コマンドラインツールのテンプレートができました。
sandbox-cobra ├── LICENSE ├── cmd │ └── root.go └── main.go
コマンドを追加する
go mod
で初期設定しておきます。
$ cd sandbox-cobra $ go mod init $ go mod tidy
cobra add
コマンドでサンプルの実行用コマンド(hello)を作成します。
$ cobra add hello
hello.go
コマンドが新規で追加されました。
. ├── LICENSE ├── cmd │ ├── hello.go ← 新しくできたコマンド │ └── root.go ├── go.mod ├── go.sum └── main.go
この時点でコマンド実行するとhello called
が表示されます。これでコマンドの追加は完了です。hello.go
のRUN
に処理を書いていけばいいわけです。
$ go run main.go hello hello called
Run: func(cmd *cobra.Command, args []string) { // ここに処理を書く fmt.Println("hello called") },
コンフィグを利用する
続いてコンフィグを利用可能にします。コマンドラインツールのhelp
をみてみると、Global Flags
にdefault is $HOME/.sandbox-cobra.yaml
とあるのが分かります。デフォルトではこのコンフィグファイルが読み込み対象となります。
$ go run main.go hello --help # --- 省略 --- Usage: sandbox-cobra hello [flags] Flags: -h, --help help for hello Global Flags: --config string config file (default is $HOME/.sandbox-cobra.yaml)
$HOME/.sandbox-cobra.yaml
に例としてDB
用コンフィグを作成します。
$ vi $HOME/.sandbox-cobra.yaml DBConfig: SCHEMA: sandbox-cobra User: admin Password: password Host: localhost Port: 3306
続いてconfig/config.go
を作成します。構成は以下通りです。
. ├── LICENSE ├── cmd │ ├── hello.go │ └── root.go ├── config │ └── config.go ├── go.mod ├── go.sum └── main.go
config/config.go
にコンフィグのstruct
を記述します。
package config var Conf Config type Config struct { DBConfig dbConfig } type dbConfig struct { Schema string `env:"SCHEMA" envDefault:"sandbox-cobra"` User string `env:"USER" envDefault:"root"` Password string `env:"PASSWORD" envDefault:"password"` Host string `env:"HOST" envDefault:"localhost"` Port string `env:"PORT" envDefault:"3306"` }
ここまでできたら、root.go
のinitConfig()
にコンフィグの読み込み用コードを追記します。initConfig()
ではviper
でコンフィグの初期化コードがgenerator
で生成されるのでそれを利用します。
// initConfig reads in config file and ENV variables if set. func initConfig() { // --- 省略 --- if err := viper.Unmarshal(&config.Conf); err != nil { fmt.Println(err) os.Exit(1) } }
読み込みめるようになったかhello.go
にコンフィグを出力して確認してみます。
Run: func(cmd *cobra.Command, args []string) { fmt.Printf("configFile: %s\nconfig: %#v\n", cfgFile, config.Conf) fmt.Println("hello called") },
$HOME/.sandbox-cobra.yaml
に指定したコンフィグが無事読み込めました。
$ go run main.go hello Using config file: /Users/tt-dev/.sandbox-cobra.yaml configFile: config: config.Config{DBConfig:config.dbConfig{Schema:"sandbox-cobra", User:"admin", Password:"password", Host:"localhost", Port:"3306"}} hello called
ただ$HOME
ではなくプロジェクトルートにコンフィグを置きたいので、root.go
のinit()
の記述を変え、プロジェクトルートの`config.yaml`を読むように変更します。
func init() { cobra.OnInitialize(initConfig) // --- 省略--- //rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.sandbox-cobra.yaml)") rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "config.yaml", "load config file (default is config.yaml)") // --- 省略 --- }
default
がconfig.yaml
に変わりました。
$ go run main.go hello --help # --- 省略 --- Global Flags: --config string load config file (default "config.yaml")
先程作成した$HOME/.sandbox-cobra.yaml
は消しておきます。
rm -rf $HOME/.sandbox-cobra.yaml
ここまでの構成は以下の通りです。
. ├── LICENSE ├── cmd │ ├── hello.go │ └── root.go ├── config │ └── config.go ├── config.yaml ├── go.mod ├── go.sum └── main.go
あらためてhello
コマンドを実行してみると問題なくコンフィグが読めていることが分かります。
$ go run main.go hello Using config file: config.yaml configFile: config.yaml config: config.Config{DBConfig:config.dbConfig{Schema:"sandbox-cobra", User:"admin", Password:"password", Host:"localhost", Port:"3306"}} hello called
サブコマンドを追加する
続いてサブコマンドを追加します。サブコマンドもgenerator
でサンプルコードが生成されているのでそれを利用します。hello.go
のinit()
のサブコマンドのコメントアウトを外します。
func init() { rootCmd.AddCommand(helloCmd) // --- 省略 --- // and all subcommands, e.g.: helloCmd.PersistentFlags().String("foo", "", "A help for // --- 省略 --- }
あらためて--help
でUsage
を確認してみると、--foo
が追加されいるのが確認できます。
go run main.go hello --help # --- 省略 --- Usage: sandbox-cobra hello [flags] Flags: --foo string A help for foo -h, --help help for hello Global Flags: --config string load config file (default "config.yaml")
最後にhello.go
にサブコマンドの値を読み込む設定を追加したら完了です。
var helloCmd = &cobra.Command{ Use: "hello", Short: "A brief description of your command", Long: `省略`, Run: func(cmd *cobra.Command, args []string) { fmt.Printf("configFile: %s\nconfig: %#v\n", cfgFile, config.Conf) fmt.Println("hello called") // 読み込みコード追加 foo, err := cmd.PersistentFlags().GetString("foo") if err != nil { fmt.Println(err) os.Exit(1) } fmt.Println(foo) }, }
サブコマンドをつけて実行するとfoo
が出力されました。
$ go run main.go hello --foo=foo Using config file: config.yaml configFile: config.yaml config: config.Config{DBConfig:config.dbConfig{Schema:"sandbox-cobra", User:"admin", Password:"password", Host:"localhost", Port:"3306"}} hello called foo
まとめ
ここまでくればコマンドラインツールでやりたいことができるようになると思います。他にも様々なオプションがあるので使いこなしたいところです。Cobra
使いたてなので何かあればコメントもらえると嬉しいです。