package integrations
import (
"fmt"
"reflect"
"strings"
"testing"
"gopkg.in/yaml.v2"
v1 "github.com/grafana/agent/pkg/integrations"
"github.com/grafana/agent/pkg/integrations/v2/common"
"github.com/grafana/agent/pkg/util"
)
var (
integrationByName = make(map[string]interface{})
integrationTypes = make(map[reflect.Type]Type)
nameByType = make(map[reflect.Type]string)
registered = []interface{}{}
upgraders = make(map[reflect.Type]UpgradeFunc)
emptyStructType = reflect.TypeOf(struct{}{})
configsType = reflect.TypeOf(Configs{})
)
func Register(cfg Config, ty Type) {
registerIntegration(cfg, cfg.Name(), ty, nil)
}
func registerIntegration(v interface{}, name string, ty Type, upgrader UpgradeFunc) {
if reflect.TypeOf(v).Kind() != reflect.Ptr {
panic(fmt.Sprintf("Register must be given a pointer, got %T", v))
}
if _, exist := integrationByName[name]; exist {
panic(fmt.Sprintf("Integration %q registered twice", name))
}
integrationByName[name] = v
registered = append(registered, v)
configTy := reflect.TypeOf(v)
integrationTypes[configTy] = ty
upgraders[configTy] = upgrader
nameByType[configTy] = name
}
func RegisterLegacy(cfg v1.Config, ty Type, upgrader UpgradeFunc) {
realConfig := upgrader(cfg, common.MetricsConfig{})
registerIntegration(cfg, realConfig.Name(), ty, upgrader)
}
type UpgradeFunc func(cfg v1.Config, common common.MetricsConfig) UpgradedConfig
type UpgradedConfig interface {
Config
LegacyConfig() (v1.Config, common.MetricsConfig)
}
type Type int
const (
TypeInvalid Type = iota
TypeSingleton
TypeMultiplex
TypeEither
)
func setRegistered(t *testing.T, cc map[Config]Type) {
clear := func() {
integrationByName = make(map[string]interface{})
integrationTypes = make(map[reflect.Type]Type)
registered = registered[:0]
upgraders = make(map[reflect.Type]UpgradeFunc)
nameByType = make(map[reflect.Type]string)
}
t.Cleanup(clear)
clear()
for c, t := range cc {
Register(c, t)
}
}
func Registered() []Config {
res := make([]Config, 0, len(registered))
for _, r := range registered {
res = append(res, cloneConfig(r))
}
return res
}
func RegisteredType(c Config) (Type, bool) {
cType := reflect.TypeOf(c)
if cType.Kind() != reflect.Ptr {
cType = reflect.PtrTo(cType)
}
t, ok := integrationTypes[cType]
return t, ok
}
func cloneConfig(r interface{}) Config {
switch v := r.(type) {
case Config:
return cloneValue(v).(Config)
case v1.Config:
mut, ok := upgraders[reflect.TypeOf(v)]
if !ok || mut == nil {
panic(fmt.Sprintf("Could not find transformer for legacy integration %T", r))
}
return mut(cloneValue(r).(v1.Config), common.MetricsConfig{})
default:
panic(fmt.Sprintf("unexpected type %T", r))
}
}
func cloneValue(in interface{}) interface{} {
return reflect.New(reflect.TypeOf(in).Elem()).Interface()
}
type Configs []Config
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 (
outType = getConfigTypeForIntegrations(inType)
outPointer = reflect.New(outType)
outVal = outPointer.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 outType.Field(i).PkgPath != "" {
continue
}
outVal.Field(i).Set(inVal.Field(i))
}
if configs == nil {
return nil, fmt.Errorf("integrations: Configs field not found in type: %T", v)
}
uniqueSingletons := make(map[string]struct{})
for _, c := range configs {
fieldName := c.Name()
var data interface{} = c
if wc, ok := c.(UpgradedConfig); ok {
data, _ = wc.LegacyConfig()
}
integrationType, ok := integrationTypes[reflect.TypeOf(data)]
if !ok {
panic(fmt.Sprintf("config not registered: %T", data))
}
if _, exists := uniqueSingletons[fieldName]; exists {
return nil, fmt.Errorf("integration %q may not be defined more than once", fieldName)
}
uniqueSingletons[fieldName] = struct{}{}
var (
bb []byte
err error
)
switch v := c.(type) {
case UpgradedConfig:
inner, common := v.LegacyConfig()
bb, err = util.MarshalYAMLMerged(common, inner)
default:
bb, err = yaml.Marshal(v)
}
if err != nil {
return nil, fmt.Errorf("failed to marshal integration %q: %w", fieldName, err)
}
raw := util.RawYAML(bb)
switch integrationType {
case TypeSingleton:
field := outVal.FieldByName("XXX_Config_" + fieldName)
field.Set(reflect.ValueOf(&raw))
case TypeMultiplex, TypeEither:
field := outVal.FieldByName("XXX_Configs_" + fieldName)
field.Set(reflect.Append(field, reflect.ValueOf(&raw)))
}
}
return outPointer.Interface(), nil
}
func UnmarshalYAML(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(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))
}
for i := outVal.NumField(); i < cfgVal.NumField(); i++ {
fieldType := cfgVal.Type().Field(i)
field := cfgVal.Field(i)
if field.IsNil() {
continue
}
switch field.Kind() {
case reflect.Slice:
configName := strings.TrimPrefix(fieldType.Name, "XXX_Configs_")
configReference, ok := integrationByName[configName]
if !ok {
return fmt.Errorf("integration %q not registered", configName)
}
for i := 0; i < field.Len(); i++ {
if field.Index(i).IsNil() {
continue
}
raw := field.Index(i).Interface().(*util.RawYAML)
c, err := deferredConfigUnmarshal(*raw, configReference)
if err != nil {
return err
}
*configs = append(*configs, c)
}
default:
configName := strings.TrimPrefix(fieldType.Name, "XXX_Config_")
configReference, ok := integrationByName[configName]
if !ok {
return fmt.Errorf("integration %q not registered", configName)
}
raw := field.Interface().(*util.RawYAML)
c, err := deferredConfigUnmarshal(*raw, configReference)
if err != nil {
return err
}
*configs = append(*configs, c)
}
}
return nil
}
func deferredConfigUnmarshal(raw util.RawYAML, ref interface{}) (Config, error) {
switch ref := ref.(type) {
case Config:
out := cloneValue(ref).(Config)
err := yaml.UnmarshalStrict(raw, out)
return out, err
case v1.Config:
var (
common common.MetricsConfig
out = cloneValue(ref).(v1.Config)
)
mut, ok := upgraders[reflect.TypeOf(out)]
if !ok {
panic(fmt.Sprintf("unexpected type %T", ref))
}
err := util.UnmarshalYAMLMerged(raw, &common, out)
return mut(out, common), err
default:
panic(fmt.Sprintf("unexpected type %T", ref))
}
}
func getConfigTypeForIntegrations(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 _, reg := range registered {
configTy := reflect.TypeOf(reg)
fieldName := nameByType[configTy]
singletonType := reflect.PtrTo(reflect.TypeOf(util.RawYAML{}))
fields = append(fields, reflect.StructField{
Name: "XXX_Config_" + fieldName,
Tag: reflect.StructTag(fmt.Sprintf(`yaml:"%s,omitempty"`, fieldName)),
Type: singletonType,
})
fields = append(fields, reflect.StructField{
Name: "XXX_Configs_" + fieldName,
Tag: reflect.StructTag(fmt.Sprintf(`yaml:"%s_configs,omitempty"`, fieldName)),
Type: reflect.SliceOf(singletonType),
})
}
return reflect.StructOf(fields)
}
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
}