-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathconfig.go
More file actions
123 lines (106 loc) · 3.4 KB
/
config.go
File metadata and controls
123 lines (106 loc) · 3.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package plugin
import (
"fmt"
"reflect"
"github.com/bluemedora/bplogagent/bundle"
"github.com/bluemedora/bplogagent/entry"
"github.com/bluemedora/bplogagent/errors"
"github.com/mitchellh/mapstructure"
"go.etcd.io/bbolt"
"go.uber.org/zap"
)
// Config defines the configuration and build process of a plugin.
type Config interface {
ID() string
Type() string
Build(BuildContext) (Plugin, error)
}
// BuildContext supplies contextual resources when building a plugin.
type BuildContext struct {
Bundles []*bundle.BundleDefinition
Database *bbolt.DB
Logger *zap.SugaredLogger
}
// BuildPlugins will build a collection of plugins from plugin configs.
func BuildPlugins(configs []Config, context BuildContext) ([]Plugin, error) {
plugins := make([]Plugin, 0, len(configs))
for _, config := range configs {
plugin, err := config.Build(context)
if err != nil {
return plugins, errors.WithDetails(err,
"plugin_id", config.ID(),
"plugin_type", config.Type(),
)
}
plugins = append(plugins, plugin)
}
return plugins, nil
}
// configDefinitions is a registry of plugin types to plugin configs.
var configDefinitions = make(map[string]func() (Config, mapstructure.DecodeHookFunc))
// Register will register a plugin config by plugin type.
func Register(pluginType string, config Config, decoders ...mapstructure.DecodeHookFunc) {
configDefinitions[pluginType] = func() (Config, mapstructure.DecodeHookFunc) {
val := reflect.New(reflect.TypeOf(config).Elem()).Interface()
return val.(Config), mapstructure.ComposeDecodeHookFunc(decoders...)
}
}
// ConfigDecoder is a function that uses the config registry to unmarshal plugin configs.
var ConfigDecoder mapstructure.DecodeHookFunc = func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
if t.String() != "plugin.Config" {
return data, nil
}
var mapInterface map[interface{}]interface{}
var mapString map[string]interface{}
switch f {
case reflect.TypeOf(mapInterface):
mapString = make(map[string]interface{})
for k, v := range data.(map[interface{}]interface{}) {
if kString, ok := k.(string); ok {
mapString[kString] = v
} else {
return nil, fmt.Errorf("map has non-string key")
}
}
case reflect.TypeOf(mapString):
mapString = data.(map[string]interface{})
default:
return data, nil
}
typeInterface, ok := mapString["type"]
if !ok {
return nil, errors.NewError(
"Plugin config is missing a `type` field.",
"Ensure that all plugin configs have a `type` field defined.",
)
}
typeString, ok := typeInterface.(string)
if !ok {
return nil, errors.NewError(
"Plugin config does not have a `type` field as a string.",
"Ensure that all plugin configs have a `type` field formatted as a string.",
)
}
createConfig, ok := configDefinitions[typeString]
if !ok {
return nil, errors.NewError(
"Plugin config has an unknown plugin type.",
"Ensure that all plugin configs have a known, valid type.",
"plugin_type", typeString,
)
}
config, decodeHook := createConfig()
decoderCfg := &mapstructure.DecoderConfig{
Result: &config,
DecodeHook: mapstructure.ComposeDecodeHookFunc(decodeHook, entry.FieldSelectorDecoder),
}
decoder, err := mapstructure.NewDecoder(decoderCfg)
if err != nil {
return nil, fmt.Errorf("build decoder: %w", err)
}
err = decoder.Decode(data)
if err != nil {
return nil, fmt.Errorf("decode plugin definition: %s", err)
}
return config, nil
}