From c32d2b49169e98a002de8a694f6cad9f2f8b0b25 Mon Sep 17 00:00:00 2001 From: Byung Jun Park Date: Fri, 14 Jul 2017 00:35:54 +0900 Subject: [PATCH] ing --- .vscode/launch.json | 18 +++ commons/config/config.go | 234 +++++++++++++++++++++++++++++++++++++++ config/config.go | 45 +++----- config/config_test.go | 18 +++ glide.yaml | 4 +- 5 files changed, 291 insertions(+), 28 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 commons/config/config.go create mode 100644 config/config_test.go diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..3bf3fa0 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch", + "type": "go", + "request": "launch", + "mode": "debug", + "remotePath": "", + "port": 2345, + "host": "127.0.0.1", + "program": "${fileDirname}", + "env": {}, + "args": [], + "showLog": true + } + ] +} \ No newline at end of file diff --git a/commons/config/config.go b/commons/config/config.go new file mode 100644 index 0000000..05de960 --- /dev/null +++ b/commons/config/config.go @@ -0,0 +1,234 @@ +package config + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "path" + "reflect" + "regexp" + "strings" + + "github.com/BurntSushi/toml" + + "gopkg.in/yaml.v2" +) + +type Configurator struct { + *Config +} + +type Config struct { + Environment string + ENVPrefix string +} + +// New initialize a Configurator +func New(config *Config) *Configurator { + if config == nil { + config = &Config{} + } + return &Configurator{Config: config} +} + +// GetEnvironment get environment +func (c *Configurator) GetEnvironment() string { + if c.Environment == "" { + if env := os.Getenv("CONFIGOR_ENV"); env != "" { + return env + } + + if isTest, _ := regexp.MatchString("/_test/", os.Args[0]); isTest { + return "test" + } + + return "development" + } + return c.Environment +} + +// Load will unmarshal configurations to struct from files that you provide +func (c *Configurator) Load(config interface{}, files ...string) error { + for _, file := range c.getConfigurationFiles(files...) { + if err := processFile(config, file); err != nil { + return err + } + } + + if prefix := c.getENVPrefix(config); prefix == "-" { + return processTags(config) + } else { + return processTags(config, prefix) + } +} + +func (c *Configurator) getENVPrefix(config interface{}) string { + if c.Config.ENVPrefix == "" { + if prefix := os.Getenv("CONFIGOR_ENV_PREFIX"); prefix != "" { + return prefix + } + return "Configurator" + } + return c.Config.ENVPrefix +} + +func (c *Configurator) getConfigurationFiles(files ...string) []string { + var results []string + + for i := len(files) - 1; i >= 0; i-- { + foundFile := false + file := files[i] + + // check configuration + if fileInfo, err := os.Stat(file); err == nil && fileInfo.Mode().IsRegular() { + foundFile = true + results = append(results, file) + } + + // check configuration with env + if file, err := getConfigurationFileWithENVPrefix(file, c.GetEnvironment()); err == nil { + foundFile = true + results = append(results, file) + } + + // check example configuration + if !foundFile { + if example, err := getConfigurationFileWithENVPrefix(file, "example"); err == nil { + fmt.Printf("Failed to find configuration %v, using example file %v\n", file, example) + results = append(results, example) + } else { + fmt.Printf("Failed to find configuration %v\n", file) + } + } + } + return results +} + +// ENV return environment +func ENV() string { + return New(nil).GetEnvironment() +} + +// Load will unmarshal configurations to struct from files that you provide +func Load(config interface{}, files ...string) error { + return New(nil).Load(config, files...) +} + +func getConfigurationFileWithENVPrefix(file, env string) (string, error) { + var ( + envFile string + extname = path.Ext(file) + ) + + if extname == "" { + envFile = fmt.Sprintf("%v.%v", file, env) + } else { + envFile = fmt.Sprintf("%v.%v%v", strings.TrimSuffix(file, extname), env, extname) + } + + if fileInfo, err := os.Stat(envFile); err == nil && fileInfo.Mode().IsRegular() { + return envFile, nil + } + return "", fmt.Errorf("failed to find file %v", file) +} + +func processFile(config interface{}, file string) error { + data, err := ioutil.ReadFile(file) + if err != nil { + return err + } + + switch { + case strings.HasSuffix(file, ".yaml") || strings.HasSuffix(file, ".yml"): + return yaml.Unmarshal(data, config) + case strings.HasSuffix(file, ".toml"): + return toml.Unmarshal(data, config) + case strings.HasSuffix(file, ".json"): + return json.Unmarshal(data, config) + default: + if toml.Unmarshal(data, config) != nil { + if json.Unmarshal(data, config) != nil { + if yaml.Unmarshal(data, config) != nil { + return errors.New("failed to decode config") + } + } + } + return nil + } +} + +func getPrefixForStruct(prefixes []string, fieldStruct *reflect.StructField) []string { + if fieldStruct.Anonymous && fieldStruct.Tag.Get("anonymous") == "true" { + return prefixes + } + return append(prefixes, fieldStruct.Name) +} + +func processTags(config interface{}, prefixes ...string) error { + configValue := reflect.Indirect(reflect.ValueOf(config)) + if configValue.Kind() != reflect.Struct { + return errors.New("invalid config, should be struct") + } + + configType := configValue.Type() + for i := 0; i < configType.NumField(); i++ { + var ( + envNames []string + fieldStruct = configType.Field(i) + field = configValue.Field(i) + envName = fieldStruct.Tag.Get("env") // read configuration from shell env + ) + + if envName == "" { + envNames = append(envNames, strings.Join(append(prefixes, fieldStruct.Name), "_")) // Configor_DB_Name + envNames = append(envNames, strings.ToUpper(strings.Join(append(prefixes, fieldStruct.Name), "_"))) // CONFIGOR_DB_NAME + } else { + envNames = []string{envName} + } + + // Load From Shell ENV + for _, env := range envNames { + if value := os.Getenv(env); value != "" { + if err := yaml.Unmarshal([]byte(value), field.Addr().Interface()); err != nil { + return err + } + break + } + } + + if isBlank := reflect.DeepEqual(field.Interface(), reflect.Zero(field.Type()).Interface()); isBlank { + // Set default configuration if blank + if value := fieldStruct.Tag.Get("default"); value != "" { + if err := yaml.Unmarshal([]byte(value), field.Addr().Interface()); err != nil { + return err + } + } else if fieldStruct.Tag.Get("required") == "true" { + // return error if it is required but blank + return errors.New(fieldStruct.Name + " is required, but blank") + } + } + + for field.Kind() == reflect.Ptr { + field = field.Elem() + } + + if field.Kind() == reflect.Struct { + if err := processTags(field.Addr().Interface(), getPrefixForStruct(prefixes, &fieldStruct)...); err != nil { + return err + } + } + + if field.Kind() == reflect.Slice { + for i := 0; i < field.Len(); i++ { + if reflect.Indirect(field.Index(i)).Kind() == reflect.Struct { + if err := processTags(field.Index(i).Addr().Interface(), append(getPrefixForStruct(prefixes, &fieldStruct), fmt.Sprint(i))...); err != nil { + return err + } + } + } + } + } + return nil +} diff --git a/config/config.go b/config/config.go index 3b11f83..645a7ca 100644 --- a/config/config.go +++ b/config/config.go @@ -1,42 +1,33 @@ package config -import ( - "io/ioutil" - - "gopkg.in/yaml.v2" -) +import "git.loafle.net/overflow/overflow-service-websocket/commons/config" type Config struct { - websocket Websocket `yaml:"websocket"` - grpc GRPC `yaml:"grpc"` + Websocket Websocket + Grpc GRpc } type Websocket struct { - writeTimeout int8 `yaml:"writeTimeout"` - readTimeout int8 `yaml:"readTimeout"` - pongTimeout int8 `yaml:"pongTimeout"` - pingTimeout int8 `yaml:"pingTimeout"` - maxMessageSize int64 `yaml:"maxMessageSize"` - readBufferSize int `yaml:"readBufferSize"` - writeBufferSize int `yaml:"writeBufferSize"` + WriteTimeout int8 `default:"0"` + ReadTimeout int8 `default:"0"` + PongTimeout int8 `default:"60"` + PingTimeout int8 `default:"10"` + MaxMessageSize int64 `default:"1024"` + ReadBufferSize int `default:"4096"` + WriteBufferSize int `default:"4096"` } -type GRPC struct { - host string `yaml:"host"` - port int16 `yaml:"port"` - tls bool `yaml:"tls"` +type GRpc struct { + Host string `required:"true"` + Port int16 `required:"true"` + Tls bool `default:"false"` } func LoadConfig(filename string) (*Config, error) { - bytes, err := ioutil.ReadFile(filename) - if err != nil { + var c Config + err := config.Load(&c, filename) + if nil != err { return nil, err } - c := new(Config) - err = yaml.Unmarshal(bytes, &c) - if err != nil { - return nil, err - } - - return c, nil + return &c, nil } diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000..ae61266 --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,18 @@ +package config + +import ( + "log" + "os" + "path/filepath" + "testing" +) + +func TestLoadConfig(t *testing.T) { + os.Chdir("../") + wd, _ := os.Getwd() + path := filepath.Join(wd, "config.yml") + + c, _ := LoadConfig(path) + + log.Println(c.Grpc.Host) +} diff --git a/glide.yaml b/glide.yaml index f10afe3..caaefec 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,4 +1,4 @@ -package: git.loafle.net/overflow/overflow_websocket_service +package: git.loafle.net/overflow/overflow_service_websocket import: - package: github.com/satori/go.uuid version: ^1.1.0 @@ -17,3 +17,5 @@ import: subpackages: - golang - package: gopkg.in/yaml.v2 +- package: github.com/BurntSushi/toml + version: ^0.3.0