Go viper .yaml值环境变量覆盖

sqxo8psd  于 2022-12-07  发布在  Go
关注(0)|答案(6)|浏览(404)

我试图在go应用程序中有一个application.yaml文件,其中包含我想用环境变量覆盖的${RMQ_HOST}值。
application.yaml中,我得到:

rmq:
  test:
    host: ${RMQ_HOST}
    port: ${RMQ_PORT}

在我的装载机里有:

log.Println("Loading config...")
viper.SetConfigName("application")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.AutomaticEnv()
err := viper.ReadInConfig()

我遇到的问题是${RMQ_HOST}不会被我在环境变量中设置的值替换,并尝试使用以下字符串连接到RabbitMQ
amqp://测试:在${RMQ主机}:${RMQ端口}/测试时进行测试
代替
amqp://测试:测试@测试:测试/测试

jvlzgdj9

jvlzgdj91#

Viper不能为键/值对中的值保留占位符,因此我使用以下代码片段解决了这个问题:

log.Println("Loading config...")
viper.SetConfigName("application")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
    panic("Couldn't load configuration, cannot start. Terminating. Error: " + err.Error())
}
log.Println("Config loaded successfully...")
log.Println("Getting environment variables...")
for _, k := range viper.AllKeys() {
    value := viper.GetString(k)
    if strings.HasPrefix(value, "${") && strings.HasSuffix(value, "}") {
        viper.Set(k, getEnvOrPanic(strings.TrimSuffix(strings.TrimPrefix(value,"${"), "}")))
    }
}

func getEnvOrPanic(env string) string {
    res := os.Getenv(env)
    if len(res) == 0 {
        panic("Mandatory env variable not found:" + env)
    }
    return res
}

这将覆盖集合中找到的所有占位符。

webghufk

webghufk2#

更新日期:

我用这个功能扩展了原生的yaml解析器,并在github上发布了它。

用法:

type Config struct {
    Port     string   `yaml:"port"`
    RabbitMQ RabbitMQ `yaml:"rabbitmq"`
}

type RabbitMQ struct {
    Host     string `yaml:"host"`
    Port     string `yaml:"port"`
    Username string `yaml:"username"`
    Password string `yaml:"password"`
    Vhost    string `yaml:"vhost"`
}

func main() {
    var config Config
    file, err := ioutil.ReadFile("application.yaml")
    if err != nil {
        panic(err)
    }
    yaml.Unmarshal(file, &config)
    spew.Dump(config)
}

下面是application.yaml的外观:

port: ${SERVER_PORT}
rabbitmq:
  host: ${RMQ_HOST}
  port: ${RMQ_PORT}
  username: ${RMQ_USERNAME}
  password: ${RMQ_PASSWORD}
  vhost: test
  • vhost* value将照常解析,而用“${”和“}”括起来的所有内容将替换为环境变量。
7qhs6swi

7qhs6swi3#

您可以在调用“Unmarshal”方法之前显式替换env变量。假设配置存储在“Config”变量中,则以下代码片段应该可以工作。

log.Println("Loading config...")
viper.SetConfigName("application")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err != nil {
    fmt.Fprintf("Error reading config file %s\n", err)
}

for _, k := range viper.AllKeys() {
    v := viper.GetString(k)
    viper.Set(k, os.ExpandEnv(v))
}

if err := viper.Unmarshal(&Config); err != nil {
    fmt.Fprintf("Unable to decode into struct %s\n", err)
}
bq3bfh9z

bq3bfh9z4#

我首先用regexp代替ENV解决了类似的问题,下面是我的解决方案:

# config.yaml
DB_URI: ${DB_USER}

和主星,开始:

package main

import (
    "fmt"
    "os"
    "regexp"

    "github.com/spf13/viper"
)

type DBCfg struct {
    DBURI string `mapstructure:"DB_URI"`
}

func main() {
    viper.SetConfigName("config")
    viper.SetConfigType("yaml")
    viper.AddConfigPath(".")
    viper.AutomaticEnv()

    if err := viper.ReadInConfig(); err != nil {
        panic(fmt.Errorf("Failed to read config"))
    }

    for _, key := range viper.AllKeys() {
        value := viper.GetString(key)
        envOrRaw := replaceEnvInConfig([]byte(value))
        viper.Set(key, string(envOrRaw))
    }

    var config DBCfg
    if err := viper.Unmarshal(&config); err != nil {
        panic(fmt.Errorf("failed to load"))
    }

    fmt.Println(config)
}

func replaceEnvInConfig(body []byte) []byte {
    search := regexp.MustCompile(`\$\{([^{}]+)\}`)
    replacedBody := search.ReplaceAllFunc(body, func(b []byte) []byte {
        group1 := search.ReplaceAllString(string(b), `$1`)

        envValue := os.Getenv(group1)
        if len(envValue) > 0 {
            return []byte(envValue)
        }
        return []byte("")
    })

    return replacedBody
}

和我的输出:

>>> DB_USER=iamddjsaio go run main.go

{iamddjsaio}
dgiusagp

dgiusagp5#

我认为更好的方法是在将配置解码为结构体时使用Viper的 DecodeHooks 概念:
如果您有一个如下所示的 yaml 配置文件:

server:
  property: ${AN_ENV_VARIABLE}

然后在decode挂接内,在解码发生时获取ENV变量:

decodeHook := func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
    if f.Kind() == reflect.String {
        stringData := data.(string)
        if strings.HasPrefix(stringData, "${") && strings.HasSuffix(stringData, "}") {
            envVarValue := os.Getenv(strings.TrimPrefix(strings.TrimSuffix(stringData, "}"), "${"))
            if len(envVarValue) > 0 {
                return envVarValue, nil
            }
        }
    }
    return data, nil
}
err := viper.Unmarshal(c, viper.DecodeHook(decodeHook))

我认为这可以进一步改进,因为你可能有一个传入的属性,可能需要设置为一个int,例如。

tnkciper

tnkciper6#

可能不是直接的答案,但你试图解决的问题的解决方案。
很有可能你不需要替换。如果你把yml中的键保留为空,并打开viper.AutomaticEnv(),那么它会从环境变量中选取这些值。你还需要添加一个替换器来匹配键和环境变量名,比如:

viper.AutomaticEnv()
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.SetConfigFile("config.yml")
config := &Config{}

现在,当您取消marhsal配置时,您将从env中获得缺少的密钥。
例如:RABBITMQ_PASSWORD env变量将被用来设置rabbitmq.password,如果你的yml看起来像这样:

rabbitmq
  password: ""

相关问题