整合管理配置 - viper
- Viper是适用于Go应用程序的完整配置解决方案。它被设计用于在应用程序中工作,并且可以处理所有类型的配置需求和格式
- viper是一个配置管理的解决方案,它能够从 json,toml,ini,yaml,hcl,env 等多种格式文件中,读取配置内容,它还能从一些远程配置中心读取配置文件,如consul,etcd等;它还能够监听文件的内容变化
- 读取 json,toml,ini,yaml,hcl,env 等格式的文件内容
- 读取远程配置文件,如 consul,etcd 等和监控配置文件变化
- 读取命令行 flag 的值
- 从 buffer 中读取值
- viper 读取配置文件的优先顺序,从高到低,如下:
- 显式设置的Set函数
- 命令行参数
- 环境变量
- 配置文件
- 远程k-v 存储系统,如consul,etcd等
- 默认值
- Viper 配置key是不区分大小写的
Famous parsing library
Get Viper
go get github.com/spf13/viper
Use viper
使用viper.Set
viper.Set("db.info", "this is db info")
优先级最高
建立默认值
viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
读取配置文件
- Viper需要最少知道在哪里查找配置文件的配置。Viper支持JSON、TOML、YAML、HCL、envfile和Java properties格式的配置文件。Viper可以搜索多个路径,但目前单个Viper实例只支持单个配置文件。Viper不默认任何配置搜索路径,将默认决策留给应用程序
viper.SetConfigFile("./config.yaml") 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("Fatal error config file: %s \n", err)) }
|
- 存在多个config 文件按照
config.json
- > config.toml
- >config.yaml
- >config.yml
- > config.properties
- > config.props
顺序
- 可以处理一些特殊情况
if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { } else { } }
|
写入配置文件
- WriteConfig - 将当前的viper配置写入预定义的路径并覆盖(如果存在的话)。如果没有预定义的路径,则报错。
- SafeWriteConfig - 将当前的viper配置写入预定义的路径。如果没有预定义的路径,则报错。如果存在,将不会覆盖当前的配置文件。
- WriteConfigAs - 将当前的viper配置写入给定的文件路径。将覆盖给定的文件(如果它存在的话)。
- SafeWriteConfigAs - 将当前的viper配置写入给定的文件路径。不会覆盖给定的文件(如果它存在的话)。
- 根据经验,标记为safe的所有方法都不会覆盖任何文件,而是直接创建(如果不存在),而默认行为是创建或截断
viper.WriteConfig() viper.SafeWriteConfig() viper.WriteConfigAs("/path/to/my/.config") viper.SafeWriteConfigAs("/path/to/my/.config") viper.SafeWriteConfigAs("/path/to/my/.other_config")
|
监控并重新读取配置文件
- Viper可以实现读取配置文件
- 确保在调用WatchConfig()之前添加了所有的配置路径
viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { fmt.Println("Config file changed:", e.Name) })
|
从io.Reader读取配置
viper.SetConfigType("yaml")
var yamlExample = []byte(` Hacker: true name: steve hobbies: - skateboarding - snowboarding - go clothing: jacket: leather trousers: denim age: 35 eyes : brown beard: true `)
viper.ReadConfig(bytes.NewBuffer(yamlExample))
viper.Get("name")
|
覆盖设置
viper.Set("Verbose", true) viper.Set("LogFile", LogFile)
|
注册和使用别名
viper.RegisterAlias("first", "sec")
viper.Set("first", true) viper.Set("sec", true)
viper.GetBool("first") viper.GetBool("sec")
|
使用环境变量
- 使用ENV变量时,务必要意识到Viper将ENV变量视为区分大小写
SetEnvPrefix("spf") BindEnv("id")
os.Setenv("SPF_ID", "13")
id := Get("id")
|
使用Flags
- Viper 具有绑定到标志的能力。具体来说,Viper支持Cobra库中使用的Pflag。
- 与BindEnv类似,该值不是在调用绑定方法时设置的,而是在访问该方法时设置的。这意味着你可以根据需要尽早进行绑定,即使在init()函数中也是如此。
- 对于单个标志,BindPFlag()方法提供此功能
serverCmd.Flags().Int("port", 1138, "Port to run Application server on") viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
pflag.Int("flagname", 1234, "help message for flagname")
pflag.Parse() viper.BindPFlags(pflag.CommandLine)
i := viper.GetInt("flagname")
|
远程Key/Value存储支持
- Viper将读取从Key/Value存储(例如etcd或Consul)中的路径检索到的配置字符串(如JSON、TOML、YAML、HCL、envfile和Java properties格式)。这些值的优先级高于默认值,但是会被从磁盘、flag或环境变量检索到的配置值覆盖。(译注:也就是说Viper加载配置值的优先级为:磁盘上的配置文件>命令行标志位>环境变量>远程Key/Value存储>默认值。)
- 需要导入
import _ "github.com/spf13/viper/remote"
远程Key/Value存储示例-未加密
etcd
viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json") viper.SetConfigType("json") err := viper.ReadRemoteConfig()
|
Consul
viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY") viper.SetConfigType("json") err := viper.ReadRemoteConfig()
fmt.Println(viper.Get("port")) fmt.Println(viper.Get("hostname"))
|
Firestore
viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document") viper.SetConfigType("json") err := viper.ReadRemoteConfig()
|
远程Key/Value存储示例-加密
viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg") viper.SetConfigType("json") err := viper.ReadRemoteConfig()
|
监控etcd中的更改-未加密
var runtime_viper = viper.New()
runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml") runtime_viper.SetConfigType("yaml")
err := runtime_viper.ReadRemoteConfig()
runtime_viper.Unmarshal(&runtime_conf)
go func(){ for { time.Sleep(time.Second * 5)
err := runtime_viper.WatchRemoteConfig() if err != nil { log.Errorf("unable to read remote config: %v", err) continue }
runtime_viper.Unmarshal(&runtime_conf) } }()
|
从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()方法
- 通过”.”获得嵌套字段:GetString(“datastore.metric.host”) // (返回 “127.0.0.1”)
提取子树
配置项:
app: cache1: max-items: 100 item-size: 64 cache2: max-items: 200 item-size: 80
|
subv := viper.Sub("app.cache1")
“subv” 表示 :
max-items: 100 item-size: 64
|
反序列化
- Unmarshal(rawVal interface{}) : error
- UnmarshalKey(key string, rawVal interface{}) : error
- 需要修改本身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)
|
序列化成字符串
- 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) }
|
使用结构体变量保存配置信息
package main
import ( "fmt" "net/http"
"github.com/fsnotify/fsnotify"
"github.com/gin-gonic/gin" "github.com/spf13/viper" )
type Config struct { Port int `mapstructure:"port"` Version string `mapstructure:"version"` }
var Conf = new(Config)
func main() { viper.SetConfigFile("./conf/config.yaml") err := viper.ReadInConfig() if err != nil { panic(fmt.Errorf("Fatal error config file: %s \n", err)) } if err := viper.Unmarshal(Conf); err != nil { panic(fmt.Errorf("unmarshal conf failed, err:%s \n", err)) } viper.WatchConfig() viper.OnConfigChange(func(in fsnotify.Event) { fmt.Println("夭寿啦~配置文件被人修改啦...") if err := viper.Unmarshal(Conf); err != nil { panic(fmt.Errorf("unmarshal conf failed, err:%s \n", err)) } })
r := gin.Default() r.GET("/version", func(c *gin.Context) { c.String(http.StatusOK, Conf.Version) })
if err := r.Run(fmt.Sprintf(":%d", Conf.Port)); err != nil { panic(err) } }
|