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  }