diff --git a/config.go b/config.go index 504841d..34bd932 100644 --- a/config.go +++ b/config.go @@ -1,14 +1,18 @@ package goconf import ( - "fmt" + "bytes" + "io/ioutil" + "log" "os" + "time" + "github.com/cenkalti/backoff/v4" + capi "github.com/hashicorp/consul/api" "github.com/joho/godotenv" "github.com/pkg/errors" "github.com/spf13/viper" _ "github.com/spf13/viper/remote" - "log" ) type Source string @@ -23,6 +27,11 @@ const ( EnvTypeKey = "GOCONF_TYPE" EnvFileNameKey = "GOCONF_FILENAME" EnvPrefixKey = "GOCONF_ENV_PREFIX" + EnvPath = "GOCONF_PATH" + + EnvHttpAddr = "CONSUL_HTTP_ADDR" + EnvHttpToken = "CONSUL_HTTP_TOKEN" + EnvHttpTokenFile = "CONSUL_HTTP_TOKEN_FILE" //configuration sources SourceEnv Source = "env" @@ -31,11 +40,12 @@ const ( ) var ( - typ = DefaultType - fname = DefaultFilename - prefix string + typ = DefaultType + fname = DefaultFilename + prefix, token, addr, path string c *viper.Viper + pair *capi.KVPair dirs = []string{ ".", "$HOME", @@ -60,12 +70,32 @@ func Configure() { if v := os.Getenv(EnvTypeKey); len(v) > 0 { typ = v } + if v := os.Getenv(EnvPath); len(v) > 0 { + path = v + } if v := os.Getenv(EnvFileNameKey); len(v) > 0 { - fname = v + fname = path + v } if v := os.Getenv(EnvPrefixKey); len(v) > 0 { prefix = v } + if v := os.Getenv(EnvPrefixKey); len(v) > 0 { + prefix = v + } + if v := os.Getenv(EnvHttpToken); len(v) > 0 { + token = v + } else if v := os.Getenv(EnvHttpTokenFile); len(v) > 0 { + fileContent, err := ioutil.ReadFile(v) + if err != nil { + log.Fatal(err) + } + token = string(fileContent) + } + if v := os.Getenv(EnvConsulHostKey); len(v) > 0 { + addr = v + } else if v := os.Getenv(EnvHttpAddr); len(v) > 0 { + addr = v + } // setup and configure viper instance c = viper.New() @@ -74,28 +104,58 @@ func Configure() { if len(prefix) > 0 { c.SetEnvPrefix(prefix) } + c.AutomaticEnv() // next we load from consul; only if consul host defined - if ch := os.Getenv(EnvConsulHostKey); ch != "" { - if err := c.AddRemoteProvider("consul", ch, fmt.Sprintf("/%s", fname)); err != nil { - errConsul = errors.Cause(err) + if addr != "" { + if token == "" { + if err := c.AddRemoteProvider("consul", addr, fname); err != nil { + errConsul = errors.Cause(err) + } } else { - if err := c.ReadRemoteConfig(); err != nil { + client, err := capi.NewClient(&capi.Config{ + Address: addr, + Token: token, + }) + if err != nil { errConsul = errors.Cause(err) } + + // Lookup the pair + pair, _, err = client.KV().Get(fname, nil) + if err != nil { + errConsul = errors.Cause(err) + } + } + + connect := func() error { + if token != "" { + return c.ReadConfig(bytes.NewBuffer(pair.Value)) + } else { + return c.ReadRemoteConfig() + } + } + notify := func(err error, t time.Duration) { log.Println("[goconf]", err.Error(), t) } + b := backoff.NewExponentialBackOff() + b.MaxElapsedTime = 2 * time.Minute + + err := backoff.RetryNotify(connect, b, notify) + if err != nil { + log.Printf("[goconf] giving up connecting to remote config ") + errConsul = errors.Cause(err) + } + + // last, we attempt to load from file in configured dir + for _, d := range dirs { + c.AddConfigPath(d) + } + if err := c.ReadInConfig(); err != nil { + errFile = errors.Cause(err) } } else { errConsul = errors.New("failed loading remote source; ENV not defined") } - - // last, we attempt to load from file in configured dir - for _, d := range dirs { - c.AddConfigPath(d) - } - if err := c.ReadInConfig(); err != nil { - errFile = errors.Cause(err) - } } func MustSource(s ...Source) { @@ -151,3 +211,7 @@ func GetInt(k string) int { func GetFloat64(k string) float64 { return c.GetFloat64(k) } + +func GetStringSlice(k string) []string { + return c.GetStringSlice(k) +}