整合管理配置 - 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.AddConfigPath()”和“viper.SetConfigName”设置的预定义路径
    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") // 或者 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") // 这里会得到 "steve"

覆盖设置

viper.Set("Verbose", true)
viper.Set("LogFile", LogFile)

注册和使用别名

viper.RegisterAlias("first", "sec")  // 注册别名(此处loud和Verbose建立了别名)

viper.Set("first", true) // 结果与下一行相同
viper.Set("sec", true) // 结果与前一行相同

viper.GetBool("first") // true
viper.GetBool("sec") // true

使用环境变量

  • 使用ENV变量时,务必要意识到Viper将ENV变量视为区分大小写
    SetEnvPrefix("spf") // 将自动转为大写
    BindEnv("id")

    os.Setenv("SPF_ID", "13") // 通常是在应用程序之外完成的

    id := Get("id") // 13

使用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") // 从viper而不是从pflag检索值

远程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") // 因为在字节流中没有文件扩展名,所以这里需要设置下类型。支持的扩展名有 "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
err := viper.ReadRemoteConfig()

Consul

viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY")
viper.SetConfigType("json") // 需要显示设置成json
err := viper.ReadRemoteConfig()

fmt.Println(viper.Get("port")) // 8080
fmt.Println(viper.Get("hostname")) // liwenzhou.com

Firestore

viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document")
viper.SetConfigType("json") // 配置的格式: "json", "toml", "yaml", "yml"
err := viper.ReadRemoteConfig()

远程Key/Value存储示例-加密

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()

监控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一直监控远端的变更
go func(){
for {
time.Sleep(time.Second * 5) // 每次请求后延迟一下

// 目前只测试了etcd支持
err := runtime_viper.WatchRemoteConfig()
if err != nil {
log.Errorf("unable to read remote config: %v", err)
continue
}

// 将新配置反序列化到我们运行时的配置结构体中。你还可以借助channel实现一个通知系统更改的信号
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))
}
// 将读取的配置信息保存至全局变量Conf
if err := viper.Unmarshal(Conf); err != nil {
panic(fmt.Errorf("unmarshal conf failed, err:%s \n", err))
}
// 监控配置文件变化
viper.WatchConfig()
// 注意!!!配置文件发生变化后要同步到全局变量Conf
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()
// 访问/version的返回值会随配置文件的变化而变化
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)
}
}