github.com/yandex/pandora@v0.5.32/core/plugin/pluginconfig/hooks.go (about) 1 // Package pluginconfig contains integration plugin with config packages. 2 // Doing such integration in different package allows to config and plugin packages 3 // not depend on each other, and set hooks when their are really needed. 4 package pluginconfig 5 6 import ( 7 "fmt" 8 "reflect" 9 "strings" 10 11 "github.com/pkg/errors" 12 "github.com/yandex/pandora/core/config" 13 "github.com/yandex/pandora/core/plugin" 14 "github.com/yandex/pandora/lib/tag" 15 "go.uber.org/zap" 16 ) 17 18 func AddHooks() { 19 config.AddTypeHook(Hook) 20 config.AddTypeHook(FactoryHook) 21 } 22 23 const PluginNameKey = "type" 24 25 func Hook(f reflect.Type, t reflect.Type, data interface{}) (p interface{}, err error) { 26 if !plugin.Lookup(t) { 27 return data, nil 28 } 29 name, fillConf, err := parseConf(t, data) 30 if err != nil { 31 return 32 } 33 return plugin.New(t, name, fillConf) 34 } 35 36 func FactoryHook(f reflect.Type, t reflect.Type, data interface{}) (p interface{}, err error) { 37 if !plugin.LookupFactory(t) { 38 return data, nil 39 } 40 name, fillConf, err := parseConf(t, data) 41 if err != nil { 42 return 43 } 44 return plugin.NewFactory(t, name, fillConf) 45 } 46 47 func parseConf(t reflect.Type, data interface{}) (name string, fillConf func(conf interface{}) error, err error) { 48 if tag.Debug { 49 zap.L().Debug("Parsing plugin config", 50 zap.Stringer("plugin", t), 51 zap.Reflect("conf", data), 52 ) 53 } 54 confData, err := toStringKeyMap(data) 55 if err != nil { 56 return 57 } 58 var names []string 59 for key, val := range confData { 60 if PluginNameKey == strings.ToLower(key) { 61 strVal, ok := val.(string) 62 if !ok { 63 err = errors.Errorf("%s has non-string value %s", PluginNameKey, val) 64 return 65 } 66 names = append(names, strVal) 67 delete(confData, key) 68 } 69 } 70 if len(names) == 0 { 71 err = errors.Errorf("plugin %s expected", PluginNameKey) 72 return 73 } 74 if len(names) > 1 { 75 err = errors.Errorf("too many %s keys", PluginNameKey) 76 return 77 } 78 name = names[0] 79 fillConf = func(conf interface{}) error { 80 if tag.Debug { 81 zap.L().Debug("Decoding plugin", 82 zap.String("name", name), 83 zap.Stringer("type", t), 84 zap.Stringer("config type", reflect.TypeOf(conf).Elem()), 85 zap.String("config data", fmt.Sprint(confData)), 86 ) 87 } 88 err := config.DecodeAndValidate(confData, conf) 89 if err != nil { 90 err = errors.Errorf("%s %s plugin\n"+ 91 "%s from %v %s", 92 t, name, reflect.TypeOf(conf).Elem(), confData, err) 93 } 94 return err 95 } 96 return 97 } 98 99 func toStringKeyMap(data interface{}) (out map[string]interface{}, err error) { 100 out, ok := data.(map[string]interface{}) 101 if ok { 102 return 103 } 104 untypedKeyData, ok := data.(map[interface{}]interface{}) 105 if !ok { 106 err = errors.Errorf("unexpected config type %T: should be map[string or interface{}]interface{}", data) 107 return 108 } 109 out = make(map[string]interface{}, len(untypedKeyData)) 110 for key, val := range untypedKeyData { 111 strKey, ok := key.(string) 112 if !ok { 113 err = errors.Errorf("unexpected key type %T: %v", key, key) 114 } 115 out[strKey] = val 116 } 117 return 118 }