使用 Flags
Viper 能够绑定到 flags。具体来说,viper 支持 Cobra 库中使用的 Pflags。
与 BindEnv 一样,在调用绑定方法时,不会设置该值,而是在访问绑定方法时设置该值。这意味着您可以尽早绑定,即使在 init() 函数中。
对于单个 Flag,BindPFlag() 方法提供此功能。
例如:
serverCmd.Flags().Int("port", 1138, "Port to run Application server on") viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
您还可以绑定一组现有的 pflags(pflag.FlagSet):
例如:
pflag.Int("flagname", 1234, "help message for flagname") pflag.Parse() viper.BindPFlags(pflag.CommandLine) i := viper.GetInt("flagname") // retrieve values from viper instead of pflag
在 Viper 中使用 pflag 并不阻碍其他包中使用标准库中的 flag 包。pflag 包可以通过导入这些 flags 来处理为 flag 包定义的 flags。这是通过调用一个pflag 包提供的便利函数 AddGoFlagSet() 实现的。
例如:
package main import ( "flag" "github.com/spf13/pflag" ) func main() { // using standard library "flag" package flag.Int("flagname", 1234, "help message for flagname") pflag.CommandLine.AddGoFlagSet(flag.CommandLine) pflag.Parse() viper.BindPFlags(pflag.CommandLine) i := viper.GetInt("flagname") // retrieve value from viper ... }
flag 接口
如果您不使用 Pflags,Viper 提供两个 Go 接口来绑定其他 flag 系统。
FlagValue 表示单个 flag。这是一个说明如何实现此接口的非常简单的示例:
type myFlag struct {} func (f myFlag) HasChanged() bool { return false } func (f myFlag) Name() string { return "my-flag-name" } func (f myFlag) ValueString() string { return "my-flag-value" } func (f myFlag) ValueType() string { return "string" }
一旦您的 flag 实现此接口,您只需告诉 Viper 将其绑定:
viper.BindFlagValue("my-flag-name", myFlag{})
FlagValueSet 表示一组 flags。这是一个说明如何实现此接口的非常简单的示例:
type myFlagSet struct { flags []myFlag } func (f myFlagSet) VisitAll(fn func(FlagValue)) { for _, flag := range flags { fn(flag) } }
一旦您的 flag 集合实现此接口,您只需告诉 Viper 绑定它:
fSet := myFlagSet{ flags: []myFlag{myFlag{}, myFlag{}}, } viper.BindFlagValues("my-flags", fSet)
远程 Key/Value 存储支持
若要在 Viper 中启用远程支持,请对 viper/remote 包进行空白导入:
import _ "github.com/spf13/viper/remote"
Viper 将读取从 Key/Value 存储(例如 etcd 或 Consul )中的路径检索到的配置字符串(如JSON,TOML,YAML,HCL 或 envfile)。这些值优先级高于默认值,但会被从磁盘,命令行参数(flag)或环境变量检索的配置值覆盖。
Viper 使用 crypt 从 K / V 存储中检索配置,这意味着如果您具有正确的 gpg 密钥,您可以将配置值加密后存储,并可以自动将其解密。加密是可选的。
您可以将远程配置与本地配置结合使用,也可以独立使用。
crypt 有一个命令行帮助程序,您可以用来将配置放入 K / V 存储中。crypt 默认使用在 http://127.0.0.1:4001 上的 etcd。
$ go get github.com/bketelsen/crypt/bin/crypt $ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json
确认已设置值:
$ crypt get -plaintext /config/hugo.json
有关如何设置加密值或如何使用 Consul 的示例,请参见 crypt 文档。
https://github.com/bketelsen/crypt
远程 Key/Value 存储示例 - 未加密
etcd
viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json") viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" err := viper.ReadRemoteConfig()
Consul
您需要使用具有所需配置的 JSON 值设置 Consul 存储中的 key。例如,创建一个具有 JSON 值得 Consul key/value 存储的 key MY_CONSUL_KEY。
{ "port": 8080, "hostname": "myhostname.com" }
viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY") viper.SetConfigType("json") // Need to explicitly set this to json err := viper.ReadRemoteConfig() fmt.Println(viper.Get("port")) // 8080 fmt.Println(viper.Get("hostname")) // myhostname.com
Firestore
viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document") viper.SetConfigType("json") // Config's format: "json", "toml", "yaml", "yml" err := viper.ReadRemoteConfig()
当然,您也可以使用 SecureRemoteProvider
远程 Key/Value 存储示例 - 加密
viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg") viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" err := viper.ReadRemoteConfig()
监控 etcd 中的更改 - 未加密
// alternatively, you can create a new viper instance. var runtime_viper = viper.New() runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml") runtime_viper.SetConfigType("yaml") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" // read from remote config the first time. err := runtime_viper.ReadRemoteConfig() // unmarshal config runtime_viper.Unmarshal(&runtime_conf) // open a goroutine to watch remote changes forever go func(){ for { time.Sleep(time.Second * 5) // delay after each request // currently, only tested with etcd support err := runtime_viper.WatchRemoteConfig() if err != nil { log.Errorf("unable to read remote config: %v", err) continue } // unmarshal new config into our runtime config struct. you can also use channel // to implement a signal to notify the system of the changes runtime_viper.Unmarshal(&runtime_conf) } }()
04
怎么在 Viper 中获取配置项?
在 Viper 中,有几种根据值的类型获取值的方法。存在以下功能和方法:
- Get(key string) : interface{}
- GetBool(key string) : bool
- GetFloat64(key string) : float64
- GetInt(key string) : int
- GetIntSlice(key string) : []int
- GetString(key string) : string
- GetStringMap(key string) : map[string]interface{}
- GetStringMapString(key string) : map[string]string
- GetStringSlice(key string) : []string
- GetTime(key string) : time.Time
- GetDuration(key string) : time.Duration
- IsSet(key string) : bool
- AllSettings() : map[string]interface{}
认识到的一件重要事情是,每个 Get 函数如果找不到值,它将返回零值。为了检查给定键是否存在,提供了 IsSet() 方法。
例如:
viper.GetString("logfile") // case-insensitive Setting & Getting if viper.GetBool("verbose") { fmt.Println("verbose enabled") }
访问嵌套键
访问器方法还接受深度嵌套键的格式化路径。例如,如果加载了以下JSON文件:
{ "host": { "address": "localhost", "port": 5799 }, "datastore": { "metric": { "host": "127.0.0.1", "port": 3099 }, "warehouse": { "host": "198.0.0.1", "port": 2112 } } }
Viper 可以通过传递「.」分隔键的路径来访问嵌套字段:
GetString("datastore.metric.host") // (returns "127.0.0.1")
遵守上面建立的优先级规则;搜索路径将遍历其余配置注册表,直到找到为止。
例如,在给定此配置文件的情况下,datastore.metric.host 和 datastore.metric.port 均已定义(并且可以被覆盖)。如果另外在默认设置中定义了 datastore.metric.protocol,Viper 也会找到它。
但是,如果 datastore.metric 被直接赋值覆盖(通过 flag,环境变量,Set() 方法等),则 datastore.metric 的所有子键也都变为未定义状态,它们被较高的优先级配置遮蔽(shadowed)了。
Viper 可以使用路径中的数字访问数组索引。例如:
{ "host": { "address": "localhost", "ports": [ 5799, 6029 ] }, "datastore": { "metric": { "host": "127.0.0.1", "port": 3099 }, "warehouse": { "host": "198.0.0.1", "port": 2112 } } } GetInt("host.ports.1") // returns 6029
最后,如果存在与分隔的键路径匹配的键,则将返回其值。例如
{ "datastore.metric.host": "0.0.0.0", "host": { "address": "localhost", "port": 5799 }, "datastore": { "metric": { "host": "127.0.0.1", "port": 3099 }, "warehouse": { "host": "198.0.0.1", "port": 2112 } } } GetString("datastore.metric.host") // returns "0.0.0.0"
在开发可重用模块时,提取配置的子集并传递给模块通常很有用。这样,模块可以实例化一次,就获取到不同的配置。
例如,应用程序可能出于不同的目的使用多个不同的缓存存储:
cache: cache1: max-items: 100 item-size: 64 cache2: max-items: 200 item-size: 80
我们可以将缓存名称传递给模块(例如 NewCache("缓存1"),但访问配置键需要奇怪的串联,并且与全局配置的分离更少。
因此,与其这样做,我们不要将 Viper 实例传递给表示配置子集的构造函数:
cache1Config := viper.Sub("cache.cache1") if cache1Config == nil { // Sub returns nil if the key cannot be found panic("cache configuration not found") } cache1 := NewCache(cache1Config)
注意:始终检查 Sub 的返回值。如果找不到 Key,则返回 nil。
在内部,NewCache 函数可以直接处理 max-items 和 item-size 的键:
func NewCache(v *Viper) *Cache { return &Cache{ MaxItems: v.GetInt("max-items"), ItemSize: v.GetInt("item-size"), } }
生成的代码易于测试,因为它与主配置结构分离,并且更易于重用(出于同样的原因)。
反序列化
您还可以选择将所有值或特定值解析到 struct、map 和 etc。
有两种方法可以做到这一点:
- Unmarshal(rawVal interface{}) : error
- UnmarshalKey(key string, rawVal interface{}) : error
例如:
type config struct { Port int Name string PathMap string `mapstructure:"path_map"` } var C config err := viper.Unmarshal(&C) if err != nil { t.Fatalf("unable to decode into struct, %v", err) }
如果要解析 Key 本身包含「.」(默认键分隔符)的配置,必须更改分隔符:
v := viper.NewWithOptions(viper.KeyDelimiter("::")) v.SetDefault("chart::values", map[string]interface{}{ "ingress": map[string]interface{}{ "annotations": map[string]interface{}{ "traefik.frontend.rule.type": "PathPrefix", "traefik.ingress.kubernetes.io/ssl-redirect": "true", }, }, }) type config struct { Chart struct{ Values map[string]interface{} } } var C config v.Unmarshal(&C)
Viper 还支持解析到嵌入结构体:
/* Example config: module: enabled: true token: 89h3f98hbwf987h3f98wenf89ehf */ type config struct { Module struct { Enabled bool moduleConfig `mapstructure:",squash"` } } // moduleConfig could be in a module specific package type moduleConfig struct { Token string } var C config err := viper.Unmarshal(&C) if err != nil { t.Fatalf("unable to decode into struct, %v", err) }
Viper 在内部使用
github.com/mitchellh/mapstructure 解析值,默认情况下使用 mapstructure tag。
序列化为字符串
您可能需要将 viper 中保存的所有设置序列化到字符串中,而不是将它们写入文件。您可以将您最喜爱的格式的序列化程序与 AllSettings() 返回的配置一起使用。
import ( yaml "gopkg.in/yaml.v2" // ... ) func yamlStringSettings() string { c := viper.AllSettings() bs, err := yaml.Marshal(c) if err != nil { log.Fatalf("unable to marshal config to YAML: %v", err) } return string(bs) }
05
使用单个 Viper 实例,还是使用多个 Viper 实例?
Viper 可以开箱即用。无需配置或初始化,就可以使用 Viper。由于大多数应用程序都希望使用单个中央存储库进行配置,因此 viper 包提供了此功能。它类似于单例模式。
在上面的所有示例中,他们都以单例模式风格演示了使用 Viper 的使用方法。
使用多个 Viper 实例
您还可以创建许多不同的 Viper 实例,供应用程序使用。每个都有其独特的配置和值集。每个都可以从不同的配置文件、Key/Value 存储等读取。Viper 包支持的所有函数都镜像为 Viper 上的方法。
例如:
x := viper.New() y := viper.New() x.SetDefault("ContentDir", "content") y.SetDefault("ContentDir", "foobar") //...
当使用多个 Viper 时,由用户管理不同的 Viper。
06
使用 Viper 读取配置文件的模拟示例
模拟示例的项目目录:
. ├── configs │ └── config.yaml ├── go.mod ├── go.sum └── main.go
配置文件:
configs/config.yaml
Server: RunMode: debug HttpPort: 8080 ReadTimeout: 60 WriteTimeout: 60
使用 Viper 读取配置文件中的内容,并解码到 struct 中:
main.go
type ServerSetting struct { RunMode string HttpPort string ReadTimeout time.Duration WriteTimeout time.Duration } var server ServerSetting func main() { // 实例化 viper 实例 vp := viper.New() // 设置配置文件名称 vp.SetConfigName("config") // 设置配置文件类型 vp.SetConfigType("yaml") // 添加配置文件路径 vp.AddConfigPath("configs/") // 读取配置文件内容 err := vp.ReadInConfig() if err != nil { panic(fmt.Errorf("Fatal error config file: %s\n", err)) } // 解码 key 到 struct err = vp.UnmarshalKey("Server", &server) if err != nil { panic(fmt.Errorf("unable to decode into struct, %v", err)) } // 打印 fmt.Printf("Server: %+v\n", server) }
07
总结
本文是 Viper 开源库的 README 的中文翻译,文章内容介绍了什么是 Viper,Viper 包含哪些功能和 Viper 管理配置信息的不同方式的使用方法,以及不同方式之间的优先级顺序。翻译内容难免有不准确的地方,建议对照英文原稿阅读。文章结尾,还给出了一个使用 Viper 读取配置文件的模拟示例。截止发稿,Viper 的最新版本为 v1.7.1,并且作者目前正在收集使用反馈,为开发 v2.0 做准备。