本文分享如何使用golang的viper库从环境变量中读取参数配置
参考资料:
https://github.com/spf13/viper/issues/188#issuecomment-399884438
首先配置结构体每一项后面要加一个mapstructure
的注释
然后通过viper.AutomaticEnv()
启用
然而,当前版本的viper有问题,这样还无法读环境变量
需要写一个函数来将变量逐个绑定到配置中:
gofunc 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解码。
完整例子:
gopackage 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 许可协议。转载请注明出处!