webhookd/pkg/config/flag/bind.go

122 lines
3.2 KiB
Go

package configflag
import (
"flag"
"fmt"
"os"
"reflect"
"strconv"
"strings"
"time"
"github.com/ncarlier/webhookd/pkg/helper"
)
// Bind conf struct tags with flags
func Bind(conf interface{}, envPrefix string) error {
return bind(conf, envPrefix, "")
}
func bind(conf interface{}, envPrefix, keyPrefix string) error {
rv := reflect.ValueOf(conf)
for rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface {
rv = rv.Elem()
}
typ := rv.Type()
for i := 0; i < typ.NumField(); i++ {
fieldType := typ.Field(i)
field := rv.Field(i)
var key, desc, val string
// Get field key from struct tag
if tag, ok := fieldType.Tag.Lookup("flag"); ok {
key = tag
} else {
continue
}
// Get field description from struct tag
if tag, ok := fieldType.Tag.Lookup("desc"); ok {
desc = tag
}
// Get field value from struct tag
if tag, ok := fieldType.Tag.Lookup("default"); ok {
val = tag
}
if keyPrefix != "" {
key = keyPrefix + "-" + key
}
// Get field value and description from environment variable
val = getEnvValue(envPrefix, key, val)
desc = getEnvDesc(envPrefix, key, desc)
// Get field value by reflection from struct definition
// And bind value to command line flag
switch fieldType.Type.Kind() {
case reflect.String:
field.SetString(val)
ptr, _ := field.Addr().Interface().(*string)
flag.StringVar(ptr, key, val, desc)
case reflect.Bool:
bVal, err := strconv.ParseBool(val)
if err != nil {
return fmt.Errorf("invalid boolean value for %s: %v", key, err)
}
field.SetBool(bVal)
ptr, _ := field.Addr().Interface().(*bool)
flag.BoolVar(ptr, key, bVal, desc)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if field.Kind() == reflect.Int64 && field.Type().PkgPath() == "time" && field.Type().Name() == "Duration" {
d, err := time.ParseDuration(val)
if err != nil {
return fmt.Errorf("invalid duration value for %s: %v", key, err)
}
field.SetInt(int64(d))
ptr, _ := field.Addr().Interface().(*time.Duration)
flag.DurationVar(ptr, key, d, desc)
} else {
i64Val, err := strconv.ParseInt(val, 0, fieldType.Type.Bits())
if err != nil {
return fmt.Errorf("invalid number value for %s: %v", key, err)
}
field.SetInt(i64Val)
ptr, _ := field.Addr().Interface().(*int)
flag.IntVar(ptr, key, int(i64Val), desc)
}
case reflect.Struct:
if err := bind(field.Addr().Interface(), envPrefix, key); err != nil {
return fmt.Errorf("invalid struct value for %s: %v", key, err)
}
case reflect.Slice:
sliceType := field.Type().Elem()
if sliceType.Kind() == reflect.String {
vals := strings.Split(val, ",")
sl := make([]string, len(vals))
copy(sl, vals)
field.Set(reflect.ValueOf(sl))
ptr, _ := field.Addr().Interface().(*[]string)
af := newArrayFlags(ptr)
flag.Var(af, key, desc)
}
}
}
return nil
}
func getEnvKey(prefix, key string) string {
return helper.ToScreamingSnake(prefix + "_" + key)
}
func getEnvValue(prefix, key, fallback string) string {
if value, ok := os.LookupEnv(getEnvKey(prefix, key)); ok {
return value
}
return fallback
}
func getEnvDesc(prefix, key, desc string) string {
return fmt.Sprintf("%s (env: %s)", desc, getEnvKey(prefix, key))
}