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 }