From ed6133420e9dfe44f6c6fd762ebad54e30e441b7 Mon Sep 17 00:00:00 2001 From: Sumanth Chinthagunta Date: Tue, 9 Jun 2020 15:59:39 -0700 Subject: [PATCH] feat(defaults): added workarround when fields are structs with pointers Workarround for https://github.com/jinzhu/configor/issues/57 https://github.com/jinzhu/configor/issues/57 --- README.md | 42 +++++++++++++++++++++----- configor.go | 4 +++ configor_test.go | 77 +++++++++++++++++++++++++++++------------------- utils.go | 13 +++++++- 4 files changed, 96 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 1d27e46..0b5c0ce 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Golang Configuration module that support YAML, JSON, Shell Environment +This is based on [jinzhu/configor's](https://github.com/jinzhu/configor) work, with some bug fixes and enhancements. + ## Features - Strongly typed config with tags @@ -14,14 +16,16 @@ Golang Configuration module that support YAML, JSON, Shell Environment - Config Sources - YAML files - Environment Variables - - [x] Command line flags + - Command line flags + - [ ] Kubernetes ConfigMaps - Merge multiple config sources (Overlays) - Dynamic Configuration Management (Hot Reconfiguration) - Remote config push - Externalized configuration - Live component reloading / zero-downtime - - Observe Changes - https://play.golang.org/p/41ygGZ-QaB https://gist.github.com/patrickmn/1549985 -- Detect Runtime Environment + - [ ] Observe Config [Changes](https://play.golang.org/p/41ygGZ-QaB https://gist.github.com/patrickmn/1549985) +- Detect Runtime Environment (test, development, production) +- Support Embed config files in Go binaries via [pkger](https://github.com/markbates/pkger) ```golang type Item struct { @@ -48,13 +52,13 @@ import ( ) var Config = struct { - APPName string `default:"app name"` + APPName string `default:"app name" yaml:",omitempty"` DB struct { Name string - User string `default:"root"` + User string `default:"root" yaml:",omitempty"` Password string `required:"true" env:"DBPassword"` - Port uint `default:"3306"` + Port uint `default:"3306" yaml:",omitempty"` } Contacts []struct { @@ -91,12 +95,19 @@ Debug/Verbose mode is helpful when debuging your application, `debug mode` will ```go // Enable debug mode or set env `CONFIGOR_DEBUG_MODE` to true when running your application -configor.New(&configor.Config{Debug: true}).Load(&Config, "config.json") +configor.New(&configor.Config{Debug: true}).Load(&Config, "config.yaml") // Enable verbose mode or set env `CONFIGOR_VERBOSE_MODE` to true when running your application -configor.New(&configor.Config{Verbose: true}).Load(&Config, "config.json") +configor.New(&configor.Config{Verbose: true}).Load(&Config, "config.yaml") + +// You can create custom Configor once and reuse to load multiple different configs +Configor := configor.New(&configor.Config{Debug: true}) +Configor.Load(&Config2, "config2.yaml") +Configor.Load(&Config3, "config3.yaml") ``` +## Load + # Advanced Usage * Load mutiple configurations @@ -159,6 +170,17 @@ $ go run config.go // Will load `config.example.yml` automatically if `config.yml` not found and print warning message ``` +* Load files Via [Pkger](https://github.com/markbates/pkger) + +> Enable Pkger or set via env `CONFIGOR_VERBOSE_MODE` to true to use Pkger for loading files + +```go +// config.go +configor.New(&configor.Config{UsePkger: true}).Load(&Config, "/config/config.json") +# or set via Environment Variable +$ CONFIGOR_USE_PKGER=true go run config.go +``` + * Load From Shell Environment ```go @@ -207,3 +229,7 @@ func main() { // configor.Load(&Config) // only load configurations from shell env & flag } ``` + +## Gotchas +- Defaults not initialized for `Map` type fields +- Overlaying (merging) not working for `Map` type fields \ No newline at end of file diff --git a/configor.go b/configor.go index 44f355e..d06e446 100644 --- a/configor.go +++ b/configor.go @@ -43,6 +43,10 @@ func New(config *Config) *Configor { config.Silent = true } + if os.Getenv("CONFIGOR_USE_PKGER") != "" { + config.UsePkger = true + } + return &Configor{Config: config} } diff --git a/configor_test.go b/configor_test.go index e439c8d..7cdef66 100644 --- a/configor_test.go +++ b/configor_test.go @@ -16,49 +16,40 @@ type Anonymous struct { Description string } -type testConfig struct { - APPName string `default:"configor" json:",omitempty"` - Hosts []string - - DB struct { - Name string - User string `default:"root"` - Password string `required:"true" env:"DBPassword"` - Port uint `default:"3306" json:",omitempty"` - SSL bool `default:"true" json:",omitempty"` - } +type Database struct { + Name string + User string `yaml:",omitempty" default:"root"` + Password string `required:"true" env:"DBPassword"` + Port uint `default:"3306" yaml:",omitempty" json:",omitempty"` + SSL bool `default:"true" yaml:",omitempty" json:",omitempty"` +} - Contacts []struct { - Name string - Email string `required:"true"` - } +type Contact struct { + Name string + Email string `required:"true"` +} +type testConfig struct { + APPName string `default:"configor" yaml:",omitempty" json:",omitempty"` + Hosts []string + DB *Database + Contacts []Contact Anonymous `anonymous:"true"` - - private string + private string } func generateDefaultConfig() testConfig { return testConfig{ APPName: "configor", Hosts: []string{"http://example.org", "http://jinzhu.me"}, - DB: struct { - Name string - User string `default:"root"` - Password string `required:"true" env:"DBPassword"` - Port uint `default:"3306" json:",omitempty"` - SSL bool `default:"true" json:",omitempty"` - }{ + DB: &Database{ Name: "configor", User: "configor", Password: "configor", Port: 3306, SSL: true, }, - Contacts: []struct { - Name string - Email string `required:"true"` - }{ + Contacts: []Contact{ { Name: "Jinzhu", Email: "wosmvp@gmail.com", @@ -203,6 +194,30 @@ func TestUnmatchedKeyInYamltestConfigFile(t *testing.T) { } } +func TestYamlDefaultValue(t *testing.T) { + config := generateDefaultConfig() + config.APPName = "" + config.DB.Port = 0 + config.DB.SSL = false + + if bytes, err := yaml.Marshal(config); err == nil { + if file, err := ioutil.TempFile("/tmp", "configor.*.yaml"); err == nil { + defer file.Close() + defer os.Remove(file.Name()) + file.Write(bytes) + + var result testConfig + Load(&result, file.Name()) + + if !reflect.DeepEqual(result, generateDefaultConfig()) { + t.Errorf("result should be set default value correctly") + } + } + } else { + t.Errorf("failed to marshal config") + } +} + func TestLoadtestConfigurationByEnvironment(t *testing.T) { config := generateDefaultConfig() config2 := struct { @@ -546,7 +561,7 @@ func TestValidation(t *testing.T) { Slient bool } - cfg := &config{Email: "a@b.com", Email2: " ", AuthorIP: "1.1"} + cfg := &config{Email: "a@b.com", Email2: "", AuthorIP: "1.1"} err := Load(cfg) fmt.Printf("%+v\n", cfg) if err != nil { @@ -569,7 +584,7 @@ func TestUsePkger(t *testing.T) { file.Write(bytes) var result testConfig - New(&Config{UsePkger: true}).Load(&result, "/" + file.Name()) + New(&Config{UsePkger: true}).Load(&result, "/"+file.Name()) if !reflect.DeepEqual(result, config) { t.Errorf("result should equal to original configuration") } @@ -577,4 +592,4 @@ func TestUsePkger(t *testing.T) { } else { t.Errorf("failed to marshal config") } -} \ No newline at end of file +} diff --git a/utils.go b/utils.go index f7adfcf..31c8142 100644 --- a/utils.go +++ b/utils.go @@ -185,10 +185,17 @@ func (configor *Configor) processDefaults(config interface{}) error { } for field.Kind() == reflect.Ptr { + if field.IsNil() { + if field.CanAddr() { + field.Set(reflect.New(field.Type().Elem())) + } else { + return fmt.Errorf("ProcessDefaults: field(%v) is nil", field.Type().String()) + } + } field = field.Elem() } - switch field.Kind() { + switch field.Kind() { // TODO reflect.Map case reflect.Struct: if err := configor.processDefaults(field.Addr().Interface()); err != nil { return err @@ -328,6 +335,10 @@ func (configor *Configor) load(config interface{}, files ...string) (err error) return err } + if configor.Config.Verbose { + fmt.Printf("Configuration after Defaults set, and before loading :\n %#v\n", config) + } + for _, file := range configFiles { if configor.Config.Debug || configor.Config.Verbose { fmt.Printf("Loading configurations from file '%v'...\n", file)