本文分享如何使用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 许可协议。转载请注明出处!