github.com/yandex/pandora@v0.5.32/core/import/import.go (about)

     1  package coreimport
     2  
     3  import (
     4  	"reflect"
     5  
     6  	"github.com/spf13/afero"
     7  	"github.com/yandex/pandora/core"
     8  	"github.com/yandex/pandora/core/aggregator"
     9  	"github.com/yandex/pandora/core/aggregator/netsample"
    10  	"github.com/yandex/pandora/core/config"
    11  	"github.com/yandex/pandora/core/datasink"
    12  	"github.com/yandex/pandora/core/datasource"
    13  	"github.com/yandex/pandora/core/plugin"
    14  	"github.com/yandex/pandora/core/plugin/pluginconfig"
    15  	"github.com/yandex/pandora/core/provider"
    16  	"github.com/yandex/pandora/core/register"
    17  	"github.com/yandex/pandora/core/schedule"
    18  	"github.com/yandex/pandora/lib/confutil"
    19  	"github.com/yandex/pandora/lib/tag"
    20  	"go.uber.org/zap"
    21  )
    22  
    23  const (
    24  	fileDataKey          = "file"
    25  	compositeScheduleKey = "composite"
    26  )
    27  
    28  // getter for fs to avoid afero dependency in custom guns
    29  func GetFs() afero.Fs {
    30  	return afero.NewOsFs()
    31  }
    32  
    33  func Import(fs afero.Fs) {
    34  
    35  	register.DataSink(fileDataKey, func(conf datasink.FileConfig) core.DataSink {
    36  		return datasink.NewFile(fs, conf)
    37  	})
    38  	const (
    39  		stdoutSinkKey = "stdout"
    40  		stderrSinkKey = "stderr"
    41  	)
    42  	register.DataSink(stdoutSinkKey, datasink.NewStdout)
    43  	register.DataSink(stderrSinkKey, datasink.NewStderr)
    44  	AddSinkConfigHook(func(str string) (ok bool, pluginType string, _ map[string]interface{}) {
    45  		for _, key := range []string{stdoutSinkKey, stderrSinkKey} {
    46  			if str == key {
    47  				return true, key, nil
    48  			}
    49  		}
    50  		return
    51  	})
    52  
    53  	register.DataSource(fileDataKey, func(conf datasource.FileConfig) core.DataSource {
    54  		return datasource.NewFile(fs, conf)
    55  	})
    56  	const (
    57  		stdinSourceKey = "stdin"
    58  	)
    59  	register.DataSource(stdinSourceKey, datasource.NewStdin)
    60  	AddSinkConfigHook(func(str string) (ok bool, pluginType string, _ map[string]interface{}) {
    61  		if str != stdinSourceKey {
    62  			return
    63  		}
    64  		return true, stdinSourceKey, nil
    65  	})
    66  	register.DataSource("inline", datasource.NewInline)
    67  
    68  	// NOTE(skipor): json provider SHOULD NOT used normally. Register your own, that will return
    69  	// type that you need, but untyped map.
    70  	RegisterCustomJSONProvider("json", func() core.Ammo { return map[string]interface{}{} })
    71  
    72  	register.Provider("dummy", func() core.Provider {
    73  		return provider.Dummy{}
    74  	})
    75  
    76  	register.Aggregator("phout", func(conf netsample.PhoutConfig) (core.Aggregator, error) {
    77  		a, err := netsample.NewPhout(fs, conf)
    78  		return netsample.WrapAggregator(a), err
    79  	}, netsample.DefaultPhoutConfig)
    80  	register.Aggregator("jsonlines", aggregator.NewJSONLinesAggregator, aggregator.DefaultJSONLinesAggregatorConfig)
    81  	register.Aggregator("json", aggregator.NewJSONLinesAggregator, aggregator.DefaultJSONLinesAggregatorConfig) // TODO(skipor): should be done via alias, but we don't have them yet
    82  	register.Aggregator("log", aggregator.NewLog)
    83  	register.Aggregator("discard", aggregator.NewDiscard)
    84  
    85  	register.Limiter("line", schedule.NewLineConf)
    86  	register.Limiter("const", schedule.NewConstConf)
    87  	register.Limiter("once", schedule.NewOnceConf)
    88  	register.Limiter("unlimited", schedule.NewUnlimitedConf)
    89  	register.Limiter("step", schedule.NewStepConf)
    90  	register.Limiter("instance_step", schedule.NewInstanceStepConf)
    91  	register.Limiter(compositeScheduleKey, schedule.NewCompositeConf)
    92  
    93  	config.AddTypeHook(sinkStringHook)
    94  	config.AddTypeHook(scheduleSliceToCompositeConfigHook)
    95  
    96  	confutil.RegisterTagResolver("", confutil.EnvTagResolver)
    97  	confutil.RegisterTagResolver("ENV", confutil.EnvTagResolver)
    98  	confutil.RegisterTagResolver("PROPERTY", confutil.PropertyTagResolver)
    99  
   100  	// Required for decoding plugins. Need to be added after Composite Schedule hacky hook.
   101  	pluginconfig.AddHooks()
   102  }
   103  
   104  var (
   105  	scheduleType   = plugin.PtrType((*core.Schedule)(nil))
   106  	dataSinkType   = plugin.PtrType((*core.DataSink)(nil))
   107  	dataSourceType = plugin.PtrType((*core.DataSource)(nil))
   108  )
   109  
   110  func isPluginOrFactory(expectedPluginType, actualType reflect.Type) bool {
   111  	if actualType.Kind() != reflect.Interface && actualType.Kind() != reflect.Func {
   112  		return false
   113  	}
   114  	factoryPluginType, isPluginFactory := plugin.FactoryPluginType(actualType)
   115  	return actualType == expectedPluginType || isPluginFactory && factoryPluginType == expectedPluginType
   116  }
   117  
   118  type PluginConfigStringHook func(str string) (ok bool, pluginType string, conf map[string]interface{})
   119  
   120  var (
   121  	dataSinkConfigHooks   []PluginConfigStringHook
   122  	dataSourceConfigHooks []PluginConfigStringHook
   123  )
   124  
   125  func AddSinkConfigHook(hook PluginConfigStringHook) {
   126  	dataSinkConfigHooks = append(dataSinkConfigHooks, hook)
   127  }
   128  
   129  func AddSourceConfigHook(hook PluginConfigStringHook) {
   130  	dataSourceConfigHooks = append(dataSourceConfigHooks, hook)
   131  }
   132  
   133  func RegisterCustomJSONProvider(name string, newAmmo func() core.Ammo) {
   134  	register.Provider(name, func(conf provider.JSONProviderConfig) core.Provider {
   135  		return provider.NewJSONProvider(newAmmo, conf)
   136  	}, provider.DefaultJSONProviderConfig)
   137  }
   138  
   139  // sourceStringHook helps to decode string as core.DataSource plugin.
   140  // Try use source hooks and use file as fallback.
   141  func sourceStringHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
   142  	if f.Kind() != reflect.String {
   143  		return data, nil
   144  	}
   145  	if !isPluginOrFactory(dataSourceType, t) {
   146  		return data, nil
   147  	}
   148  	if tag.Debug {
   149  		zap.L().Debug("DataSource string hook triggered")
   150  	}
   151  	var (
   152  		ok         bool
   153  		pluginType string
   154  		conf       map[string]interface{}
   155  	)
   156  	dataStr := data.(string)
   157  
   158  	for _, hook := range dataSourceConfigHooks {
   159  		ok, pluginType, conf = hook(dataStr)
   160  		zap.L().Debug("Source hooked", zap.String("plugin", pluginType))
   161  		if ok {
   162  			break
   163  		}
   164  	}
   165  
   166  	if !ok {
   167  		zap.L().Debug("Consider source as a file", zap.String("source", dataStr))
   168  		pluginType = fileDataKey
   169  		conf = map[string]interface{}{
   170  			"path": data,
   171  		}
   172  	}
   173  
   174  	if conf == nil {
   175  		conf = make(map[string]interface{})
   176  	}
   177  	conf[pluginconfig.PluginNameKey] = pluginType
   178  
   179  	if tag.Debug {
   180  		zap.L().Debug("Hooked DataSource config", zap.Any("config", conf))
   181  	}
   182  	return conf, nil
   183  }
   184  
   185  // sinkStringHook helps to decode string as core.DataSink plugin.
   186  // Try use sink hooks and use file as fallback.
   187  func sinkStringHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
   188  	if f.Kind() != reflect.String {
   189  		return data, nil
   190  	}
   191  	if !isPluginOrFactory(dataSinkType, t) {
   192  		return data, nil
   193  	}
   194  	if tag.Debug {
   195  		zap.L().Debug("DataSink string hook triggered")
   196  	}
   197  	var (
   198  		ok         bool
   199  		pluginType string
   200  		conf       map[string]interface{}
   201  	)
   202  	dataStr := data.(string)
   203  
   204  	for _, hook := range dataSinkConfigHooks {
   205  		ok, pluginType, conf = hook(dataStr)
   206  		zap.L().Debug("Sink hooked", zap.String("plugin", pluginType))
   207  		if ok {
   208  			break
   209  		}
   210  	}
   211  
   212  	if !ok {
   213  		zap.L().Debug("Consider sink as a file", zap.String("source", dataStr))
   214  		pluginType = fileDataKey
   215  		conf = map[string]interface{}{
   216  			"path": data,
   217  		}
   218  	}
   219  
   220  	if conf == nil {
   221  		conf = make(map[string]interface{})
   222  	}
   223  	conf[pluginconfig.PluginNameKey] = pluginType
   224  
   225  	if tag.Debug {
   226  		zap.L().Debug("Hooked DataSink config", zap.Any("config", conf))
   227  	}
   228  	return conf, nil
   229  }
   230  
   231  // scheduleSliceToCompositeConfigHook helps to decode []interface{} as core.Schedule plugin.
   232  func scheduleSliceToCompositeConfigHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
   233  	if f.Kind() != reflect.Slice {
   234  		return data, nil
   235  	}
   236  	if t.Kind() != reflect.Interface && t.Kind() != reflect.Func {
   237  		return data, nil
   238  	}
   239  	if !isPluginOrFactory(scheduleType, t) {
   240  		return data, nil
   241  	}
   242  	if tag.Debug {
   243  		zap.L().Debug("Composite schedule hook triggered")
   244  	}
   245  	return map[string]interface{}{
   246  		pluginconfig.PluginNameKey: compositeScheduleKey,
   247  		"nested":                   data,
   248  	}, nil
   249  }