package integrations
import (
"fmt"
"reflect"
"strings"
"github.com/grafana/agent/pkg/integrations/config"
"github.com/grafana/agent/pkg/util"
"gopkg.in/yaml.v2"
)
var (
registeredIntegrations = []Config{}
configFieldNames = make(map[reflect.Type]string)
emptyStructType = reflect.TypeOf(struct{}{})
configsType = reflect.TypeOf(Configs{})
)
func RegisterIntegration(cfg Config) {
if reflect.TypeOf(cfg).Kind() != reflect.Ptr {
panic(fmt.Sprintf("RegisterIntegration must be given a pointer, got %T", cfg))
}
registeredIntegrations = append(registeredIntegrations, cfg)
configFieldNames[reflect.TypeOf(cfg)] = cfg.Name()
}
func RegisteredIntegrations() []Config {
res := make([]Config, 0, len(registeredIntegrations))
for _, in := range registeredIntegrations {
res = append(res, cloneIntegration(in))
}
return res
}
func cloneIntegration(c Config) Config {
return reflect.New(reflect.TypeOf(c).Elem()).Interface().(Config)
}
type Configs []UnmarshaledConfig
type UnmarshaledConfig struct {
Config
Common config.Common
}
func MarshalYAML(v interface{}) (interface{}, error) {
inVal := reflect.ValueOf(v)
for inVal.Kind() == reflect.Ptr {
inVal = inVal.Elem()
}
if inVal.Kind() != reflect.Struct {
return nil, fmt.Errorf("integrations: can only marshal a struct, got %T", v)
}
inType := inVal.Type()
var (
cfgType = getConfigTypeForIntegrations(registeredIntegrations, inType)
cfgPointer = reflect.New(cfgType)
cfgVal = cfgPointer.Elem()
)
var configs Configs
for i, n := 0, inType.NumField(); i < n; i++ {
if inType.Field(i).Type == configsType {
configs = inVal.Field(i).Interface().(Configs)
if configs == nil {
configs = Configs{}
}
}
if cfgType.Field(i).PkgPath != "" {
continue
}
cfgVal.Field(i).Set(inVal.Field(i))
}
if configs == nil {
return nil, fmt.Errorf("integrations: Configs field not found in type: %T", v)
}
for _, c := range configs {
fieldName, ok := configFieldNames[reflect.TypeOf(c.Config)]
if !ok {
return nil, fmt.Errorf("integrations: cannot marshal unregistered Config type: %T", c)
}
field := cfgVal.FieldByName("XXX_Config_" + fieldName)
rawConfig, err := getRawIntegrationConfig(c)
if err != nil {
return nil, fmt.Errorf("integrations: cannot marshal integration %q: %w", c.Name(), err)
}
field.Set(rawConfig)
}
return cfgPointer.Interface(), nil
}
func getRawIntegrationConfig(uc UnmarshaledConfig) (v reflect.Value, err error) {
bb, err := util.MarshalYAMLMerged(uc.Common, uc.Config)
if err != nil {
return v, err
}
raw := util.RawYAML(bb)
return reflect.ValueOf(&raw), nil
}
func UnmarshalYAML(out interface{}, unmarshal func(interface{}) error) error {
return unmarshalIntegrationsWithList(registeredIntegrations, out, unmarshal)
}
func unmarshalIntegrationsWithList(integrations []Config, out interface{}, unmarshal func(interface{}) error) error {
outVal := reflect.ValueOf(out)
if outVal.Kind() != reflect.Ptr {
return fmt.Errorf("integrations: can only unmarshal into a struct pointer, got %T", out)
}
outVal = outVal.Elem()
if outVal.Kind() != reflect.Struct {
return fmt.Errorf("integrations: can only unmarshal into a struct pointer, got %T", out)
}
outType := outVal.Type()
var (
cfgType = getConfigTypeForIntegrations(integrations, outType)
cfgPointer = reflect.New(cfgType)
cfgVal = cfgPointer.Elem()
)
var configs *Configs
for i := 0; i < outVal.NumField(); i++ {
if outType.Field(i).Type == configsType {
if configs != nil {
return fmt.Errorf("integrations: Multiple Configs fields found in %T", out)
}
configs = outVal.Field(i).Addr().Interface().(*Configs)
continue
}
if cfgType.Field(i).PkgPath != "" {
continue
}
cfgVal.Field(i).Set(outVal.Field(i))
}
if configs == nil {
return fmt.Errorf("integrations: No Configs field found in %T", out)
}
if err := unmarshal(cfgPointer.Interface()); err != nil {
return replaceYAMLTypeError(err, cfgType, outType)
}
for i := 0; i < outVal.NumField(); i++ {
if cfgType.Field(i).PkgPath != "" {
continue
}
outVal.Field(i).Set(cfgVal.Field(i))
}
integrationLookup := buildIntegrationsMap(integrations)
for i := outVal.NumField(); i < cfgVal.NumField(); i++ {
fieldType := cfgVal.Type().Field(i)
field := cfgVal.Field(i)
if field.IsNil() {
continue
}
configName := strings.TrimPrefix(fieldType.Name, "XXX_Config_")
configReference, ok := integrationLookup[configName]
if !ok {
return fmt.Errorf("integration %q not registered", configName)
}
uc, err := buildUnmarshaledConfig(field.Interface().(*util.RawYAML), configReference)
if err != nil {
return fmt.Errorf("failed to unmarshal integration %q: %w", configName, err)
}
*configs = append(*configs, uc)
}
return nil
}
func getConfigTypeForIntegrations(integrations []Config, out reflect.Type) reflect.Type {
var fields []reflect.StructField
for i, n := 0, out.NumField(); i < n; i++ {
switch field := out.Field(i); {
case field.PkgPath == "" && field.Type != configsType:
fields = append(fields, field)
default:
fields = append(fields, reflect.StructField{
Name: "_" + field.Name,
PkgPath: out.PkgPath(),
Type: emptyStructType,
})
}
}
for _, cfg := range integrations {
fieldName := "XXX_Config_" + cfg.Name()
fields = append(fields, reflect.StructField{
Name: fieldName,
Tag: reflect.StructTag(fmt.Sprintf(`yaml:"%s,omitempty"`, cfg.Name())),
Type: reflect.PtrTo(reflect.TypeOf(util.RawYAML{})),
})
}
return reflect.StructOf(fields)
}
func buildIntegrationsMap(in []Config) map[string]Config {
m := make(map[string]Config, len(in))
for _, i := range in {
m[i.Name()] = i
}
return m
}
func buildUnmarshaledConfig(raw *util.RawYAML, ref Config) (uc UnmarshaledConfig, err error) {
uc = UnmarshaledConfig{
Config: cloneIntegration(ref),
}
err = util.UnmarshalYAMLMerged(*raw, &uc.Common, uc.Config)
return
}
func replaceYAMLTypeError(err error, oldTyp, newTyp reflect.Type) error {
if e, ok := err.(*yaml.TypeError); ok {
oldStr := oldTyp.String()
newStr := newTyp.String()
for i, s := range e.Errors {
e.Errors[i] = strings.ReplaceAll(s, oldStr, newStr)
}
}
return err
}