2021-10-12
技术笔记
00
请注意,本文编写于 1033 天前,最后修改于 25 天前,其中某些信息可能已经过时。

本文分享如何使用golang的viper库从环境变量中读取参数配置

参考资料:
https://github.com/spf13/viper/issues/188#issuecomment-399884438

首先配置结构体每一项后面要加一个mapstructure的注释
然后通过viper.AutomaticEnv()启用
然而,当前版本的viper有问题,这样还无法读环境变量
需要写一个函数来将变量逐个绑定到配置中:

go
func bindEnvs(iface interface{}, parts ...string) { ifv := reflect.ValueOf(iface) ift := reflect.TypeOf(iface) for i := 0; i < ift.NumField(); i++ { v := ifv.Field(i) t := ift.Field(i) tv, ok := t.Tag.Lookup("mapstructure") if !ok { continue } switch v.Kind() { case reflect.Struct: bindEnvs(v.Interface(), append(parts, tv)...) default: err := viper.BindEnv(strings.Join(append(parts, tv), ".")) if err != nil { klog.Fatalf("viper bind env error: %+v", err) } } } }

通过viper.SetEnvPrefix("PREFIX")来为环境变量添加前缀
通过viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))来将下划线转换成点
设置了这两个函数之后,环境变量PREFIX_SERVER_IP将会被解析为server.ip,绑定到配置结构体的对应成员中。

如何在docker env读取多行文本:

有时候需要读取多行文本(比如kubeconfig或者ssh key),如果是在bash脚本里,可以直接把文本用引号包起来;然而如果是使用docker的env file方式,多行文本是不支持的,这时候需要先把文本内容base64转码成一行,存到环境变量,然后在代码里面读取出来再做base64解码。

完整例子:

go
package config import ( "os" "reflect" "strings" toml "github.com/pelletier/go-toml" jww "github.com/spf13/jwalterweatherman" "github.com/spf13/viper" "k8s.io/klog" ) type ServerConfig struct { Server struct { URL string `mapstructure:"URL"` //connection timeout in second Timeout int `mapstructure:"TIMEOUT"` Workspace string `mapstructure:"WORKSPACE"` } `mapstructure:"SERVER"` Gin struct { Mode string `mapstructure:"MODE"` } `mapstructure:"GIN"` Log struct { Level string `mapstructure:"LEVEL"` } `mapstructure:"LOG"` } var instance ServerConfig func bindEnvs(iface interface{}, parts ...string) { ifv := reflect.ValueOf(iface) ift := reflect.TypeOf(iface) for i := 0; i < ift.NumField(); i++ { v := ifv.Field(i) t := ift.Field(i) tv, ok := t.Tag.Lookup("mapstructure") if !ok { continue } switch v.Kind() { case reflect.Struct: bindEnvs(v.Interface(), append(parts, tv)...) default: err := viper.BindEnv(strings.Join(append(parts, tv), ".")) if err != nil { klog.Fatalf("viper bind env error: %+v", err) } } } } func fileExists(path string) (bool, error) { stat, err := os.Stat(path) if err == nil { return !stat.IsDir(), nil } if os.IsNotExist(err) { return false, nil } return false, err } func NewConfig(file string) error { exists, err := fileExists(file) if err != nil { klog.Errorf("stat %s error: %s", file, err.Error()) return err } if exists { t, err := toml.LoadFile(file) if err != nil { klog.Errorf("load server config: %s failed, error: %s", file, err.Error()) return err } err = t.Unmarshal(&instance) if err != nil { klog.Errorf("toml unmarshal failed, error: %s", err.Error()) return err } klog.V(6).Infof("server config: %+v", instance) return nil } //let viper print debug log jww.SetStdoutThreshold(jww.LevelTrace) klog.Infof("config file %s not found, will read config from envs", file) viper.SetEnvPrefix("MY_APP") viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) bindEnvs(instance) viper.AutomaticEnv() if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { } else { klog.Errorf("load server config: %s failed, error: %s", file, err.Error()) return err } } err = viper.Unmarshal(&instance) if err != nil { klog.Errorf("toml unmarshal failed, error: %s", err.Error()) return err } klog.V(6).Infof("server config: %+v", instance) return nil }

本文作者:renbear

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC 2.0 许可协议。转载请注明出处!