【3分で作れる】Goのコマンドラインツール by Cobra


同僚のおすすめでコマンドラインツール作成に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.goRUNに処理を書いていけばいいわけです。

$ go run main.go hello
hello called
Run: func(cmd *cobra.Command, args []string) {
// ここに処理を書く
fmt.Println("hello called")
},

コンフィグを利用する

続いてコンフィグを利用可能にします。コマンドラインツールのhelpをみてみると、Global Flagsdefault 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.goinitConfig()にコンフィグの読み込み用コードを追記します。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.goinit()の記述を変え、プロジェクトルートの`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)")
// --- 省略 ---
}

defaultconfig.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.goinit()のサブコマンドのコメントアウトを外します。

func init() {
rootCmd.AddCommand(helloCmd)
// --- 省略 ---
// and all subcommands, e.g.:
helloCmd.PersistentFlags().String("foo", "", "A help for
// --- 省略 ---
}

あらためて--helpUsageを確認してみると、--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使いたてなので何かあればコメントもらえると嬉しいです。


カテゴリー: Go

関連記事