2023-05-06 21:00:42 +00:00
|
|
|
// Package env contains a function to load configuration from environment.
|
|
|
|
package env
|
2020-10-13 21:36:27 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"reflect"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2023-10-07 21:32:15 +00:00
|
|
|
// Unmarshaler can be implemented to override the unmarshaling process.
|
|
|
|
type Unmarshaler interface {
|
|
|
|
UnmarshalEnv(prefix string, v string) error
|
2021-10-11 09:46:40 +00:00
|
|
|
}
|
|
|
|
|
2023-06-30 14:47:10 +00:00
|
|
|
func envHasAtLeastAKeyWithPrefix(env map[string]string, prefix string) bool {
|
|
|
|
for key := range env {
|
|
|
|
if strings.HasPrefix(key, prefix) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-10-07 21:32:15 +00:00
|
|
|
func loadEnvInternal(env map[string]string, prefix string, prv reflect.Value) error {
|
|
|
|
if prv.Kind() != reflect.Pointer {
|
|
|
|
return loadEnvInternal(env, prefix, prv.Addr())
|
|
|
|
}
|
2020-10-13 21:36:27 +00:00
|
|
|
|
2023-10-23 18:07:28 +00:00
|
|
|
rt := prv.Type().Elem()
|
|
|
|
|
2023-10-07 21:32:15 +00:00
|
|
|
if i, ok := prv.Interface().(Unmarshaler); ok {
|
2021-05-09 15:51:38 +00:00
|
|
|
if ev, ok := env[prefix]; ok {
|
2023-10-23 18:07:28 +00:00
|
|
|
if prv.IsNil() {
|
|
|
|
prv.Set(reflect.New(rt))
|
|
|
|
i = prv.Interface().(Unmarshaler)
|
|
|
|
}
|
2023-10-07 21:32:15 +00:00
|
|
|
err := i.UnmarshalEnv(prefix, ev)
|
|
|
|
if err != nil {
|
2024-01-03 20:13:20 +00:00
|
|
|
return fmt.Errorf("%s: %w", prefix, err)
|
2023-10-07 21:32:15 +00:00
|
|
|
}
|
|
|
|
} else if envHasAtLeastAKeyWithPrefix(env, prefix) {
|
|
|
|
err := i.UnmarshalEnv(prefix, "")
|
2020-10-13 21:36:27 +00:00
|
|
|
if err != nil {
|
2024-01-03 20:13:20 +00:00
|
|
|
return fmt.Errorf("%s: %w", prefix, err)
|
2020-10-13 21:36:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-09-27 08:36:28 +00:00
|
|
|
switch rt {
|
|
|
|
case reflect.TypeOf(""):
|
2021-05-09 15:51:38 +00:00
|
|
|
if ev, ok := env[prefix]; ok {
|
2023-10-07 21:32:15 +00:00
|
|
|
if prv.IsNil() {
|
|
|
|
prv.Set(reflect.New(rt))
|
|
|
|
}
|
|
|
|
prv.Elem().SetString(ev)
|
2020-10-13 21:36:27 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
|
2021-09-27 08:36:28 +00:00
|
|
|
case reflect.TypeOf(int(0)):
|
2021-05-09 15:51:38 +00:00
|
|
|
if ev, ok := env[prefix]; ok {
|
2023-10-07 21:32:15 +00:00
|
|
|
if prv.IsNil() {
|
|
|
|
prv.Set(reflect.New(rt))
|
|
|
|
}
|
2023-05-08 11:12:47 +00:00
|
|
|
iv, err := strconv.ParseInt(ev, 10, 32)
|
2020-10-13 21:36:27 +00:00
|
|
|
if err != nil {
|
2024-01-03 20:13:20 +00:00
|
|
|
return fmt.Errorf("%s: %w", prefix, err)
|
2020-10-13 21:36:27 +00:00
|
|
|
}
|
2023-10-07 21:32:15 +00:00
|
|
|
prv.Elem().SetInt(iv)
|
2020-10-13 21:36:27 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
|
2024-08-20 22:08:54 +00:00
|
|
|
case reflect.TypeOf(uint(0)):
|
2021-05-09 15:51:38 +00:00
|
|
|
if ev, ok := env[prefix]; ok {
|
2023-10-07 21:32:15 +00:00
|
|
|
if prv.IsNil() {
|
|
|
|
prv.Set(reflect.New(rt))
|
|
|
|
}
|
2023-05-08 11:12:47 +00:00
|
|
|
iv, err := strconv.ParseUint(ev, 10, 32)
|
2021-01-10 11:55:53 +00:00
|
|
|
if err != nil {
|
2024-01-03 20:13:20 +00:00
|
|
|
return fmt.Errorf("%s: %w", prefix, err)
|
2021-01-10 11:55:53 +00:00
|
|
|
}
|
2023-10-07 21:32:15 +00:00
|
|
|
prv.Elem().SetUint(iv)
|
2021-01-10 11:55:53 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
|
Add additional Raspberry Pi Camera parameters (#1198)
* rpicamera: add rpiCameraHFlip and rpiCameraVFlip parameters
* rpicamera: add rpiCameraBrightness, rpiCameraContrast,
rpiCameraSaturation, rpiCameraSharpness, rpiCameraExposure,
rpiCameraAWB, rpiCameraDenoise, rpiCameraShutter, rpiCameraMetering,
rpiCameraGain, rpiCameraEV, rpiCameraROI, rpiCameraTuningFile
* support float values in config file
2022-10-24 13:08:30 +00:00
|
|
|
case reflect.TypeOf(float64(0)):
|
|
|
|
if ev, ok := env[prefix]; ok {
|
2023-10-07 21:32:15 +00:00
|
|
|
if prv.IsNil() {
|
|
|
|
prv.Set(reflect.New(rt))
|
|
|
|
}
|
Add additional Raspberry Pi Camera parameters (#1198)
* rpicamera: add rpiCameraHFlip and rpiCameraVFlip parameters
* rpicamera: add rpiCameraBrightness, rpiCameraContrast,
rpiCameraSaturation, rpiCameraSharpness, rpiCameraExposure,
rpiCameraAWB, rpiCameraDenoise, rpiCameraShutter, rpiCameraMetering,
rpiCameraGain, rpiCameraEV, rpiCameraROI, rpiCameraTuningFile
* support float values in config file
2022-10-24 13:08:30 +00:00
|
|
|
iv, err := strconv.ParseFloat(ev, 64)
|
|
|
|
if err != nil {
|
2024-01-03 20:13:20 +00:00
|
|
|
return fmt.Errorf("%s: %w", prefix, err)
|
Add additional Raspberry Pi Camera parameters (#1198)
* rpicamera: add rpiCameraHFlip and rpiCameraVFlip parameters
* rpicamera: add rpiCameraBrightness, rpiCameraContrast,
rpiCameraSaturation, rpiCameraSharpness, rpiCameraExposure,
rpiCameraAWB, rpiCameraDenoise, rpiCameraShutter, rpiCameraMetering,
rpiCameraGain, rpiCameraEV, rpiCameraROI, rpiCameraTuningFile
* support float values in config file
2022-10-24 13:08:30 +00:00
|
|
|
}
|
2023-10-07 21:32:15 +00:00
|
|
|
prv.Elem().SetFloat(iv)
|
Add additional Raspberry Pi Camera parameters (#1198)
* rpicamera: add rpiCameraHFlip and rpiCameraVFlip parameters
* rpicamera: add rpiCameraBrightness, rpiCameraContrast,
rpiCameraSaturation, rpiCameraSharpness, rpiCameraExposure,
rpiCameraAWB, rpiCameraDenoise, rpiCameraShutter, rpiCameraMetering,
rpiCameraGain, rpiCameraEV, rpiCameraROI, rpiCameraTuningFile
* support float values in config file
2022-10-24 13:08:30 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
|
2021-09-27 08:36:28 +00:00
|
|
|
case reflect.TypeOf(bool(false)):
|
2021-05-09 15:51:38 +00:00
|
|
|
if ev, ok := env[prefix]; ok {
|
2023-10-07 21:32:15 +00:00
|
|
|
if prv.IsNil() {
|
|
|
|
prv.Set(reflect.New(rt))
|
|
|
|
}
|
2020-10-13 21:36:27 +00:00
|
|
|
switch strings.ToLower(ev) {
|
|
|
|
case "yes", "true":
|
2023-10-07 21:32:15 +00:00
|
|
|
prv.Elem().SetBool(true)
|
2020-10-13 21:36:27 +00:00
|
|
|
|
|
|
|
case "no", "false":
|
2023-10-07 21:32:15 +00:00
|
|
|
prv.Elem().SetBool(false)
|
2020-10-13 21:36:27 +00:00
|
|
|
|
|
|
|
default:
|
2021-05-09 15:51:38 +00:00
|
|
|
return fmt.Errorf("%s: invalid value '%s'", prefix, ev)
|
2020-10-13 21:36:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2021-09-27 08:36:28 +00:00
|
|
|
}
|
2020-10-13 21:36:27 +00:00
|
|
|
|
2021-09-27 08:36:28 +00:00
|
|
|
switch rt.Kind() {
|
2020-10-13 21:36:27 +00:00
|
|
|
case reflect.Map:
|
|
|
|
for k := range env {
|
2021-05-09 15:51:38 +00:00
|
|
|
if !strings.HasPrefix(k, prefix+"_") {
|
2020-10-13 21:36:27 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-05-09 15:51:38 +00:00
|
|
|
mapKey := strings.Split(k[len(prefix+"_"):], "_")[0]
|
2020-10-14 07:48:54 +00:00
|
|
|
if len(mapKey) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
2020-10-30 22:08:09 +00:00
|
|
|
|
|
|
|
// allow only keys in uppercase
|
|
|
|
if mapKey != strings.ToUpper(mapKey) {
|
|
|
|
continue
|
|
|
|
}
|
2020-10-13 21:36:27 +00:00
|
|
|
|
2020-10-18 22:22:16 +00:00
|
|
|
// initialize only if there's at least one key
|
2023-10-07 21:32:15 +00:00
|
|
|
if prv.Elem().IsNil() {
|
|
|
|
prv.Elem().Set(reflect.MakeMap(rt))
|
2020-10-18 22:22:16 +00:00
|
|
|
}
|
|
|
|
|
2020-10-30 22:08:09 +00:00
|
|
|
mapKeyLower := strings.ToLower(mapKey)
|
2023-10-07 21:32:15 +00:00
|
|
|
nv := prv.Elem().MapIndex(reflect.ValueOf(mapKeyLower))
|
2020-10-13 21:36:27 +00:00
|
|
|
zero := reflect.Value{}
|
|
|
|
if nv == zero {
|
|
|
|
nv = reflect.New(rt.Elem().Elem())
|
2023-10-07 21:32:15 +00:00
|
|
|
prv.Elem().SetMapIndex(reflect.ValueOf(mapKeyLower), nv)
|
2020-10-13 21:36:27 +00:00
|
|
|
}
|
|
|
|
|
2021-09-26 15:07:48 +00:00
|
|
|
err := loadEnvInternal(env, prefix+"_"+mapKey, nv.Elem())
|
2020-10-13 21:36:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
|
|
|
|
case reflect.Struct:
|
|
|
|
flen := rt.NumField()
|
|
|
|
for i := 0; i < flen; i++ {
|
2020-10-13 22:50:08 +00:00
|
|
|
f := rt.Field(i)
|
2023-10-07 21:32:15 +00:00
|
|
|
jsonTag := f.Tag.Get("json")
|
2020-10-13 21:36:27 +00:00
|
|
|
|
2020-10-19 20:17:48 +00:00
|
|
|
// load only public fields
|
2023-10-07 21:32:15 +00:00
|
|
|
if jsonTag == "-" {
|
2020-10-13 21:36:27 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-10-07 21:32:15 +00:00
|
|
|
err := loadEnvInternal(env, prefix+"_"+
|
|
|
|
strings.ToUpper(strings.TrimSuffix(jsonTag, ",omitempty")), prv.Elem().Field(i))
|
2020-10-13 21:36:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2022-12-15 23:50:47 +00:00
|
|
|
|
|
|
|
case reflect.Slice:
|
2023-10-07 21:32:15 +00:00
|
|
|
switch {
|
|
|
|
case rt.Elem() == reflect.TypeOf(""):
|
2022-12-15 23:50:47 +00:00
|
|
|
if ev, ok := env[prefix]; ok {
|
2022-12-30 17:40:27 +00:00
|
|
|
if ev == "" {
|
2023-10-07 21:32:15 +00:00
|
|
|
prv.Elem().Set(reflect.MakeSlice(prv.Elem().Type(), 0, 0))
|
2022-12-30 17:40:27 +00:00
|
|
|
} else {
|
2023-10-23 18:07:28 +00:00
|
|
|
if prv.IsNil() {
|
|
|
|
prv.Set(reflect.New(rt))
|
|
|
|
}
|
2023-10-07 21:32:15 +00:00
|
|
|
prv.Elem().Set(reflect.ValueOf(strings.Split(ev, ",")))
|
2022-12-30 17:40:27 +00:00
|
|
|
}
|
2022-12-15 23:50:47 +00:00
|
|
|
}
|
|
|
|
return nil
|
2023-06-30 14:47:10 +00:00
|
|
|
|
2024-01-28 20:48:21 +00:00
|
|
|
case rt.Elem() == reflect.TypeOf(float64(0)):
|
|
|
|
if ev, ok := env[prefix]; ok {
|
|
|
|
if ev == "" {
|
|
|
|
prv.Elem().Set(reflect.MakeSlice(prv.Elem().Type(), 0, 0))
|
|
|
|
} else {
|
|
|
|
if prv.IsNil() {
|
|
|
|
prv.Set(reflect.New(rt))
|
|
|
|
}
|
|
|
|
|
|
|
|
raw := strings.Split(ev, ",")
|
|
|
|
vals := make([]float64, len(raw))
|
|
|
|
|
|
|
|
for i, v := range raw {
|
|
|
|
tmp, err := strconv.ParseFloat(v, 64)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
vals[i] = tmp
|
|
|
|
}
|
|
|
|
|
|
|
|
prv.Elem().Set(reflect.ValueOf(vals))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
|
2023-10-07 21:32:15 +00:00
|
|
|
case rt.Elem().Kind() == reflect.Struct:
|
2023-07-16 22:12:05 +00:00
|
|
|
if ev, ok := env[prefix]; ok && ev == "" { // special case: empty list
|
2023-10-07 21:32:15 +00:00
|
|
|
prv.Elem().Set(reflect.MakeSlice(prv.Elem().Type(), 0, 0))
|
2023-07-16 22:12:05 +00:00
|
|
|
} else {
|
|
|
|
for i := 0; ; i++ {
|
|
|
|
itemPrefix := prefix + "_" + strconv.FormatInt(int64(i), 10)
|
|
|
|
if !envHasAtLeastAKeyWithPrefix(env, itemPrefix) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
elem := reflect.New(rt.Elem())
|
|
|
|
err := loadEnvInternal(env, itemPrefix, elem.Elem())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-10-07 21:32:15 +00:00
|
|
|
prv.Elem().Set(reflect.Append(prv.Elem(), elem.Elem()))
|
2023-06-30 14:47:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2020-10-13 21:36:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Errorf("unsupported type: %v", rt)
|
|
|
|
}
|
|
|
|
|
2023-10-23 18:07:28 +00:00
|
|
|
func loadWithEnv(env map[string]string, prefix string, v interface{}) error {
|
|
|
|
return loadEnvInternal(env, prefix, reflect.ValueOf(v).Elem())
|
|
|
|
}
|
|
|
|
|
|
|
|
func envToMap() map[string]string {
|
2020-10-13 21:36:27 +00:00
|
|
|
env := make(map[string]string)
|
|
|
|
for _, kv := range os.Environ() {
|
2020-10-30 23:08:50 +00:00
|
|
|
tmp := strings.SplitN(kv, "=", 2)
|
2020-10-13 21:36:27 +00:00
|
|
|
env[tmp[0]] = tmp[1]
|
|
|
|
}
|
2023-10-23 18:07:28 +00:00
|
|
|
return env
|
|
|
|
}
|
2020-10-13 21:36:27 +00:00
|
|
|
|
2023-10-23 18:07:28 +00:00
|
|
|
// Load loads the configuration from the environment.
|
|
|
|
func Load(prefix string, v interface{}) error {
|
|
|
|
return loadWithEnv(envToMap(), prefix, v)
|
2020-10-13 21:36:27 +00:00
|
|
|
}
|