initial commit
This commit is contained in:
241
internal/config/envconfig.go
Normal file
241
internal/config/envconfig.go
Normal file
@ -0,0 +1,241 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type structInfo struct {
|
||||
Name string
|
||||
Alt string
|
||||
Info string
|
||||
Key string
|
||||
Field reflect.Value
|
||||
Tags reflect.StructTag
|
||||
Type reflect.Type
|
||||
DefaultValue interface{}
|
||||
Secret interface{}
|
||||
}
|
||||
|
||||
func getEnv[t string | bool | int | int64 | float64](env string, def t) (t, error) {
|
||||
val := os.Getenv(env)
|
||||
if len(val) == 0 {
|
||||
return def, nil
|
||||
}
|
||||
|
||||
output := *new(t)
|
||||
switch (interface{})(def).(type) {
|
||||
case string:
|
||||
v, err := typeConversion("string", val)
|
||||
if err != nil {
|
||||
return (interface{})(false).(t), err
|
||||
}
|
||||
output = v.(t)
|
||||
case bool:
|
||||
v, err := typeConversion("bool", val)
|
||||
if err != nil {
|
||||
return (interface{})(false).(t), err
|
||||
}
|
||||
output = v.(t)
|
||||
case int:
|
||||
v, err := typeConversion("int", val)
|
||||
if err != nil {
|
||||
return (interface{})(int(0)).(t), err
|
||||
}
|
||||
output = (interface{})(int(v.(int64))).(t)
|
||||
case int64:
|
||||
v, err := typeConversion("int64", val)
|
||||
if err != nil {
|
||||
return (interface{})(int64(0)).(t), err
|
||||
}
|
||||
output = v.(t)
|
||||
case float64:
|
||||
v, err := typeConversion("float64", val)
|
||||
if err != nil {
|
||||
return (interface{})(float64(0)).(t), err
|
||||
}
|
||||
output = v.(t)
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func getStructInfo(spec interface{}) ([]structInfo, error) {
|
||||
s := reflect.ValueOf(spec)
|
||||
|
||||
if s.Kind() != reflect.Pointer {
|
||||
return []structInfo{}, fmt.Errorf("getStructInfo() was sent a %s instead of a pointer to a struct.\n", s.Kind())
|
||||
}
|
||||
|
||||
s = s.Elem()
|
||||
if s.Kind() != reflect.Struct {
|
||||
return []structInfo{}, fmt.Errorf("getStructInfo() was sent a %s instead of a struct.\n", s.Kind())
|
||||
}
|
||||
typeOfSpec := s.Type()
|
||||
|
||||
infos := make([]structInfo, 0, s.NumField())
|
||||
for i := 0; i < s.NumField(); i++ {
|
||||
f := s.Field(i)
|
||||
ftype := typeOfSpec.Field(i)
|
||||
|
||||
ignored, _ := strconv.ParseBool(ftype.Tag.Get("ignored"))
|
||||
if !f.CanSet() || ignored {
|
||||
continue
|
||||
}
|
||||
|
||||
for f.Kind() == reflect.Pointer {
|
||||
if f.IsNil() {
|
||||
if f.Type().Elem().Kind() != reflect.Struct {
|
||||
break
|
||||
}
|
||||
f.Set(reflect.New(f.Type().Elem()))
|
||||
}
|
||||
f = f.Elem()
|
||||
}
|
||||
|
||||
secret, err := typeConversion(ftype.Type.String(), ftype.Tag.Get("secret"))
|
||||
if err != nil {
|
||||
secret = false
|
||||
}
|
||||
|
||||
var desc string
|
||||
if len(ftype.Tag.Get("info")) != 0 {
|
||||
desc = fmt.Sprintf("(%s) %s", strings.ToUpper(ftype.Tag.Get("env")), ftype.Tag.Get("info"))
|
||||
} else {
|
||||
desc = fmt.Sprintf("(%s)", strings.ToUpper(ftype.Tag.Get("env")))
|
||||
}
|
||||
|
||||
info := structInfo{
|
||||
Name: ftype.Name,
|
||||
Alt: strings.ToUpper(ftype.Tag.Get("env")),
|
||||
Info: desc,
|
||||
Key: ftype.Name,
|
||||
Field: f,
|
||||
Tags: ftype.Tag,
|
||||
Type: ftype.Type,
|
||||
Secret: secret,
|
||||
}
|
||||
if info.Alt != "" {
|
||||
info.Key = info.Alt
|
||||
}
|
||||
info.Key = strings.ToUpper(info.Key)
|
||||
if ftype.Tag.Get("default") != "" {
|
||||
v, err := typeConversion(ftype.Type.String(), ftype.Tag.Get("default"))
|
||||
if err != nil {
|
||||
return []structInfo{}, err
|
||||
}
|
||||
info.DefaultValue = v
|
||||
}
|
||||
infos = append(infos, info)
|
||||
}
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
func typeConversion(t, v string) (interface{}, error) {
|
||||
switch t {
|
||||
case "string": //nolint:goconst
|
||||
return v, nil
|
||||
case "int": //nolint:goconst
|
||||
return strconv.ParseInt(v, 10, 0)
|
||||
case "int8":
|
||||
return strconv.ParseInt(v, 10, 8)
|
||||
case "int16":
|
||||
return strconv.ParseInt(v, 10, 16)
|
||||
case "int32":
|
||||
return strconv.ParseInt(v, 10, 32)
|
||||
case "int64":
|
||||
return strconv.ParseInt(v, 10, 64)
|
||||
case "uint":
|
||||
return strconv.ParseUint(v, 10, 0)
|
||||
case "uint16":
|
||||
return strconv.ParseUint(v, 10, 16)
|
||||
case "uint32":
|
||||
return strconv.ParseUint(v, 10, 32)
|
||||
case "uint64":
|
||||
return strconv.ParseUint(v, 10, 64)
|
||||
case "float32":
|
||||
return strconv.ParseFloat(v, 32)
|
||||
case "float64":
|
||||
return strconv.ParseFloat(v, 64)
|
||||
case "complex64":
|
||||
return strconv.ParseComplex(v, 64)
|
||||
case "complex128":
|
||||
return strconv.ParseComplex(v, 128)
|
||||
case "bool": //nolint:goconst
|
||||
return strconv.ParseBool(v)
|
||||
}
|
||||
return nil, fmt.Errorf("Unable to identify type.")
|
||||
}
|
||||
|
||||
func (cfg *Config) parseFlags(cfgInfo []structInfo) error { //nolint:gocognit
|
||||
for _, info := range cfgInfo {
|
||||
switch info.Type.String() {
|
||||
case "string":
|
||||
var dv string
|
||||
|
||||
if info.DefaultValue != nil {
|
||||
dv = info.DefaultValue.(string)
|
||||
}
|
||||
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*string)
|
||||
retVal, err := getEnv(info.Alt, dv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flag.StringVar(p, info.Name, retVal, info.Info)
|
||||
case "bool":
|
||||
var dv bool
|
||||
|
||||
if info.DefaultValue != nil {
|
||||
dv = info.DefaultValue.(bool)
|
||||
}
|
||||
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*bool)
|
||||
retVal, err := getEnv(info.Alt, dv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flag.BoolVar(p, info.Name, retVal, info.Info)
|
||||
case "int":
|
||||
var dv int
|
||||
|
||||
if info.DefaultValue != nil {
|
||||
dv = int(info.DefaultValue.(int64))
|
||||
}
|
||||
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*int)
|
||||
retVal, err := getEnv(info.Alt, dv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flag.IntVar(p, info.Name, retVal, info.Info)
|
||||
case "int64":
|
||||
var dv int64
|
||||
|
||||
if info.DefaultValue != nil {
|
||||
dv = info.DefaultValue.(int64)
|
||||
}
|
||||
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*int64)
|
||||
retVal, err := getEnv(info.Alt, dv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flag.Int64Var(p, info.Name, retVal, info.Info)
|
||||
case "float64":
|
||||
var dv float64
|
||||
|
||||
if info.DefaultValue != nil {
|
||||
dv = info.DefaultValue.(float64)
|
||||
}
|
||||
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*float64)
|
||||
retVal, err := getEnv(info.Alt, dv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flag.Float64Var(p, info.Name, retVal, info.Info)
|
||||
}
|
||||
}
|
||||
flag.Parse()
|
||||
return nil
|
||||
}
|
38
internal/config/initialize.go
Normal file
38
internal/config/initialize.go
Normal file
@ -0,0 +1,38 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Init() Config {
|
||||
cfg := New()
|
||||
|
||||
cfgInfo, err := getStructInfo(&cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to initialize program: %v", err)
|
||||
}
|
||||
|
||||
// get command line flags
|
||||
if err := cfg.parseFlags(cfgInfo); err != nil {
|
||||
log.Fatalf("Unable to initialize program: %v", err)
|
||||
}
|
||||
|
||||
// set logging Level
|
||||
setLogLevel(&cfg)
|
||||
|
||||
// set timezone & time format
|
||||
cfg.TZUTC, _ = time.LoadLocation("UTC")
|
||||
cfg.TZLocal, err = time.LoadLocation(cfg.TimeZoneLocal)
|
||||
if err != nil {
|
||||
cfg.Log.Error("Unable to parse timezone string", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// print running config
|
||||
printRunningConfig(&cfg, cfgInfo)
|
||||
|
||||
// return configuration
|
||||
return cfg
|
||||
}
|
81
internal/config/struct-config.go
Normal file
81
internal/config/struct-config.go
Normal file
@ -0,0 +1,81 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// time configuration
|
||||
TimeFormat string `default:"2006-01-02 15:04:05" env:"time_format"`
|
||||
TimeZoneLocal string `default:"America/Chicago" env:"time_zone"`
|
||||
TZLocal *time.Location `ignored:"true"`
|
||||
TZUTC *time.Location `ignored:"true"`
|
||||
|
||||
// logging
|
||||
LogLevel int `default:"50" env:"log_level"`
|
||||
Log *slog.Logger `ignored:"true"`
|
||||
SLogLevel *slog.LevelVar `ignored:"true"`
|
||||
|
||||
// webserver
|
||||
WebServerPort int `default:"8080" env:"webserver_port"`
|
||||
WebServerIP string `default:"0.0.0.0" env:"webserver_ip"`
|
||||
WebServerReadTimeout int `default:"5" env:"webserver_read_timeout"`
|
||||
WebServerWriteTimeout int `default:"1" env:"webserver_write_timeout"`
|
||||
WebServerIdleTimeout int `default:"2" env:"webserver_idle_timeout"`
|
||||
}
|
||||
|
||||
// New initializes the config variable for use with a prepared set of defaults.
|
||||
func New() Config {
|
||||
cfg := Config{
|
||||
SLogLevel: new(slog.LevelVar),
|
||||
}
|
||||
|
||||
cfg.Log = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
||||
Level: cfg.SLogLevel,
|
||||
}))
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func setLogLevel(cfg *Config) {
|
||||
switch {
|
||||
// error
|
||||
case cfg.LogLevel <= 20:
|
||||
cfg.SLogLevel.Set(slog.LevelError)
|
||||
cfg.Log.Info("Log level updated", "level", slog.LevelError)
|
||||
// warning
|
||||
case cfg.LogLevel > 20 && cfg.LogLevel <= 40:
|
||||
cfg.SLogLevel.Set(slog.LevelWarn)
|
||||
cfg.Log.Info("Log level updated", "level", slog.LevelWarn)
|
||||
// info
|
||||
case cfg.LogLevel > 40 && cfg.LogLevel <= 60:
|
||||
cfg.SLogLevel.Set(slog.LevelInfo)
|
||||
cfg.Log.Info("Log level updated", "level", slog.LevelInfo)
|
||||
// debug
|
||||
case cfg.LogLevel > 60:
|
||||
cfg.SLogLevel.Set(slog.LevelDebug)
|
||||
cfg.Log.Info("Log level updated", "level", slog.LevelDebug)
|
||||
}
|
||||
// set default logger
|
||||
slog.SetDefault(cfg.Log)
|
||||
}
|
||||
|
||||
func printRunningConfig(cfg *Config, cfgInfo []structInfo) {
|
||||
for _, info := range cfgInfo {
|
||||
switch info.Type.String() {
|
||||
case "string":
|
||||
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*string)
|
||||
cfg.Log.Debug("Running Configuration", info.Alt, *p)
|
||||
case "bool":
|
||||
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*bool)
|
||||
cfg.Log.Debug("Running Configuration", info.Alt, strconv.FormatBool(*p))
|
||||
case "int":
|
||||
p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*int)
|
||||
cfg.Log.Debug("Running Configuration", info.Alt, strconv.FormatInt(int64(*p), 10))
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user