Skip to content

Instantly share code, notes, and snippets.

@Quentin-M
Last active October 1, 2017 21:26
Show Gist options
  • Save Quentin-M/841cfa8cb2079b0ae2cef42b8ec31ce3 to your computer and use it in GitHub Desktop.
Save Quentin-M/841cfa8cb2079b0ae2cef42b8ec31ce3 to your computer and use it in GitHub Desktop.
Go: Generic configuration and typed configuration for dynamic providers (with defaults)
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