cobraとviperで設定ファイルの値をフラグの値で上書きする

Go言語でコマンドを作ろうとしたときに、オプションの指定に設定ファイルの読み込みと、オプションで読み込んだ値を上書きをしたかったのでcobraとviperでの実現の仕方を確認する。

ロングオプションを利用する

qiita.com

を参考に。

記事にあるように、 viper.BindPFlag() を呼び出さないと値がフラグの値で更新されない。

また、PersistentFlags().String()でデフォルト値を設定しても、configFileの指定と違い意味が無いので行わない。

package main

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

// 設定項目
type Config struct {
    ApplicationName string
    Debug bool
}

// 設定ファイル名
var configFile string

var config Config


func main() {
    rootCmd := &cobra.Command{
        Use: "app",
        Run: func(c *cobra.Command, args []string) {
            // セットされた値の取得
            fmt.Printf("configFile: %s\nconfig: %#v\n", configFile, config)
        },
    }

    // デフォルト値を設定する
    rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "default_config.toml", "config file name")

    rootCmd.PersistentFlags().String("name", "", "application name")

    viper.BindPFlag("ApplicationName", rootCmd.PersistentFlags().Lookup("name"))

    cobra.OnInitialize(func() {
        viper.SetConfigFile(configFile)
        viper.AutomaticEnv()

        if err := viper.ReadInConfig(); err != nil {
            fmt.Println("config file read error")
            fmt.Println(err)
            os.Exit(1)
        }

        if err := viper.Unmarshal(&config); err != nil {
            fmt.Println("config file Unmarshal error")
            fmt.Println(err)
            os.Exit(1)
        }
    })

    if err := rootCmd.Execute(); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    println(config.ApplicationName)
}

ショートオプションも追加

ヘルプオプションの実行結果

$ go run main.go  --help
Usage:
  app [flags]

Flags:
  -c, --config string   config file name (default "default_config.toml")
  -h, --help            help for app
  -n, --name string     application name

ショートオプションを使うには PersistentFlags().StringVarP() を利用する。

pflag/flag.go at master · spf13/pflag · GitHubにあるようにショートオプションには1文字のみ利用可能で、それ以上の文字を指定するとエラーになる。

package main

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

// 設定項目
type Config struct {
    ApplicationName string
    Debug bool
}

// 設定ファイル名
var configFile string

var config Config


func main() {
    rootCmd := &cobra.Command{
        Use: "app",
        Run: func(c *cobra.Command, args []string) {
            // セットされた値の取得
            fmt.Printf("configFile: %s\nconfig: %#v\n", configFile, config)
        },
    }

    // デフォルト値を設定する
    rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "default_config.toml", "config file name")

    rootCmd.PersistentFlags().StringVarP(&config.ApplicationName, "name", "n", "", "application name")

    viper.BindPFlag("ApplicationName", rootCmd.PersistentFlags().Lookup("name"))

    cobra.OnInitialize(func() {
        viper.SetConfigFile(configFile)
        viper.AutomaticEnv()

        if err := viper.ReadInConfig(); err != nil {
            fmt.Println("config file read error")
            fmt.Println(err)
            os.Exit(1)
        }

        if err := viper.Unmarshal(&config); err != nil {
            fmt.Println("config file Unmarshal error")
            fmt.Println(err)
            os.Exit(1)
        }
    })

    if err := rootCmd.Execute(); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    println(config.ApplicationName)

実行結果を見るとやりたかったことが実現出来ていることがわかる。

$ cat default_config.toml
ApplicationName = "DEFAULT_APP_TOML"
Debug = true

$ go run main.go 
configFile: default_config.toml
config: main.Config{ApplicationName:"DEFAULT_APP_TOML", Debug:true}
DEFAULT_APP_TOML

$ go run main.go  -n abc
configFile: default_config.toml
config: main.Config{ApplicationName:"abc", Debug:true}
abc