在每個語言內一定都會有管理設定檔的相關套件,像是在 Node.js 的 dotenv 套件,而在 Go 語言內呢?相信大家一定都會推 Hugo 作者寫的 Viper,Viper 可以支援讀取 JSON, TOML, YAML, HCL 等格式的設定檔案,也可以讀取環境變數,另外也可以直接跟取遠端設定檔整合(像是 etcd 或 Consul),本篇會介紹如何使用 Viper。
情境需求
當專案是使用 Yaml 或 JSON 存放設定檔時,在不同的部署環境都需要不同的設定檔。這時候就需要設定 App 可以指定不同設定檔路徑,指令如下
這樣測試同事拿到執行檔時,就可以透過 -c
參數來讀取個人設定檔。有個問題,假設設定檔需要動態修改,每次測完就改動一次有點麻煩,所以 App 必須要支援環境變數,像是如下:
1
| $ APP_PORT=8088 app -c config.yaml
|
假如沒有帶入 -c
參數,App 要能讀取系統預設環境設定檔案,像是 ($HOME/.app/config.yaml
)。下面來教大家如何透過 Viper 做到上述環境。
建立預設檔案
在 Go 語言內可以先用變數方式將 Yaml 直接寫在程式碼內:
1
2
3
4
| var defaultConf = []byte(`
app:
port: 3000
`)
|
接著設定 Viper 讀取 Yaml
檔案型態。
1
| viper.SetConfigType("yaml")
|
讀取指定檔案
透過 Go 語言的 flag 套件可以輕易實作出命令列 -c
參數
1
| flag.StringVar(&configFile, "c", "", "Configuration file path.")
|
接著就可以直接讀取 Yaml 檔案
1
2
3
4
5
6
7
8
9
| if configFile != "" {
content, err := ioutil.ReadFile(confPath)
if err != nil {
return conf, err
}
viper.ReadConfig(bytes.NewBuffer(content))
}
|
可以看到透過 viper.ReadConfig
可以把 Yaml 內容丟進去,之後就可以透過 viper.GetInt("app.port")
來存取資料。
讀取動態目錄
Viper 有個功能就是可以直接幫忙找尋相關目錄內的設定檔案。先假設底下路徑是您希望 App 可以自動幫你讀取:
/etc/app/
(Linux 常用的 /etc/
目錄)$HOME/.app
(家目錄底下的 .app
目錄).
(執行當下目錄)
首先設定 Viper 要去找 config
開頭的設定檔案
1
| viper.SetConfigName("config")
|
上面設定好,就會直接找 config.yaml
檔案,如果設定 app
則是找 app.yaml
。接著指定設定檔所在目錄
1
2
3
| viper.AddConfigPath("/etc/app/")
viper.AddConfigPath("$HOME/.app")
viper.AddConfigPath(".")
|
最後透過 ReadInConfig
來自動搜尋並且讀取檔案。
1
2
3
| if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
|
從環境變數讀取
如果專案需要跑在容器環境,這樣此功能對部署來說非常重要,也就是我只需要將 Go 語言的執行檔包進去 Docker 就好,而不需要將 Yaml 設定檔一起包入,或是透過 Volume 方式掛起來。這樣至少減少了一個步驟。首先設定 Viper 自動讀取環境變數:
1
2
| // read in environment variables that match
viper.AutomaticEnv()
|
接著設定環境變數 Prefix,避免跟其他專案衝突
1
2
| // will be uppercased automatically
viper.SetEnvPrefix("test")
|
最後設定環境變數的分隔符號從 .
換成 _
1
| viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
以上面的例子來說,你可以透過 TEST_APP_PORT
來指定不同的 port
1
| $ TEST_APP_PORT=3001 app -c config.yaml
|
實作範例
我們可以把上面的說明整理成範例,讀取流程會如下
- 讀取實體路徑
- 讀取預設路徑
- 讀取預設設定
假如 App 讀取特定路徑設定檔 (-c
參數),那就不會執行 2, 3 步驟,步驟 1 省略的話,App 就會自動先找預設路徑,如果預設路徑找不到就會執行步驟 3。程式碼範例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| viper.SetConfigType("yaml")
viper.AutomaticEnv() // read in environment variables that match
viper.SetEnvPrefix("gorush") // will be uppercased automatically
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
if confPath != "" {
content, err := ioutil.ReadFile(confPath)
if err != nil {
return conf, err
}
viper.ReadConfig(bytes.NewBuffer(content))
} else {
// Search config in home directory with name ".gorush" (without extension).
viper.AddConfigPath("/etc/gorush/")
viper.AddConfigPath("$HOME/.gorush")
viper.AddConfigPath(".")
viper.SetConfigName("config")
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
} else {
// load default config
viper.ReadConfig(bytes.NewBuffer(defaultConf))
}
}
|
結論
我個人用 Viper 最大的原因就是可以透過環境變數修改 App 的預設參數,另外編譯 Docker 容器時也不需要將設定檔丟入。在 Kubernetes 架構內可以透過 config map 方式來動態改變 App 行為。如果要搭配命令列,可以使用 cobra 結合 Viper。
See also