Skip to content

Commit d717df0

Browse files
Custom env var names (#12)
* Support of envvar tag * Update documentation
1 parent ee6592f commit d717df0

File tree

3 files changed

+54
-26
lines changed

3 files changed

+54
-26
lines changed

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ type Config struct {
2929

3030
type Database struct {
3131
Host string `default:"localhost" validate:"required"`
32-
Password string `validate:"required"`
32+
Password string `validate:"required" envvar:"DB_PASS"`
3333
DbName string `default:"mydb"`
3434
Username string `default:"root"`
3535
Port int `default:"5432"`
@@ -46,7 +46,7 @@ func main() {
4646
}
4747

4848
```
49-
When you want to change, for example, DB Host of your applications you can do any of the following:
49+
When you want to change, a DB Host of your applications you can do it in 3 ways:
5050
1. create config `myconf.yaml` file in home directory
5151
```
5252
db:
@@ -101,6 +101,12 @@ To set a flag via environment variable, make all letters uppercase and replace '
101101
102102
You can set a prefix for environment variables. For example `NewConfReader("myconf").WithPrefix("MYAPP")` will search for environment variables like `MYAPP_DB_HOST`
103103

104+
Environment variable names could be set in the struct tag `envvar`. For example
105+
```
106+
Password string `envvar:"DB_PASS"`
107+
```
108+
will use value from environment variable `DB_PASS` to configure `Password` field.
109+
104110
### Command Line Arguments :computer:
105111
106112
To set a configuration field via command line argument you need to pass and argument prefixes wiht `--` and lowercase field name with path. Like `--db.host=localhost`

config.go

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,32 @@ import (
1515
"github.com/spf13/viper"
1616
)
1717

18-
/* ConfReader reads configuration from config file, environment variables or command line flags.
19-
Flags have precedence over env vars and env vars have precedence over config file.
20-
18+
/*
19+
ConfReader reads configuration from config file, environment variables or command line flags.
20+
Flags have precedence over env vars and env vars have precedence over config file.
2121
2222
For example:
23-
type NestedConf struct {
24-
Foo string
25-
}
2623
27-
type Config struct{
28-
Nested NestedConf
29-
}
24+
type NestedConf struct {
25+
Foo string
26+
}
27+
28+
type Config struct{
29+
Nested NestedConf
30+
}
3031
3132
in that case flag --nested.foo will be mapped automatically
3233
This flag could be also set by NESTED_FOO env var or by creating config file .ukama.yaml:
3334
nested:
34-
foo: bar
35+
36+
foo: bar
3537
*/
3638
type ConfReader struct {
3739
viper *enviper.Enviper
3840
configName string
3941
configDirs []string
4042
envVarPrefix string
43+
Verbose bool
4144
}
4245

4346
// NewConfReader creates new instance of ConfReader
@@ -115,12 +118,12 @@ func (c *ConfReader) Read(configStruct interface{}) error {
115118

116119
func (c *ConfReader) flagsBinding(conf interface{}) error {
117120
t := reflect.TypeOf(conf)
118-
m := map[string]*flagInfo{}
119-
c.dumpStruct(t, "", m)
121+
tagsInfo := map[string]*flagInfo{}
122+
tagsInfo = c.dumpStruct(t, "", tagsInfo)
120123

121124
var flags = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError)
122125

123-
for _, v := range m {
126+
for _, v := range tagsInfo {
124127
switch v.Type.Kind() {
125128
case reflect.String:
126129
flags.String(v.Name, v.DefaultVal, "")
@@ -175,13 +178,20 @@ func (c *ConfReader) flagsBinding(conf interface{}) error {
175178
flags.BytesBase64(v.Name, []byte{}, "byte array in base64")
176179
}
177180
}
181+
182+
if v.EnvVar != "" {
183+
err := c.viper.BindEnv(v.Name, v.EnvVar)
184+
if err != nil {
185+
return err
186+
}
187+
}
178188
}
179189

180190
err := flags.Parse(os.Args[1:])
181191
if err != nil {
182192
return errors.Wrap(err, "failed to parse flags")
183193
}
184-
for k, v := range m {
194+
for k, v := range tagsInfo {
185195
f := flags.Lookup(v.Name)
186196
if f != nil && f.Changed {
187197
if v.Type.Kind() == reflect.Slice {
@@ -210,14 +220,17 @@ type flagInfo struct {
210220
Name string
211221
Type reflect.Type
212222
DefaultVal string
223+
EnvVar string
213224
}
214225

215-
func (c *ConfReader) dumpStruct(t reflect.Type, path string, res map[string]*flagInfo) {
216-
fmt.Printf("%s: %s", path, t.Name())
226+
func (c *ConfReader) dumpStruct(t reflect.Type, path string, res map[string]*flagInfo) map[string]*flagInfo {
227+
if c.Verbose {
228+
fmt.Printf("%s: %s", path, t.Name())
229+
}
217230
switch t.Kind() {
218231
case reflect.Ptr:
219232
originalValue := t.Elem()
220-
c.dumpStruct(originalValue, path, res)
233+
res = c.dumpStruct(originalValue, path, res)
221234

222235
// If it is a struct we translate each field
223236
case reflect.Struct:
@@ -228,36 +241,40 @@ func (c *ConfReader) dumpStruct(t reflect.Type, path string, res map[string]*fla
228241
f.Type.Kind() != reflect.Func && f.Type.Kind() != reflect.Interface && f.Type.Kind() != reflect.UnsafePointer {
229242

230243
// do we have flag name override ?
231-
val := f.Tag.Get("flag")
244+
flagVal := f.Tag.Get("flag")
245+
envVar := f.Tag.Get("envvar")
246+
232247
fieldPath := strings.TrimPrefix(strings.ToLower(path+"."+f.Name), ".")
233-
if val != "" {
248+
if flagVal != "" {
234249
res[fieldPath] = &flagInfo{
235-
Name: val,
250+
Name: flagVal,
236251
Type: f.Type,
237252
DefaultVal: f.Tag.Get("default"),
253+
EnvVar: envVar,
238254
}
239255
} else {
240256
res[fieldPath] = &flagInfo{
241257
Name: fieldPath,
242258
Type: f.Type,
243259
DefaultVal: f.Tag.Get("default"),
260+
EnvVar: envVar,
244261
}
245262
}
246263

247264
} else if f.Type.Kind() == reflect.Struct || f.Type.Kind() == reflect.Ptr {
248265
val := f.Tag.Get("mapstructure")
249266
if strings.Contains(val, "squash") {
250-
c.dumpStruct(f.Type, path, res)
267+
res = c.dumpStruct(f.Type, path, res)
251268
} else {
252-
c.dumpStruct(f.Type, path+"."+f.Name, res)
269+
res = c.dumpStruct(f.Type, path+"."+f.Name, res)
253270
}
254271
}
255272
}
256273

257274
case reflect.Interface:
258275
// Skipping interface
259276
}
260-
277+
return res
261278
}
262279

263280
func (c *ConfReader) WithSearchDirs(s ...string) *ConfReader {

config_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type LocalConfig struct {
2828
FromConfig string
2929
OverriddenByEvnVar string
3030
OverriddenByArg string
31+
EnvVarName string `envvar:"CUSTOM_ENV_VAR"`
3132
}
3233

3334
func Test_ConfigReader(t *testing.T) {
@@ -48,6 +49,9 @@ func Test_ConfigReader(t *testing.T) {
4849
os.Setenv("APP_OVERRIDDENBYEVNVAR", overriddenVar)
4950
defer os.Unsetenv("APP_OVERRIDDENBYEVNVAR")
5051

52+
os.Setenv("CUSTOM_ENV_VAR", "valFromEnvVar")
53+
defer os.Unsetenv("CUSTOM_ENV_VAR")
54+
5155
// act
5256
err := confReader.Read(nc)
5357

@@ -58,6 +62,7 @@ func Test_ConfigReader(t *testing.T) {
5862
assert.Equal(t, "valFromConf", nc.App.FromConfig)
5963
assert.Equal(t, valFromVar, nc.App.FromEnvVar)
6064
assert.Equal(t, fromArgVal, nc.App.OverriddenByArg)
65+
assert.Equal(t, "valFromEnvVar", nc.App.EnvVarName)
6166
}
6267
}
6368

@@ -129,7 +134,7 @@ type dmSibling struct {
129134
FromEnvVar string
130135
}
131136

132-
func Test_DumpStruct(t *testing.T) {
137+
func Test_dumpStruct(t *testing.T) {
133138
m := map[string]*flagInfo{}
134139
c := &ConfReader{}
135140
c.dumpStruct(reflect.TypeOf(dmParent{}), "", m)

0 commit comments

Comments
 (0)