-
Notifications
You must be signed in to change notification settings - Fork 183
/
dynamic.go
111 lines (93 loc) · 3.75 KB
/
dynamic.go
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
package config
import (
"reflect"
"strings"
yaml "gopkg.in/yaml.v2"
"github.com/creasty/defaults"
"github.com/davecgh/go-spew/spew"
"github.com/pkg/errors"
"github.com/signalfx/signalfx-agent/internal/utils"
log "github.com/sirupsen/logrus"
)
// DecodeExtraConfigStrict will pull out any config values from 'in' and put them
// on the 'out' struct, returning an error if anything in 'in' isn't in 'out'.
func DecodeExtraConfigStrict(in CustomConfigurable, out interface{}) error {
return DecodeExtraConfig(in, out, true)
}
// DecodeExtraConfig will pull out the OtherConfig values from both
// ObserverConfig and MonitorConfig and decode them to a struct that is
// provided in the `out` arg. Whether all fields have to be in 'out' is
// determined by the 'strict' flag. Any errors decoding will cause `out` to be nil.
func DecodeExtraConfig(in CustomConfigurable, out interface{}, strict bool) error {
pkgPaths := strings.Split(reflect.Indirect(reflect.ValueOf(out)).Type().PkgPath(), "/")
otherYaml, err := yaml.Marshal(in.ExtraConfig())
if err != nil {
return err
}
if strict {
err = yaml.UnmarshalStrict(otherYaml, out)
} else {
err = yaml.Unmarshal(otherYaml, out)
}
if err != nil {
log.WithFields(log.Fields{
"package": pkgPaths[len(pkgPaths)-1],
"otherConfig": spew.Sdump(in.ExtraConfig()),
"error": err,
}).Error("Invalid module-specific configuration")
return err
}
if err := defaults.Set(out); err != nil {
log.WithFields(log.Fields{
"package": pkgPaths[len(pkgPaths)-1],
"error": err,
"out": spew.Sdump(out),
}).Error("Could not set defaults on module-specific config")
return err
}
return nil
}
// FillInConfigTemplate takes a config template value that a monitor/observer
// provided and fills it in dynamically from the provided conf
func FillInConfigTemplate(embeddedFieldName string, configTemplate interface{}, conf CustomConfigurable) error {
templateValue := reflect.ValueOf(configTemplate)
pkg := templateValue.Type().PkgPath()
if templateValue.Kind() != reflect.Ptr || templateValue.Elem().Kind() != reflect.Struct {
return errors.Errorf("Config template must be a pointer to a struct, got %s of kind/type %s/%s",
pkg, templateValue.Kind(), templateValue.Type())
}
embeddedField := templateValue.Elem().FieldByName(embeddedFieldName)
if !embeddedField.IsValid() {
return errors.Errorf("Could not find field %s in config. Available fields: %v",
embeddedFieldName, utils.GetStructFieldNames(templateValue))
}
embeddedField.Set(reflect.Indirect(reflect.ValueOf(conf)))
return DecodeExtraConfigStrict(conf, configTemplate)
}
// CallConfigure will call the Configure method on an observer or monitor with
// a `conf` object, typed to the correct type. This allows monitors/observers
// to set the type of the config object to their own config and not have to
// worry about casting or converting.
func CallConfigure(instance, conf interface{}) error {
instanceVal := reflect.ValueOf(instance)
_type := instanceVal.Type().PkgPath()
confVal := reflect.ValueOf(conf)
method := instanceVal.MethodByName("Configure")
if !method.IsValid() {
return errors.Errorf("No Configure method found for type %s", _type)
}
if method.Type().NumIn() != 1 {
return errors.Errorf("Configure method of %s should take exactly one argument that matches "+
"the type of the config template provided in the Register function! It has %d arguments.",
_type, method.Type().NumIn())
}
errorIntf := reflect.TypeOf((*error)(nil)).Elem()
if method.Type().NumOut() != 1 || !method.Type().Out(0).Implements(errorIntf) {
return errors.Errorf("Configure method for type %s should return an error", _type)
}
ret := method.Call([]reflect.Value{confVal})[0]
if ret.IsNil() {
return nil
}
return ret.Interface().(error)
}