adds base config code
This commit is contained in:
		
							
								
								
									
										8
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							| @@ -18,6 +18,14 @@ | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	"cSpell.words": [ | ||||
| 		"ftype", | ||||
| 		"nolint", | ||||
| 		"goconst", | ||||
| 		"TZUTC", | ||||
| 		"webserver" | ||||
| 	], | ||||
|  | ||||
| 	"comment": { | ||||
| 		"description": "Uncomment to enable goproxy and gosumdb." | ||||
| 		"go.toolsEnvVars": { | ||||
|   | ||||
							
								
								
									
										272
									
								
								internal/config/envconfig.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								internal/config/envconfig.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,272 @@ | ||||
| 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{} | ||||
| } | ||||
|  | ||||
| // getEnvString returns string from environment variable | ||||
| func getEnvString(env string, def string) string { | ||||
| 	val := os.Getenv(env) | ||||
|  | ||||
| 	if len(val) == 0 { | ||||
| 		return def | ||||
| 	} | ||||
|  | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| // getEnvBool returns boolean from environment variable | ||||
| func getEnvBool(env string, def bool) (bool, error) { | ||||
| 	val := os.Getenv(env) | ||||
|  | ||||
| 	if len(val) == 0 { | ||||
| 		return def, nil | ||||
| 	} | ||||
|  | ||||
| 	ret, err := strconv.ParseBool(val) | ||||
| 	if err != nil { | ||||
| 		return false, fmt.Errorf("Environment variable is not of type bool: %v", env) | ||||
| 	} | ||||
|  | ||||
| 	return ret, nil | ||||
| } | ||||
|  | ||||
| // getEnvInt returns int from environment variable | ||||
| func getEnvInt(env string, def int) (int, error) { | ||||
| 	val := os.Getenv(env) | ||||
|  | ||||
| 	if len(val) == 0 { | ||||
| 		return def, nil | ||||
| 	} | ||||
|  | ||||
| 	ret, err := strconv.Atoi(val) | ||||
| 	if err != nil { | ||||
| 		return 0, fmt.Errorf("Environment variable is not of type int: %v", env) | ||||
| 	} | ||||
|  | ||||
| 	return ret, nil | ||||
| } | ||||
|  | ||||
| // getEnvInt64 return int64 from environment variable | ||||
| func getEnvInt64(env string, def int64) (int64, error) { | ||||
| 	val := os.Getenv(env) | ||||
|  | ||||
| 	if len(val) == 0 { | ||||
| 		return def, nil | ||||
| 	} | ||||
|  | ||||
| 	ret, err := strconv.ParseInt(val, 10, 64) | ||||
| 	if err != nil { | ||||
| 		return 0, fmt.Errorf("Environment variable is not of type int64: %v", env) | ||||
| 	} | ||||
|  | ||||
| 	return ret, nil | ||||
| } | ||||
|  | ||||
| // getEnvFloat64 return float64 from environment variable | ||||
| func getEnvFloat64(env string, def float64) (float64, error) { | ||||
| 	val := os.Getenv(env) | ||||
|  | ||||
| 	if len(val) == 0 { | ||||
| 		return def, nil | ||||
| 	} | ||||
|  | ||||
| 	ret, err := strconv.ParseFloat(val, 64) | ||||
| 	if err != nil { | ||||
| 		return 0, fmt.Errorf("Environment variable is not of type float64: %v", env) | ||||
| 	} | ||||
|  | ||||
| 	return ret, 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) { | ||||
| 	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) | ||||
| 			flag.StringVar(p, info.Name, getEnvString(info.Alt, dv), 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 := getEnvBool(info.Alt, dv) | ||||
| 			if err != nil { | ||||
| 				cfg.Log.Error("Error Encountered", "error", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 			flag.BoolVar(p, info.Name, retVal, info.Info) | ||||
| 		case "int": | ||||
| 			var dv int | ||||
|  | ||||
| 			if info.DefaultValue != nil { | ||||
| 				dv = info.DefaultValue.(int) | ||||
| 			} | ||||
| 			p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*int) | ||||
| 			retVal, err := getEnvInt(info.Alt, dv) | ||||
| 			if err != nil { | ||||
| 				cfg.Log.Error("Error Encountered", "error", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 			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 := getEnvInt64(info.Alt, dv) | ||||
| 			if err != nil { | ||||
| 				cfg.Log.Error("Error Encountered", "error", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 			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 := getEnvFloat64(info.Alt, dv) | ||||
| 			if err != nil { | ||||
| 				cfg.Log.Error("Error Encountered", "error", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 			flag.Float64Var(p, info.Name, retVal, info.Info) | ||||
| 		} | ||||
| 	} | ||||
| 	flag.Parse() | ||||
| } | ||||
							
								
								
									
										36
									
								
								internal/config/initialize.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								internal/config/initialize.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| package config | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| func Init() *Config { | ||||
| 	cfg := New() | ||||
|  | ||||
| 	cfgInfo, err := getStructInfo(cfg) | ||||
| 	if err != nil { | ||||
| 		cfg.Log.Error("Unable to initialize program parameters", "error", err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	// get command line flags | ||||
| 	cfg.parseFlags(cfgInfo) | ||||
|  | ||||
| 	// 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 | ||||
| } | ||||
							
								
								
									
										73
									
								
								internal/config/struct-config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								internal/config/struct-config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| package config | ||||
|  | ||||
| import ( | ||||
| 	"log/slog" | ||||
| 	"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"` | ||||
|  | ||||
| 	// 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 { | ||||
| 	return &Config{} | ||||
| } | ||||
|  | ||||
| func setLogLevel(cfg *Config) { | ||||
| 	logLevel := &slog.LevelVar{} | ||||
|  | ||||
| 	switch { | ||||
| 	// error | ||||
| 	case cfg.LogLevel <= 20: | ||||
| 		logLevel.Set(slog.LevelError) | ||||
| 		cfg.Log.Info("Log level updated", "level", slog.LevelError) | ||||
| 	// warning | ||||
| 	case cfg.LogLevel > 20 && cfg.LogLevel <= 40: | ||||
| 		logLevel.Set(slog.LevelWarn) | ||||
| 		cfg.Log.Info("Log level updated", "level", slog.LevelWarn) | ||||
| 	// info | ||||
| 	case cfg.LogLevel > 40 && cfg.LogLevel <= 60: | ||||
| 		logLevel.Set(slog.LevelInfo) | ||||
| 		cfg.Log.Info("Log level updated", "level", slog.LevelInfo) | ||||
| 	// debug | ||||
| 	case cfg.LogLevel > 60: | ||||
| 		logLevel.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)) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										19
									
								
								readme.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								readme.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| # GoLang Base | ||||
|  | ||||
| ## Table of Contents | ||||
|  | ||||
| 1. [Information](#information) | ||||
| 1. [Usage](#usage) | ||||
|  | ||||
| ## Information | ||||
|  | ||||
| This is a base repository for starting a GoLang project. It includes a set of linters, rules, and other configurations to make starting a new project easier. | ||||
|  | ||||
| ## Usage | ||||
|  | ||||
| Clone this repository and run the following command: | ||||
|  | ||||
|  ```bash | ||||
| PRJCT="example.com/newproject" | ||||
| go mod init "${PRJCT}" | ||||
| ``` | ||||
		Reference in New Issue
	
	Block a user