Last active
October 1, 2017 21:26
-
-
Save Quentin-M/841cfa8cb2079b0ae2cef42b8ec31ce3 to your computer and use it in GitHub Desktop.
Go: Generic configuration and typed configuration for dynamic providers (with defaults)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"reflect" | |
"time" | |
"unsafe" | |
"gopkg.in/yaml.v2" | |
) | |
type namespacedConfig struct { | |
MyApp Config `yaml:"myApp"` | |
} | |
type Config struct { | |
VarA string `yaml:"varA"` | |
VarB string `yaml:"varB"` | |
Dyn *GenericProvider `yaml:"dyn"` // Optional dynamic provider configuration. | |
} | |
type GenericProvider struct { | |
Provider string `yaml:"provider"` | |
Params map[string]interface{} `yaml:",inline"` | |
} | |
type providerAlpha struct { | |
config providerAlphaConfig | |
} | |
type providerAlphaConfig struct { | |
Var1 string `yaml:"var1"` | |
Var2 time.Duration `yaml:"var2"` | |
} | |
type providerBeta struct { | |
config providerBetaConfig | |
} | |
type providerBetaConfig struct { | |
Var3 int `yaml:"var3"` | |
Var4 map[string]string `yaml:"var4"` | |
Var5 time.Duration `yaml:"var5"` | |
} | |
type Provider interface { | |
Configure(cfg GenericProvider) error | |
PrintConfig() | |
} | |
func main() { | |
// Define configuration. | |
// | |
// In a real application, this would typically be a file on disk. | |
yamlCfg := ` | |
myApp: | |
varB: varB | |
dyn: | |
provider: beta | |
var4: | |
m1: m1 | |
m2: m2 | |
var5: 30s | |
` | |
// Define the providers. | |
// | |
// In a real application, providers would self-register using `func init()`. | |
// See - https://github.com/coreos/clair/blob/master/ext/featurefmt/driver.go | |
// - https://github.com/coreos/clair/blob/master/ext/featurefmt/dpkg/dpkg.go#L37-L41 | |
providers := map[string]Provider{ | |
"alpha": &providerAlpha{}, | |
"beta": &providerBeta{}, | |
} | |
// Load the general configuration, with optional defaults. | |
defaults := Config{VarA: "defaulted"} | |
cfg, err := loadConfig(yamlCfg, defaults) | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
// Main application logic loads the dynamic provider and let it configure | |
// itself - before using it. | |
if cfg.Dyn == nil { | |
fmt.Println("No dynamic provider configured") | |
return | |
} | |
dynProvider := providers[cfg.Dyn.Provider] | |
if dynProvider == nil { | |
fmt.Printf("Unknown dynamic provider %q\n", cfg.Dyn.Provider) | |
return | |
} | |
if err := dynProvider.Configure(*cfg.Dyn); err != nil { | |
fmt.Printf("Invalid configuration for dynamic provider %q: %v\n", cfg.Dyn.Provider, err) | |
} | |
// While the main application uses a dynamic provider hidden behind an | |
// interface, the provider itself deals with typed configuration. | |
dynProvider.PrintConfig() | |
} | |
func (p *providerAlpha) Configure(gcfg GenericProvider) error { | |
p.config = providerAlphaConfig{Var1: "defaulted"} | |
if err := ParseParams(gcfg.Params, &p.config); err != nil { | |
return err | |
} | |
return nil | |
} | |
func (p *providerBeta) Configure(gcfg GenericProvider) error { | |
p.config = providerBetaConfig{ | |
Var3: -1, | |
Var4: map[string]string{"defaultKey": "defaultValue", "m1": "defaulted"}, | |
} | |
if err := ParseParams(gcfg.Params, &p.config); err != nil { | |
return err | |
} | |
return nil | |
} | |
func (p *providerAlpha) PrintConfig() { | |
fmt.Printf("Typed provider Alpha's configuration: %#v", p.config) | |
} | |
func (p *providerBeta) PrintConfig() { | |
fmt.Printf("Typed provider Beta's configuration: %#v", p.config) | |
} | |
func ParseParams(params map[string]interface{}, cfg interface{}) error { | |
yConfig, err := yaml.Marshal(params) | |
if err != nil { | |
return err | |
} | |
// We could use `reflect.New()` and avoid using the unsafe package if we are | |
// ok with returning a new config object (obj), and therefore using | |
// `p.config = *cfg.(*providerXConfig)` in the caller function | |
// `func (p *providerXConfig) Configure(GenericProvider). However, this means | |
// that defaults cannot be set. | |
obj := reflect.NewAt(reflect.TypeOf(cfg).Elem(), unsafe.Pointer(reflect.ValueOf(cfg).Pointer())).Interface() | |
if err := yaml.Unmarshal(yConfig, obj); err != nil { | |
return err | |
} | |
return nil | |
} | |
func loadConfig(yamlConfig string, defaults Config) (Config, error) { | |
cfg := defaults | |
if yamlConfig == "" { | |
return cfg, nil | |
} | |
var nCfg namespacedConfig | |
err := yaml.Unmarshal([]byte(yamlConfig), &nCfg) | |
if err != nil { | |
return cfg, err | |
} | |
return nCfg.MyApp, nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment