diff --git a/accounts/pkg/command/root.go b/accounts/pkg/command/root.go index 0c0e3d850..5849a808b 100644 --- a/accounts/pkg/command/root.go +++ b/accounts/pkg/command/root.go @@ -68,7 +68,12 @@ func NewLogger(cfg *config.Config) log.Logger { // ParseConfig loads accounts configuration from known paths. func ParseConfig(c *cli.Context, cfg *config.Config) error { - return ociscfg.BindSourcesToStructs("accounts", cfg) + _, err := ociscfg.BindSourcesToStructs("accounts", cfg) + if err != nil { + return err + } + + return nil } // SutureService allows for the accounts command to be embedded and supervised by a suture supervisor tree. diff --git a/ocis-pkg/config/helpers.go b/ocis-pkg/config/helpers.go index 40e3e7471..660befed6 100644 --- a/ocis-pkg/config/helpers.go +++ b/ocis-pkg/config/helpers.go @@ -65,7 +65,7 @@ func sanitizeExtensions(set []string, ext []string, f func(a, b string) bool) [] // BindSourcesToStructs assigns any config value from a config file / env variable to struct `dst`. Its only purpose // is to solely modify `dst`, not dealing with the config structs; and do so in a thread safe manner. -func BindSourcesToStructs(extension string, dst interface{}) error { +func BindSourcesToStructs(extension string, dst interface{}) (*gofig.Config, error) { sources := DefaultConfigSources(extension, supportedExtensions) cnf := gofig.NewWithOptions("proxy", gofig.ParseEnv) cnf.AddDriver(gooyaml.Driver) @@ -73,8 +73,8 @@ func BindSourcesToStructs(extension string, dst interface{}) error { err := cnf.BindStruct("", &dst) if err != nil { - return err + return nil, err } - return nil + return cnf, nil } diff --git a/proxy/pkg/command/root.go b/proxy/pkg/command/root.go index 3603180d0..0eaa5260a 100644 --- a/proxy/pkg/command/root.go +++ b/proxy/pkg/command/root.go @@ -67,7 +67,19 @@ func NewLogger(cfg *config.Config) log.Logger { // ParseConfig loads proxy configuration from known paths. func ParseConfig(c *cli.Context, cfg *config.Config) error { - return ociscfg.BindSourcesToStructs("proxy", cfg) + conf, err := ociscfg.BindSourcesToStructs("proxy", cfg) + if err != nil { + return err + } + + // load all env variables relevant to the config in the current context. + conf.LoadOSEnv(config.GetEnv(), false) + + if err = config.UnmapEnv(conf, cfg); err != nil { + return err + } + + return nil } // SutureService allows for the proxy command to be embedded and supervised by a suture supervisor tree. diff --git a/proxy/pkg/config/env_mapping.go b/proxy/pkg/config/env_mapping.go new file mode 100644 index 000000000..77c999921 --- /dev/null +++ b/proxy/pkg/config/env_mapping.go @@ -0,0 +1,82 @@ +package config + +import ( + "fmt" + "reflect" + "strings" + + gofig "github.com/gookit/config/v2" +) + +var mappings = []struct { + gType string // expected type, used for decoding. It is the type expected from gookit. + envName string // name of the env var + tagName string // name of the tag to select the value from. Tag names are to be unique. +}{ + { + gType: "bool", + envName: "PROXY_ENABLE_BASIC_AUTH", + tagName: "enable_basic_auth", + }, +} + +// GetEnv fetches a list of known env variables for this extension. +func GetEnv() []string { + var r []string + for i := range mappings { + r = append(r, mappings[i].envName) + } + + return r +} + +func UnmapEnv(gooconf *gofig.Config, cfg *Config) error { + for i := range mappings { + switch mappings[i].gType { + case "bool": + v := gooconf.Bool(mappings[i].envName) + if err := setField(cfg, mappings[i].tagName, v); err != nil { + return err + } + case "string": + v := gooconf.String(mappings[i].envName) + if err := setField(cfg, mappings[i].tagName, v); err != nil { + return err + } + default: + return fmt.Errorf("invalid type for env var: `%v`", mappings[i].envName) + } + } + + return nil +} + +// setField allows us to set a value on a struct selecting by its `mapstructure` tag. +func setField(item interface{}, fieldName string, value interface{}) error { + v := reflect.ValueOf(item).Elem() + if !v.CanAddr() { + return fmt.Errorf("cannot assign to the item passed, item must be a pointer in order to assign") + } + fName := func(t reflect.StructTag) (string, error) { + if jt, ok := t.Lookup("mapstructure"); ok { + return strings.Split(jt, ",")[0], nil + } + return "", fmt.Errorf("tag %s provided does not define a json tag", fieldName) + } + + fieldNames := map[string]int{} + for i := 0; i < v.NumField(); i++ { + typeField := v.Type().Field(i) + tag := typeField.Tag + jName, _ := fName(tag) + fieldNames[jName] = i + } + + fieldNum, ok := fieldNames[fieldName] + if !ok { + return fmt.Errorf("field does not exist within the provided item") + } + fieldVal := v.Field(fieldNum) + fieldVal.Set(reflect.ValueOf(value)) + return nil +}