package main import ( "encoding/json" "fmt" "io/ioutil" "log" "os" "reflect" "regexp" "strconv" ) var appconf *AppConfig func initConfig() { //Read config: configFilename := appname + "_config.json" configDir := os.Getenv("CONFDIR") if configDir == "" { confDirs := []string{ "/opt/" + appname, "/opt/" + appname + "/etc", "/usr/lib/" + appname, "/usr/lib/" + appname + "/etc", "/var/lib/" + appname, "/var/lib/" + appname + "/etc", "/usr/local/etc", "/etc", ".", } configDir = "." //the fallback for _, cd := range confDirs { if _, err := os.Stat(cd + "/" + configFilename); os.IsNotExist(err) { //doesn't exist... continue //..so check next one } configDir = cd } } configFile := configDir + "/" + configFilename jsonFile, err := os.Open(configFile) if err != nil { log.Println("Could not open config file: " + configFile + "\n" + err.Error()) fmt.Println("Could not open config file: " + configFile + "\n" + err.Error()) } else { defer jsonFile.Close() fileBytes, _ := ioutil.ReadAll(jsonFile) //strip out // comments from config file: re := regexp.MustCompile(`([\s]//.*)|(^//.*)`) fileCleanedBytes := re.ReplaceAll(fileBytes, nil) err = json.Unmarshal(fileCleanedBytes, &appconf) //populate the config struct with JSON data from the config file if err != nil { log.Fatal("Could not parse config file: " + configFile + "\n" + err.Error()) } } appconf.checkConfig(configFilename) } func (f *AppConfig) checkConfig(configFileName string) { var invalid bool s := reflect.ValueOf(f).Elem() //the reflected struct for i := 0; i < s.NumField(); i++ { //NumField() returns the number of fields in the struct fieldValue := s.Field(i) //value of this i'th field t := s.Type().Field(i) //fmt.Println(t.Name + fmt.Sprintf(" is of kind: %d", t.Type.Kind())) if fieldValue.Interface() == "" || fieldValue.Interface() == nil || fieldValue.Interface() == 0 || (t.Type.Kind() != reflect.Bool && fieldValue.IsZero()) { //field is not set already //fmt.Println(t.Name + " is empty or zero.") if t.Type.Kind() == reflect.String || t.Type.Kind() == reflect.Bool || t.Type.Kind() == reflect.Float64 || t.Type.Kind() == reflect.Int64 || t.Type.Kind() == reflect.Int { if !fieldValue.CanSet() { log.Printf("Config item '%s' cannot be set!\n", t.Name) invalid = true } else { env, ok := os.LookupEnv(t.Tag.Get("env")) if ok && len(env) > 0 { //fmt.Println("ENV: " + t.Tag.Get("env") + " is found and is: " + env + " Setting...") if err := setField(fieldValue, env); err != nil { invalid = true log.Println("Error setting '" + t.Name + "' to env '" + env + "'. Error: " + err.Error()) fmt.Println("Error setting '" + t.Name + "' to env '" + env + "'. Error: " + err.Error()) } else { continue } } else { //env not found //fmt.Println("ENV: '" + t.Tag.Get("env") + "' is NOT FOUND; checking for default value...") // Look for user-defined default value dflt, ok := t.Tag.Lookup("default") //fmt.Println("DEFAULT is: " + dflt) if ok { if err := setField(fieldValue, dflt); err != nil { log.Println("Error setting '" + t.Name + "' to default '" + dflt + "'. Error: " + err.Error()) fmt.Println("Error setting '" + t.Name + "' to default '" + dflt + "'. Error: " + err.Error()) invalid = true } else { continue } } } } } else { log.Printf("Config item '%s' of type %s cannot be set using environment variable %s.", s.Type().Field(i).Name, fieldValue.Type(), s.Type().Field(i).Tag.Get("env")) invalid = true } if !invalid { log.Println("===========| ERRORS IN '" + configFileName + "' CONFIG FILE: |===========") fmt.Println("--------------------------------------------------------------------------------") fmt.Println(" ===========| ERRORS IN '" + configFileName + "' CONFIG FILE: |===========") fmt.Println("") } invalid = true log.Printf(" - Required config item '%s' and/or environment variable '%s' is missing or invalid.\n", t.Tag.Get("json"), t.Tag.Get("env")) fmt.Printf(" - Required config item '%s' and/or environment variable '%s' is missing or invalid.\n", t.Tag.Get("json"), t.Tag.Get("env")) } } if invalid { fmt.Println("--------------------------------------------------------------------------------") log.Fatal("Exiting!") } } func setField(fieldValue reflect.Value, value string) (err error) { switch fieldValue.Kind() { case reflect.Bool: var b bool if b, err = strconv.ParseBool(value); err != nil { return err } fieldValue.SetBool(b) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: var i int64 if i, err = strconv.ParseInt(value, 0, 64); err != nil { return err } fieldValue.SetInt(int64(i)) case reflect.Float32, reflect.Float64: var f float64 if f, err = strconv.ParseFloat(value, 64); err != nil { return err } fieldValue.SetFloat(f) case reflect.String: fieldValue.SetString(value) default: return fmt.Errorf("%s is not a supported config type. Please use bool, float64 float32, int64, int, or string.", fieldValue.Kind()) } return }