Browse Source

fix crash when setting deprecated settings with environment (#2529) (#2550)

pull/2507/head
Alessandro Ros 3 years ago committed by GitHub
parent
commit
4dc6e338dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 30
      internal/conf/env/env.go
  2. 235
      internal/conf/env/env_test.go

30
internal/conf/env/env.go vendored

@ -28,8 +28,14 @@ func loadEnvInternal(env map[string]string, prefix string, prv reflect.Value) er
return loadEnvInternal(env, prefix, prv.Addr()) return loadEnvInternal(env, prefix, prv.Addr())
} }
rt := prv.Type().Elem()
if i, ok := prv.Interface().(Unmarshaler); ok { if i, ok := prv.Interface().(Unmarshaler); ok {
if ev, ok := env[prefix]; ok { if ev, ok := env[prefix]; ok {
if prv.IsNil() {
prv.Set(reflect.New(rt))
i = prv.Interface().(Unmarshaler)
}
err := i.UnmarshalEnv(prefix, ev) err := i.UnmarshalEnv(prefix, ev)
if err != nil { if err != nil {
return fmt.Errorf("%s: %s", prefix, err) return fmt.Errorf("%s: %s", prefix, err)
@ -43,8 +49,6 @@ func loadEnvInternal(env map[string]string, prefix string, prv reflect.Value) er
return nil return nil
} }
rt := prv.Type().Elem()
switch rt { switch rt {
case reflect.TypeOf(""): case reflect.TypeOf(""):
if ev, ok := env[prefix]; ok { if ev, ok := env[prefix]; ok {
@ -173,12 +177,12 @@ func loadEnvInternal(env map[string]string, prefix string, prv reflect.Value) er
switch { switch {
case rt.Elem() == reflect.TypeOf(""): case rt.Elem() == reflect.TypeOf(""):
if ev, ok := env[prefix]; ok { if ev, ok := env[prefix]; ok {
if prv.IsNil() {
prv.Set(reflect.New(rt))
}
if ev == "" { if ev == "" {
prv.Elem().Set(reflect.MakeSlice(prv.Elem().Type(), 0, 0)) prv.Elem().Set(reflect.MakeSlice(prv.Elem().Type(), 0, 0))
} else { } else {
if prv.IsNil() {
prv.Set(reflect.New(rt))
}
prv.Elem().Set(reflect.ValueOf(strings.Split(ev, ","))) prv.Elem().Set(reflect.ValueOf(strings.Split(ev, ",")))
} }
} }
@ -186,9 +190,6 @@ func loadEnvInternal(env map[string]string, prefix string, prv reflect.Value) er
case rt.Elem().Kind() == reflect.Struct: case rt.Elem().Kind() == reflect.Struct:
if ev, ok := env[prefix]; ok && ev == "" { // special case: empty list if ev, ok := env[prefix]; ok && ev == "" { // special case: empty list
if prv.IsNil() {
prv.Set(reflect.New(rt))
}
prv.Elem().Set(reflect.MakeSlice(prv.Elem().Type(), 0, 0)) prv.Elem().Set(reflect.MakeSlice(prv.Elem().Type(), 0, 0))
} else { } else {
for i := 0; ; i++ { for i := 0; ; i++ {
@ -213,13 +214,20 @@ func loadEnvInternal(env map[string]string, prefix string, prv reflect.Value) er
return fmt.Errorf("unsupported type: %v", rt) return fmt.Errorf("unsupported type: %v", rt)
} }
// Load loads the configuration from the environment. func loadWithEnv(env map[string]string, prefix string, v interface{}) error {
func Load(prefix string, v interface{}) error { return loadEnvInternal(env, prefix, reflect.ValueOf(v).Elem())
}
func envToMap() map[string]string {
env := make(map[string]string) env := make(map[string]string)
for _, kv := range os.Environ() { for _, kv := range os.Environ() {
tmp := strings.SplitN(kv, "=", 2) tmp := strings.SplitN(kv, "=", 2)
env[tmp[0]] = tmp[1] env[tmp[0]] = tmp[1]
} }
return env
}
return loadEnvInternal(env, prefix, reflect.ValueOf(v).Elem()) // Load loads the configuration from the environment.
func Load(prefix string, v interface{}) error {
return loadWithEnv(envToMap(), prefix, v)
} }

235
internal/conf/env/env_test.go vendored

@ -9,13 +9,28 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
type subStruct struct { func stringPtr(v string) *string {
MyParam int `json:"myParam"` return &v
} }
type mapEntry struct { func intPtr(v int) *int {
MyValue string `json:"myValue"` return &v
MyStruct subStruct `json:"myStruct"` }
func uint64Ptr(v uint64) *uint64 {
return &v
}
func boolPtr(v bool) *bool {
return &v
}
func float64Ptr(v float64) *float64 {
return &v
}
func durationPtr(v time.Duration) *time.Duration {
return &v
} }
type myDuration time.Duration type myDuration time.Duration
@ -40,106 +55,150 @@ func (d *myDuration) UnmarshalEnv(_ string, v string) error {
return d.UnmarshalJSON([]byte(`"` + v + `"`)) return d.UnmarshalJSON([]byte(`"` + v + `"`))
} }
type subStruct struct {
MyParam int `json:"myParam"`
}
type mapEntry struct {
MyValue string `json:"myValue"`
MyStruct subStruct `json:"myStruct"`
}
type mySubStruct struct { type mySubStruct struct {
URL string `json:"url"` URL string `json:"url"`
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
MyInt2 int `json:"myInt2"`
} }
type testStruct struct { type testStruct struct {
MyString string `json:"myString"` MyString string `json:"myString"`
MyInt int `json:"myInt"` MyStringOpt *string `json:"myStringOpt"`
MyFloat float64 `json:"myFloat"` MyInt int `json:"myInt"`
MyBool bool `json:"myBool"` MyIntOpt *int `json:"myIntOpt"`
MyDuration myDuration `json:"myDuration"` MyUint uint64 `json:"myUint"`
MyMap map[string]*mapEntry `json:"myMap"` MyUintOpt *uint64 `json:"myUintOpt"`
MySlice []string `json:"mySlice"` MyFloat float64 `json:"myFloat"`
MySliceEmpty []string `json:"mySliceEmpty"` MyFloatOpt *float64 `json:"myFloatOpt"`
MySliceSubStruct []mySubStruct `json:"mySliceSubStruct"` MyBool bool `json:"myBool"`
MySliceSubStructEmpty []mySubStruct `json:"mySliceSubStructEmpty"` MyBoolOpt *bool `json:"myBoolOpt"`
MyDuration myDuration `json:"myDuration"`
MyDurationOpt *myDuration `json:"myDurationOpt"`
MyDurationOptUnset *myDuration `json:"myDurationOptUnset"`
MyMap map[string]*mapEntry `json:"myMap"`
MySliceString []string `json:"mySliceString"`
MySliceStringEmpty []string `json:"mySliceStringEmpty"`
MySliceStringOpt *[]string `json:"mySliceStringOpt"`
MySliceStringOptUnset *[]string `json:"mySliceStringOptUnset"`
MySliceSubStruct []mySubStruct `json:"mySliceSubStruct"`
MySliceSubStructEmpty []mySubStruct `json:"mySliceSubStructEmpty"`
MySliceSubStructOpt *[]mySubStruct `json:"mySliceSubStructOpt"`
MySliceSubStructOptUnset *[]mySubStruct `json:"mySliceSubStructOptUnset"`
Unset *bool `json:"unset"`
} }
func TestLoad(t *testing.T) { func TestLoad(t *testing.T) {
os.Setenv("MYPREFIX_MYSTRING", "testcontent") env := map[string]string{
defer os.Unsetenv("MYPREFIX_MYSTRING") "MYPREFIX_MYSTRING": "testcontent",
"MYPREFIX_MYSTRINGOPT": "testcontent2",
os.Setenv("MYPREFIX_MYINT", "123") "MYPREFIX_MYINT": "123",
defer os.Unsetenv("MYPREFIX_MYINT") "MYPREFIX_MYINTOPT": "456",
"MYPREFIX_MYUINT": "8910",
os.Setenv("MYPREFIX_MYFLOAT", "15.2") "MYPREFIX_MYUINTOPT": "112313",
defer os.Unsetenv("MYPREFIX_MYFLOAT") "MYPREFIX_MYFLOAT": "15.2",
"MYPREFIX_MYFLOATOPT": "16.2",
os.Setenv("MYPREFIX_MYBOOL", "yes") "MYPREFIX_MYBOOL": "yes",
defer os.Unsetenv("MYPREFIX_MYBOOL") "MYPREFIX_MYBOOLOPT": "false",
"MYPREFIX_MYDURATION": "22s",
os.Setenv("MYPREFIX_MYDURATION", "22s") "MYPREFIX_MYDURATIONOPT": "30s",
defer os.Unsetenv("MYPREFIX_MYDURATION") "MYPREFIX_MYMAP_MYKEY": "",
"MYPREFIX_MYMAP_MYKEY2_MYVALUE": "asd",
os.Setenv("MYPREFIX_MYMAP_MYKEY", "") "MYPREFIX_MYMAP_MYKEY2_MYSTRUCT_MYPARAM": "456",
defer os.Unsetenv("MYPREFIX_MYMAP_MYKEY") "MYPREFIX_MYSLICESTRING": "val1,val2",
"MYPREFIX_MYSLICESTRINGEMPTY": "",
os.Setenv("MYPREFIX_MYMAP_MYKEY2_MYVALUE", "asd") "MYPREFIX_MYSLICESTRINGOPT": "aa",
defer os.Unsetenv("MYPREFIX_MYMAP_MYKEY2_MYVALUE") "MYPREFIX_MYSLICESUBSTRUCT_0_URL": "url1",
"MYPREFIX_MYSLICESUBSTRUCT_0_USERNAME": "user1",
os.Setenv("MYPREFIX_MYMAP_MYKEY2_MYSTRUCT_MYPARAM", "456") "MYPREFIX_MYSLICESUBSTRUCT_0_PASSWORD": "pass1",
defer os.Unsetenv("MYPREFIX_MYMAP_MYKEY2_MYSTRUCT_MYPARAM") "MYPREFIX_MYSLICESUBSTRUCT_1_URL": "url2",
"MYPREFIX_MYSLICESUBSTRUCT_1_PASSWORD": "pass2",
os.Setenv("MYPREFIX_MYSLICE", "val1,val2") "MYPREFIX_MYSLICESUBSTRUCTEMPTY": "",
defer os.Unsetenv("MYPREFIX_MYSLICE") "MYPREFIX_MYSLICESUBSTRUCTOPT_1_PASSWORD": "pwd",
}
os.Setenv("MYPREFIX_MYSLICEEMPTY", "")
defer os.Unsetenv("MYPREFIX_MYSLICEEMPTY")
os.Setenv("MYPREFIX_MYSLICESUBSTRUCT_0_URL", "url1")
defer os.Unsetenv("MYPREFIX_MYSLICESUBSTRUCT_0_URL")
os.Setenv("MYPREFIX_MYSLICESUBSTRUCT_0_USERNAME", "user1")
defer os.Unsetenv("MYPREFIX_MYSLICESUBSTRUCT_0_USERNAME")
os.Setenv("MYPREFIX_MYSLICESUBSTRUCT_0_PASSWORD", "pass1")
defer os.Unsetenv("MYPREFIX_MYSLICESUBSTRUCT_0_PASSWORD")
os.Setenv("MYPREFIX_MYSLICESUBSTRUCT_1_URL", "url2")
defer os.Unsetenv("MYPREFIX_MYSLICESUBSTRUCT_1_URL")
os.Setenv("MYPREFIX_MYSLICESUBSTRUCT_1_PASSWORD", "pass2")
defer os.Unsetenv("MYPREFIX_MYSLICESUBSTRUCT_1_PASSWORD")
os.Setenv("MYPREFIX_MYSLICESUBSTRUCTEMPTY", "") for key, val := range env {
defer os.Unsetenv("MYPREFIX_MYSLICESUBSTRUCTEMPTY") os.Setenv(key, val)
defer os.Unsetenv(key)
}
var s testStruct var s testStruct
err := Load("MYPREFIX", &s) err := Load("MYPREFIX", &s)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, "testcontent", s.MyString) require.Equal(t, testStruct{
require.Equal(t, 123, s.MyInt) MyString: "testcontent",
require.Equal(t, 15.2, s.MyFloat) MyStringOpt: stringPtr("testcontent2"),
require.Equal(t, true, s.MyBool) MyInt: 123,
require.Equal(t, 22*myDuration(time.Second), s.MyDuration) MyIntOpt: intPtr(456),
MyUint: 8910,
_, ok := s.MyMap["mykey"] MyUintOpt: uint64Ptr(112313),
require.Equal(t, true, ok) MyFloat: 15.2,
MyFloatOpt: float64Ptr(16.2),
v, ok := s.MyMap["mykey2"] MyBool: true,
require.Equal(t, true, ok) MyBoolOpt: boolPtr(false),
require.Equal(t, "asd", v.MyValue) MyDuration: 22000000000,
require.Equal(t, 456, v.MyStruct.MyParam) MyDurationOpt: (*myDuration)(durationPtr(30000000000)),
MyMap: map[string]*mapEntry{
require.Equal(t, []string{"val1", "val2"}, s.MySlice) "mykey": {
require.Equal(t, []string{}, s.MySliceEmpty) MyValue: "",
MyStruct: subStruct{
require.Equal(t, []mySubStruct{ MyParam: 0,
{ },
URL: "url1", },
Username: "user1", "mykey2": {
Password: "pass1", MyValue: "asd",
MyStruct: subStruct{
MyParam: 456,
},
},
}, },
{ MySliceString: []string{
URL: "url2", "val1",
Password: "pass2", "val2",
}, },
}, s.MySliceSubStruct) MySliceStringEmpty: []string{},
MySliceStringOpt: &[]string{"aa"},
MySliceSubStruct: []mySubStruct{
{
URL: "url1",
Username: "user1",
Password: "pass1",
},
{
URL: "url2",
Username: "",
Password: "pass2",
},
},
MySliceSubStructEmpty: []mySubStruct{},
}, s)
}
require.Equal(t, []mySubStruct{}, s.MySliceSubStructEmpty) func FuzzLoad(f *testing.F) {
f.Add("MYPREFIX_MYINT", "a")
f.Add("MYPREFIX_MYUINT", "a")
f.Add("MYPREFIX_MYFLOAT", "a")
f.Add("MYPREFIX_MYBOOL", "a")
f.Add("MYPREFIX_MYSLICESUBSTRUCT_0_MYINT2", "a")
f.Add("MYPREFIX_MYDURATION", "a")
f.Add("MYPREFIX_MYDURATION_A", "a")
f.Fuzz(func(t *testing.T, key string, val string) {
env := map[string]string{
key: val,
}
var s testStruct
loadWithEnv(env, "MYPREFIX", &s) //nolint:errcheck
})
} }

Loading…
Cancel
Save