go语言超好用配置库viper
带尖牙的Go配置!
许多Go项目都使用Viper构建,包括:
-
Hugo
-
EMC RexRay
-
Imgur的Incus
-
Nanobox
/
Nanopack -
Docker Notary
-
BloomApi
-
doctl
-
Clairctl
-
Mercure
-
Meshery
-
Bearer
-
Coder
-
Vitess
安装
1 | go get github.com/spf13/viper |
viper.SetDefault(“ContentDir”, “content”)viper.SetDefault(“LayoutDir”, “layouts”)viper.SetDefault(“Taxonomies”, map[string]string{“tag”: “tags”, “category”: “categories”})
1 | ### 读取配置文件 |
viper.SetConfigName(“config”) // 配置文件的名称(不带扩展名)viper.SetConfigType(“yaml”) // 如果配置文件的名称中没有扩展名,则此为必需项viper.AddConfigPath(“/etc/appname/”) // 查找配置文件的路径viper.AddConfigPath(“$HOME/.appname”) // 多次调用以添加多个搜索路径viper.AddConfigPath(“.”) // 可选地在工作目录中查找配置err := viper.ReadInConfig() // 查找并读取配置文件if err!= nil { // 处理读取配置文件时的错误 panic(fmt.Errorf(“致命错误配置文件: %w”, err))}
1 |
|
if err := viper.ReadInConfig(); err!= nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { // 配置文件未找到;如果需要,忽略错误 } else { // 配置文件已找到但产生了其他错误 }// 配置文件已找到并成功解析
1 |
|
viper.WriteConfig() // 将当前配置写入由’viper.AddConfigPath()'和’viper.SetConfigName’设置的预定义路径viper.SafeWriteConfig()viper.WriteConfigAs(“/path/to/my/.config”)viper.SafeWriteConfigAs(“/path/to/my/.config”) // 由于已经写入,将出错viper.SafeWriteConfigAs(“/path/to/my/.other_config”)
1 | ### 监视和重新读取配置文件 |
viper.OnConfigChange(func(e fsnotify.Event) { fmt.Println(“配置文件已更改:”, e.Name)})viper.WatchConfig()
1 | ### 从io.Reader读取配置 |
viper.SetConfigType(“yaml”) // 或viper.SetConfigType(“YAML”)// 任何将此配置引入程序的方法。var yamlExample = []byte(Hacker: truename: stevehobbies:- skateboarding- snowboarding- goclothing: jacket: leather trousers: denimage: 35eyes : brownbeard: true
)viper.ReadConfig(bytes.NewBuffer(yamlExample))viper.Get(“name”) // 这将是"steve"
1 | ### 设置覆盖值 |
viper.Set(“Verbose”, true)viper.Set(“LogFile”, LogFile)viper.Set(“host.port”, 5899) // 设置子集
1 | ### 注册和使用别名 |
viper.RegisterAlias(“loud”, “Verbose”)viper.Set(“verbose”, true) // 与下一行结果相同viper.Set(“loud”, true) // 与上一行结果相同viper.GetBool(“loud”) // trueviper.GetBool(“verbose”) // true
1 | ### 使用环境变量 |
SetEnvPrefix(“spf”) // 将自动大写BindEnv(“id”)os.Setenv(“SPF_ID”, “13”) // 通常在应用程序外部完成id := Get(“id”) // 13
1 | ### 使用标志 |
serverCmd.Flags().Int(“port”, 1138, “运行应用程序服务器的端口”)viper.BindPFlag(“port”, serverCmd.Flags().Lookup(“port”))
1 |
|
pflag.Int(“flagname”, 1234, “flagname的帮助消息”)pflag.Parse()viper.BindPFlags(pflag.CommandLine)i := viper.GetInt(“flagname”) // 从Viper而不是pflag中检索值
1 |
|
package mainimport (“flag”"github.com/spf13/pflag"func main() {// 使用标准库"flag"包 flag.Int(“flagname”, 1234, “flagname的帮助消息”) pflag.CommandLine.AddGoFlagSet(flag.CommandLine) pflag.Parse() viper.BindPFlags(pflag.CommandLine) i := viper.GetInt(“flagname”) // 从Viper中检索值//…}
1 | #### 标志接口 |
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” }
1 |
|
viper.BindFlagValue(“my-flag-name”, myFlag{})
1 |
|
type myFlagSet struct { flags []myFlag}func (f myFlagSet) VisitAll(fn func(FlagValue)) { for _, flag := range flags { fn(flag) }}
1 |
|
fSet := myFlagSet{ flags: []myFlag{myFlag{}, myFlag{}},}viper.BindFlagValues(“my-flags”, fSet)
1 | ### 远程键值存储支持 |
$ go get github.com/sagikazarmark/crypt/bin/crypt$ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json
1 |
|
$ crypt get -plaintext /config/hugo.json
1 |
|
viper.AddRemoteProvider(“etcd”, “http://127.0.0.1:4001”,“/config/hugo.json”)viper.SetConfigType(“json”) // 因为字节流中没有文件扩展名,支持的扩展名有 “json”、“toml”、“yaml”、“yml”、“properties”、“props”、“prop”、“env”、"dotenv"err := viper.ReadRemoteConfig()
1 | #### etcd3 |
viper.AddRemoteProvider(“etcd3”, “http://127.0.0.1:4001”,“/config/hugo.json”)viper.SetConfigType(“json”) // 因为字节流中没有文件扩展名,支持的扩展名有 “json”、“toml”、“yaml”、“yml”、“properties”、“props”、“prop”、“env”、"dotenv"err := viper.ReadRemoteConfig()
1 | #### Consul |
{ “port”: 8080, “hostname”: “myhostname.com”}
1 | ``` |
viper.AddRemoteProvider(“firestore”, “google-cloud-project-id”, “collection/document”)viper.SetConfigType(“json”) // 配置格式:“json”、“toml”、“yaml”、"yml"err := viper.ReadRemoteConfig()
1 |
|
viper.AddRemoteProvider(“nats”, “nats://127.0.0.1:4222”, “myapp.config”)viper.SetConfigType(“json”)err := viper.ReadRemoteConfig()
1 | ### 远程键值存储示例 - 加密 |
viper.AddSecureRemoteProvider(“etcd”,“http://127.0.0.1:4001”,“/config/hugo.json”,“/etc/secrets/mykeyring.gpg”)viper.SetConfigType(“json”) // 因为字节流中没有文件扩展名,支持的扩展名有 “json”、“toml”、“yaml”、“yml”、“properties”、“props”、“prop”、“env”、"dotenv"err := viper.ReadRemoteConfig()
1 | ### 监控 etcd 中的更改 - 未加密 |
// 或者,你可以创建一个新的 viper 实例。var runtime_viper = viper.New()runtime_viper.AddRemoteProvider(“etcd”, “http://127.0.0.1:4001”, “/config/hugo.yml”)runtime_viper.SetConfigType(“yaml”) // 因为字节流中没有文件扩展名,支持的扩展名有 “json”、“toml”、“yaml”、“yml”、“properties”、“props”、“prop”、“env”、“dotenv”// 首次从远程配置读取。err := runtime_viper.ReadRemoteConfig()// 反序列化配置runtime_viper.Unmarshal(&runtime_conf)// 打开一个 goroutine 以永远监控远程更改gofunc(){for { time.Sleep(time.Second * 5) // 每次请求后延迟// 当前,仅在 etcd 支持下进行了测试 err := runtime_viper.WatchRemoteConfig()if err!= nil { log.Errorf(“无法读取远程配置:%v”, err) continue }// 将新配置反序列化到我们的运行时配置结构体中。你也可以使用通道// 来实现一个信号,以通知系统更改 runtime_viper.Unmarshal(&runtime_conf) }}()
1 | ### 从Viper中获取值 |
viper.GetString(“logfile”) // 不区分大小写的设置和获取if viper.GetBool(“verbose”) { fmt.Println(“verbose enabled”)}
1 | ### 访问嵌套键 |
{ “host”: { “address”: “localhost”, “port”: 5799 }, “datastore”: { “metric”: { “host”: “127.0.0.1”, “port”: 3099 }, “warehouse”: { “host”: “198.0.0.1”, “port”: 2112 } }}
1 |
|
GetString(“datastore.metric.host”) // (返回"127.0.0.1")
1 |
|
{ “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”) // 返回6029
1 |
|
{ “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”) // 返回"0.0.0.0"
1 | ### 提取子树 |
cache: cache1: max-items: 100 item-size: 64 cache2: max-items: 200 item-size: 80
1 |
|
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)
1 |
|
func NewCache(v *Viper) *Cache { return &Cache{ MaxItems: v.GetInt(“max-items”), ItemSize: v.GetInt(“item-size”), }}
1 |
|
type config struct { Port int Name string PathMap string mapstructure:"path_map"
}var C configerr := viper.Unmarshal(&C)if err != nil { t.Fatalf(“unable to decode into struct, %v”, err)}
1 |
|
v := viper.NewWithOptions(viper.KeyDelimiter(“::”))v.SetDefault(“chart::values”, map[string]any{“ingress”: map[string]any{“annotations”: map[string]any{ “traefik.frontend.rule.type”: “PathPrefix”, “traefik.ingress.kubernetes.io/ssl-redirect”: “true”, }, },})type config struct { Chart struct{ Values map[string]any }}var C configv.Unmarshal(&C)
1 |
|
/Example config:module: enabled: true token: 89h3f98hbwf987h3f98wenf89ehf/type config struct { Module struct { Enabled bool moduleConfig mapstructure:",squash"
}}// moduleConfig could be in a module specific packagetype moduleConfig struct { Token string}var C configerr := viper.Unmarshal(&C)if err != nil { t.Fatalf(“unable to decode into struct, %v”, err)}
1 |
|
import ( yaml “gopkg.in/yaml.v2”// …)func yamlStringSettings() string { c := viper.AllSettings() bs, err := yaml.Marshal©if err != nil { log.Fatalf(“unable to marshal config to YAML: %v”, err) }returnstring(bs)}
1 | ## Viper还是Vipers? |
x := viper.New()y := viper.New()x.SetDefault(“ContentDir”, “content”)y.SetDefault(“ContentDir”, “foobar”)//…
1 |
|
make test
1 |
|
make lint # 传递-j选项以并行运行它们
1 |
|
make fmt
## 许可证
该项目根据
MIT许可证
授权。
