Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/integrations/register.go
5302 views
1
package integrations
2
3
import (
4
"fmt"
5
"reflect"
6
"strings"
7
8
"github.com/grafana/agent/pkg/integrations/config"
9
"github.com/grafana/agent/pkg/util"
10
"gopkg.in/yaml.v2"
11
)
12
13
var (
14
registeredIntegrations = []Config{}
15
configFieldNames = make(map[reflect.Type]string)
16
17
emptyStructType = reflect.TypeOf(struct{}{})
18
configsType = reflect.TypeOf(Configs{})
19
)
20
21
// RegisterIntegration dynamically registers a new integration. The Config
22
// will represent the configuration that controls the specific integration.
23
// Registered Configs may be loaded using UnmarshalYAML or manually
24
// constructed.
25
//
26
// RegisterIntegration panics if cfg is not a pointer.
27
func RegisterIntegration(cfg Config) {
28
if reflect.TypeOf(cfg).Kind() != reflect.Ptr {
29
panic(fmt.Sprintf("RegisterIntegration must be given a pointer, got %T", cfg))
30
}
31
registeredIntegrations = append(registeredIntegrations, cfg)
32
configFieldNames[reflect.TypeOf(cfg)] = cfg.Name()
33
}
34
35
// RegisteredIntegrations all Configs that were passed to RegisterIntegration.
36
// Each call will generate a new set of pointers.
37
func RegisteredIntegrations() []Config {
38
res := make([]Config, 0, len(registeredIntegrations))
39
for _, in := range registeredIntegrations {
40
res = append(res, cloneIntegration(in))
41
}
42
return res
43
}
44
45
func cloneIntegration(c Config) Config {
46
return reflect.New(reflect.TypeOf(c).Elem()).Interface().(Config)
47
}
48
49
// Configs is a list of UnmarshaledConfig. Configs for integrations which are
50
// unmarshaled from YAML are combined with common settings.
51
type Configs []UnmarshaledConfig
52
53
// UnmarshaledConfig combines an integration's config with common settings.
54
type UnmarshaledConfig struct {
55
Config
56
Common config.Common
57
}
58
59
// MarshalYAML helps implement yaml.Marshaller for structs that have a Configs
60
// field that should be inlined in the YAML string.
61
func MarshalYAML(v interface{}) (interface{}, error) {
62
inVal := reflect.ValueOf(v)
63
for inVal.Kind() == reflect.Ptr {
64
inVal = inVal.Elem()
65
}
66
if inVal.Kind() != reflect.Struct {
67
return nil, fmt.Errorf("integrations: can only marshal a struct, got %T", v)
68
}
69
inType := inVal.Type()
70
71
var (
72
cfgType = getConfigTypeForIntegrations(registeredIntegrations, inType)
73
cfgPointer = reflect.New(cfgType)
74
cfgVal = cfgPointer.Elem()
75
)
76
77
// Copy over any existing value from inVal to cfgVal.
78
//
79
// The ordering of fields in inVal and cfgVal match identically up until the
80
// extra fields appended to the end of cfgVal.
81
var configs Configs
82
for i, n := 0, inType.NumField(); i < n; i++ {
83
if inType.Field(i).Type == configsType {
84
configs = inVal.Field(i).Interface().(Configs)
85
if configs == nil {
86
configs = Configs{}
87
}
88
}
89
if cfgType.Field(i).PkgPath != "" {
90
continue // Field is unexported: ignore.
91
}
92
cfgVal.Field(i).Set(inVal.Field(i))
93
}
94
if configs == nil {
95
return nil, fmt.Errorf("integrations: Configs field not found in type: %T", v)
96
}
97
98
for _, c := range configs {
99
fieldName, ok := configFieldNames[reflect.TypeOf(c.Config)]
100
if !ok {
101
return nil, fmt.Errorf("integrations: cannot marshal unregistered Config type: %T", c)
102
}
103
field := cfgVal.FieldByName("XXX_Config_" + fieldName)
104
rawConfig, err := getRawIntegrationConfig(c)
105
if err != nil {
106
return nil, fmt.Errorf("integrations: cannot marshal integration %q: %w", c.Name(), err)
107
}
108
field.Set(rawConfig)
109
}
110
111
return cfgPointer.Interface(), nil
112
}
113
114
// getRawIntegrationConfig turns an UnmarshaledConfig into the *util.RawYAML
115
// used to represent it in configs.
116
func getRawIntegrationConfig(uc UnmarshaledConfig) (v reflect.Value, err error) {
117
bb, err := util.MarshalYAMLMerged(uc.Common, uc.Config)
118
if err != nil {
119
return v, err
120
}
121
raw := util.RawYAML(bb)
122
return reflect.ValueOf(&raw), nil
123
}
124
125
// UnmarshalYAML helps implement yaml.Unmarshaller for structs that have a
126
// Configs field that should be inlined in the YAML string.
127
func UnmarshalYAML(out interface{}, unmarshal func(interface{}) error) error {
128
return unmarshalIntegrationsWithList(registeredIntegrations, out, unmarshal)
129
}
130
131
// unmarshalIntegrationsWithList unmarshals to a subtype of out that has a
132
// field added for every integration in integrations. Code adapted from
133
// Prometheus:
134
//
135
// https://github.com/prometheus/prometheus/blob/511511324adfc4f4178f064cc104c2deac3335de/discovery/registry.go#L111
136
func unmarshalIntegrationsWithList(integrations []Config, out interface{}, unmarshal func(interface{}) error) error {
137
outVal := reflect.ValueOf(out)
138
if outVal.Kind() != reflect.Ptr {
139
return fmt.Errorf("integrations: can only unmarshal into a struct pointer, got %T", out)
140
}
141
outVal = outVal.Elem()
142
if outVal.Kind() != reflect.Struct {
143
return fmt.Errorf("integrations: can only unmarshal into a struct pointer, got %T", out)
144
}
145
outType := outVal.Type()
146
147
var (
148
cfgType = getConfigTypeForIntegrations(integrations, outType)
149
cfgPointer = reflect.New(cfgType)
150
cfgVal = cfgPointer.Elem()
151
)
152
153
// Copy over any existing value from outVal to cfgVal.
154
//
155
// The ordering of fields in outVal and cfgVal match identically up until the
156
// extra fields appended to the end of cfgVal.
157
var configs *Configs
158
for i := 0; i < outVal.NumField(); i++ {
159
if outType.Field(i).Type == configsType {
160
if configs != nil {
161
return fmt.Errorf("integrations: Multiple Configs fields found in %T", out)
162
}
163
configs = outVal.Field(i).Addr().Interface().(*Configs)
164
continue
165
}
166
if cfgType.Field(i).PkgPath != "" {
167
// Ignore unexported fields
168
continue
169
}
170
cfgVal.Field(i).Set(outVal.Field(i))
171
}
172
if configs == nil {
173
return fmt.Errorf("integrations: No Configs field found in %T", out)
174
}
175
176
// Unmarshal into our dynamic type.
177
if err := unmarshal(cfgPointer.Interface()); err != nil {
178
return replaceYAMLTypeError(err, cfgType, outType)
179
}
180
181
// Copy back unmarshaled fields that were originally in outVal.
182
for i := 0; i < outVal.NumField(); i++ {
183
if cfgType.Field(i).PkgPath != "" {
184
// Ignore unexported fields
185
continue
186
}
187
outVal.Field(i).Set(cfgVal.Field(i))
188
}
189
190
// Iterate through the remainder of our fields, which should all be dynamic
191
// structs where the first field is a config.Common and the second field is
192
// a Config.
193
integrationLookup := buildIntegrationsMap(integrations)
194
for i := outVal.NumField(); i < cfgVal.NumField(); i++ {
195
// Our integrations are unmarshaled as *util.RawYAML. If it's nil, we treat
196
// it as not defined.
197
fieldType := cfgVal.Type().Field(i)
198
field := cfgVal.Field(i)
199
if field.IsNil() {
200
continue
201
}
202
203
configName := strings.TrimPrefix(fieldType.Name, "XXX_Config_")
204
configReference, ok := integrationLookup[configName]
205
if !ok {
206
return fmt.Errorf("integration %q not registered", configName)
207
}
208
uc, err := buildUnmarshaledConfig(field.Interface().(*util.RawYAML), configReference)
209
if err != nil {
210
return fmt.Errorf("failed to unmarshal integration %q: %w", configName, err)
211
}
212
*configs = append(*configs, uc)
213
}
214
215
return nil
216
}
217
218
// getConfigTypeForIntegrations returns a dynamic struct type that has all of
219
// the same fields as out including the fields for the provided integrations.
220
//
221
// integrations are unmarshaled to *util.RawYAML for deferred unmarshaling.
222
func getConfigTypeForIntegrations(integrations []Config, out reflect.Type) reflect.Type {
223
// Initial exported fields map one-to-one.
224
var fields []reflect.StructField
225
for i, n := 0, out.NumField(); i < n; i++ {
226
switch field := out.Field(i); {
227
case field.PkgPath == "" && field.Type != configsType:
228
fields = append(fields, field)
229
default:
230
fields = append(fields, reflect.StructField{
231
Name: "_" + field.Name, // Field must be unexported.
232
PkgPath: out.PkgPath(),
233
Type: emptyStructType,
234
})
235
}
236
}
237
for _, cfg := range integrations {
238
// Use a prefix that's unlikely to collide with anything else.
239
fieldName := "XXX_Config_" + cfg.Name()
240
fields = append(fields, reflect.StructField{
241
Name: fieldName,
242
Tag: reflect.StructTag(fmt.Sprintf(`yaml:"%s,omitempty"`, cfg.Name())),
243
Type: reflect.PtrTo(reflect.TypeOf(util.RawYAML{})),
244
})
245
}
246
return reflect.StructOf(fields)
247
}
248
249
func buildIntegrationsMap(in []Config) map[string]Config {
250
m := make(map[string]Config, len(in))
251
for _, i := range in {
252
m[i.Name()] = i
253
}
254
return m
255
}
256
257
// buildUnmarshaledConfig converts raw YAML into an UnmarshaledConfig where the
258
// config type is the same as ref.
259
func buildUnmarshaledConfig(raw *util.RawYAML, ref Config) (uc UnmarshaledConfig, err error) {
260
// Initialize uc.Config so it can be unmarshaled properly as an interface.
261
uc = UnmarshaledConfig{
262
Config: cloneIntegration(ref),
263
}
264
err = util.UnmarshalYAMLMerged(*raw, &uc.Common, uc.Config)
265
return
266
}
267
268
func replaceYAMLTypeError(err error, oldTyp, newTyp reflect.Type) error {
269
if e, ok := err.(*yaml.TypeError); ok {
270
oldStr := oldTyp.String()
271
newStr := newTyp.String()
272
for i, s := range e.Errors {
273
e.Errors[i] = strings.ReplaceAll(s, oldStr, newStr)
274
}
275
}
276
return err
277
}
278
279