github.com/Jeffail/benthos/v3@v3.65.0/lib/output/constructor.go (about) 1 package output 2 3 import ( 4 "fmt" 5 6 "github.com/Jeffail/benthos/v3/internal/component/output" 7 "github.com/Jeffail/benthos/v3/internal/docs" 8 "github.com/Jeffail/benthos/v3/internal/interop" 9 "github.com/Jeffail/benthos/v3/lib/log" 10 "github.com/Jeffail/benthos/v3/lib/metrics" 11 "github.com/Jeffail/benthos/v3/lib/output/writer" 12 "github.com/Jeffail/benthos/v3/lib/pipeline" 13 "github.com/Jeffail/benthos/v3/lib/processor" 14 "github.com/Jeffail/benthos/v3/lib/types" 15 "github.com/Jeffail/benthos/v3/lib/util/config" 16 "gopkg.in/yaml.v3" 17 ) 18 19 //------------------------------------------------------------------------------ 20 21 // Category describes the general category of an output. 22 type Category string 23 24 // Output categories 25 var ( 26 CategoryLocal Category = "Local" 27 CategoryAWS Category = "AWS" 28 CategoryGCP Category = "GCP" 29 CategoryAzure Category = "Azure" 30 CategoryServices Category = "Services" 31 CategoryNetwork Category = "Network" 32 CategoryUtility Category = "Utility" 33 ) 34 35 // TypeSpec is a constructor and a usage description for each output type. 36 type TypeSpec struct { 37 constructor ConstructorFunc 38 39 // Async indicates whether this output benefits from sending multiple 40 // messages asynchronously over the protocol. 41 Async bool 42 43 // Batches indicates whether this output benefits from batching of messages. 44 Batches bool 45 46 Status docs.Status 47 Summary string 48 Description string 49 Categories []Category 50 Footnotes string 51 config docs.FieldSpec 52 FieldSpecs docs.FieldSpecs 53 Examples []docs.AnnotatedExample 54 Version string 55 } 56 57 // AppendProcessorsFromConfig takes a variant arg of pipeline constructor 58 // functions and returns a new slice of them where the processors of the 59 // provided output configuration will also be initialized. 60 func AppendProcessorsFromConfig( 61 conf Config, 62 mgr types.Manager, 63 log log.Modular, 64 stats metrics.Type, 65 pipelines ...types.PipelineConstructorFunc, 66 ) []types.PipelineConstructorFunc { 67 if len(conf.Processors) > 0 { 68 pipelines = append(pipelines, []types.PipelineConstructorFunc{func(i *int) (types.Pipeline, error) { 69 if i == nil { 70 procs := 0 71 i = &procs 72 } 73 processors := make([]types.Processor, len(conf.Processors)) 74 for j, procConf := range conf.Processors { 75 pMgr, pLog, pMetrics := interop.LabelChild(fmt.Sprintf("processor.%v", *i), mgr, log, stats) 76 var err error 77 processors[j], err = processor.New(procConf, pMgr, pLog, pMetrics) 78 if err != nil { 79 return nil, fmt.Errorf("failed to create processor '%v': %v", procConf.Type, err) 80 } 81 *i++ 82 } 83 return pipeline.NewProcessor(log, stats, processors...), nil 84 }}...) 85 } 86 return pipelines 87 } 88 89 func fromSimpleConstructor(fn func(Config, types.Manager, log.Modular, metrics.Type) (Type, error)) ConstructorFunc { 90 return func( 91 conf Config, 92 mgr types.Manager, 93 log log.Modular, 94 stats metrics.Type, 95 pipelines ...types.PipelineConstructorFunc, 96 ) (Type, error) { 97 output, err := fn(conf, mgr, log, stats) 98 if err != nil { 99 return nil, fmt.Errorf("failed to create output '%v': %w", conf.Type, err) 100 } 101 pipelines = AppendProcessorsFromConfig(conf, mgr, log, stats, pipelines...) 102 return WrapWithPipelines(output, pipelines...) 103 } 104 } 105 106 // ConstructorFunc is a func signature able to construct an output. 107 type ConstructorFunc func(Config, types.Manager, log.Modular, metrics.Type, ...types.PipelineConstructorFunc) (Type, error) 108 109 // WalkConstructors iterates each component constructor. 110 func WalkConstructors(fn func(ConstructorFunc, docs.ComponentSpec)) { 111 inferred := docs.ComponentFieldsFromConf(NewConfig()) 112 for k, v := range Constructors { 113 conf := v.config 114 if len(v.FieldSpecs) > 0 { 115 conf = docs.FieldComponent().WithChildren(v.FieldSpecs.DefaultAndTypeFrom(inferred[k])...) 116 } else { 117 conf.Children = conf.Children.DefaultAndTypeFrom(inferred[k]) 118 } 119 spec := docs.ComponentSpec{ 120 Type: docs.TypeOutput, 121 Name: k, 122 Summary: v.Summary, 123 Description: v.Description, 124 Footnotes: v.Footnotes, 125 Config: conf, 126 Examples: v.Examples, 127 Status: v.Status, 128 Version: v.Version, 129 } 130 if len(v.Categories) > 0 { 131 spec.Categories = make([]string, 0, len(v.Categories)) 132 for _, cat := range v.Categories { 133 spec.Categories = append(spec.Categories, string(cat)) 134 } 135 } 136 spec.Description = output.Description(v.Async, v.Batches, spec.Description) 137 fn(v.constructor, spec) 138 } 139 for k, v := range pluginSpecs { 140 spec := docs.ComponentSpec{ 141 Type: docs.TypeOutput, 142 Name: k, 143 Status: docs.StatusExperimental, 144 Plugin: true, 145 Config: docs.FieldComponent().Unlinted(), 146 } 147 fn(v.constructor, spec) 148 } 149 } 150 151 // Constructors is a map of all output types with their specs. 152 var Constructors = map[string]TypeSpec{} 153 154 //------------------------------------------------------------------------------ 155 156 // String constants representing each output type. 157 // Deprecated: Do not add new components here. Instead, use the public plugin 158 // APIs. Examples can be found in: ./internal/impl 159 const ( 160 TypeAMQP = "amqp" 161 TypeAMQP09 = "amqp_0_9" 162 TypeAMQP1 = "amqp_1" 163 TypeAWSDynamoDB = "aws_dynamodb" 164 TypeAWSKinesis = "aws_kinesis" 165 TypeAWSKinesisFirehose = "aws_kinesis_firehose" 166 TypeAWSS3 = "aws_s3" 167 TypeAWSSNS = "aws_sns" 168 TypeAWSSQS = "aws_sqs" 169 TypeAzureBlobStorage = "azure_blob_storage" 170 TypeAzureQueueStorage = "azure_queue_storage" 171 TypeAzureTableStorage = "azure_table_storage" 172 TypeBlobStorage = "blob_storage" 173 TypeBroker = "broker" 174 TypeCache = "cache" 175 TypeCassandra = "cassandra" 176 TypeDrop = "drop" 177 TypeDropOn = "drop_on" 178 TypeDropOnError = "drop_on_error" 179 TypeDynamic = "dynamic" 180 TypeDynamoDB = "dynamodb" 181 TypeElasticsearch = "elasticsearch" 182 TypeFallback = "fallback" 183 TypeFile = "file" 184 TypeFiles = "files" 185 TypeGCPCloudStorage = "gcp_cloud_storage" 186 TypeGCPPubSub = "gcp_pubsub" 187 TypeHDFS = "hdfs" 188 TypeHTTPClient = "http_client" 189 TypeHTTPServer = "http_server" 190 TypeInproc = "inproc" 191 TypeKafka = "kafka" 192 TypeKinesis = "kinesis" 193 TypeKinesisFirehose = "kinesis_firehose" 194 TypeMongoDB = "mongodb" 195 TypeMQTT = "mqtt" 196 TypeNanomsg = "nanomsg" 197 TypeNATS = "nats" 198 TypeNATSJetStream = "nats_jetstream" 199 TypeNATSStream = "nats_stream" 200 TypeNSQ = "nsq" 201 TypePulsar = "pulsar" 202 TypeRedisHash = "redis_hash" 203 TypeRedisList = "redis_list" 204 TypeRedisPubSub = "redis_pubsub" 205 TypeRedisStreams = "redis_streams" 206 TypeReject = "reject" 207 TypeResource = "resource" 208 TypeRetry = "retry" 209 TypeS3 = "s3" 210 TypeSFTP = "sftp" 211 TypeSNS = "sns" 212 TypeSQL = "sql" 213 TypeSQS = "sqs" 214 TypeSTDOUT = "stdout" 215 TypeSubprocess = "subprocess" 216 TypeSwitch = "switch" 217 TypeSyncResponse = "sync_response" 218 TypeTableStorage = "table_storage" 219 TypeTCP = "tcp" 220 TypeTry = "try" 221 TypeUDP = "udp" 222 TypeSocket = "socket" 223 TypeWebsocket = "websocket" 224 TypeZMQ4 = "zmq4" 225 ) 226 227 //------------------------------------------------------------------------------ 228 229 // Config is the all encompassing configuration struct for all output types. 230 // Deprecated: Do not add new components here. Instead, use the public plugin 231 // APIs. Examples can be found in: ./internal/impl 232 type Config struct { 233 Label string `json:"label" yaml:"label"` 234 Type string `json:"type" yaml:"type"` 235 AMQP writer.AMQPConfig `json:"amqp" yaml:"amqp"` 236 AMQP09 writer.AMQPConfig `json:"amqp_0_9" yaml:"amqp_0_9"` 237 AMQP1 writer.AMQP1Config `json:"amqp_1" yaml:"amqp_1"` 238 AWSDynamoDB writer.DynamoDBConfig `json:"aws_dynamodb" yaml:"aws_dynamodb"` 239 AWSKinesis writer.KinesisConfig `json:"aws_kinesis" yaml:"aws_kinesis"` 240 AWSKinesisFirehose writer.KinesisFirehoseConfig `json:"aws_kinesis_firehose" yaml:"aws_kinesis_firehose"` 241 AWSS3 writer.AmazonS3Config `json:"aws_s3" yaml:"aws_s3"` 242 AWSSNS writer.SNSConfig `json:"aws_sns" yaml:"aws_sns"` 243 AWSSQS writer.AmazonSQSConfig `json:"aws_sqs" yaml:"aws_sqs"` 244 AzureBlobStorage writer.AzureBlobStorageConfig `json:"azure_blob_storage" yaml:"azure_blob_storage"` 245 AzureQueueStorage writer.AzureQueueStorageConfig `json:"azure_queue_storage" yaml:"azure_queue_storage"` 246 AzureTableStorage writer.AzureTableStorageConfig `json:"azure_table_storage" yaml:"azure_table_storage"` 247 BlobStorage writer.AzureBlobStorageConfig `json:"blob_storage" yaml:"blob_storage"` 248 Broker BrokerConfig `json:"broker" yaml:"broker"` 249 Cache writer.CacheConfig `json:"cache" yaml:"cache"` 250 Cassandra CassandraConfig `json:"cassandra" yaml:"cassandra"` 251 Drop writer.DropConfig `json:"drop" yaml:"drop"` 252 DropOn DropOnConfig `json:"drop_on" yaml:"drop_on"` 253 DropOnError DropOnErrorConfig `json:"drop_on_error" yaml:"drop_on_error"` 254 Dynamic DynamicConfig `json:"dynamic" yaml:"dynamic"` 255 DynamoDB writer.DynamoDBConfig `json:"dynamodb" yaml:"dynamodb"` 256 Elasticsearch writer.ElasticsearchConfig `json:"elasticsearch" yaml:"elasticsearch"` 257 Fallback TryConfig `json:"fallback" yaml:"fallback"` 258 File FileConfig `json:"file" yaml:"file"` 259 Files writer.FilesConfig `json:"files" yaml:"files"` 260 GCPCloudStorage GCPCloudStorageConfig `json:"gcp_cloud_storage" yaml:"gcp_cloud_storage"` 261 GCPPubSub writer.GCPPubSubConfig `json:"gcp_pubsub" yaml:"gcp_pubsub"` 262 HDFS writer.HDFSConfig `json:"hdfs" yaml:"hdfs"` 263 HTTPClient writer.HTTPClientConfig `json:"http_client" yaml:"http_client"` 264 HTTPServer HTTPServerConfig `json:"http_server" yaml:"http_server"` 265 Inproc InprocConfig `json:"inproc" yaml:"inproc"` 266 Kafka writer.KafkaConfig `json:"kafka" yaml:"kafka"` 267 Kinesis writer.KinesisConfig `json:"kinesis" yaml:"kinesis"` 268 KinesisFirehose writer.KinesisFirehoseConfig `json:"kinesis_firehose" yaml:"kinesis_firehose"` 269 MongoDB MongoDBConfig `json:"mongodb" yaml:"mongodb"` 270 MQTT writer.MQTTConfig `json:"mqtt" yaml:"mqtt"` 271 Nanomsg writer.NanomsgConfig `json:"nanomsg" yaml:"nanomsg"` 272 NATS writer.NATSConfig `json:"nats" yaml:"nats"` 273 NATSJetStream NATSJetStreamConfig `json:"nats_jetstream" yaml:"nats_jetstream"` 274 NATSStream writer.NATSStreamConfig `json:"nats_stream" yaml:"nats_stream"` 275 NSQ writer.NSQConfig `json:"nsq" yaml:"nsq"` 276 Plugin interface{} `json:"plugin,omitempty" yaml:"plugin,omitempty"` 277 Pulsar PulsarConfig `json:"pulsar" yaml:"pulsar"` 278 RedisHash writer.RedisHashConfig `json:"redis_hash" yaml:"redis_hash"` 279 RedisList writer.RedisListConfig `json:"redis_list" yaml:"redis_list"` 280 RedisPubSub writer.RedisPubSubConfig `json:"redis_pubsub" yaml:"redis_pubsub"` 281 RedisStreams writer.RedisStreamsConfig `json:"redis_streams" yaml:"redis_streams"` 282 Reject RejectConfig `json:"reject" yaml:"reject"` 283 Resource string `json:"resource" yaml:"resource"` 284 Retry RetryConfig `json:"retry" yaml:"retry"` 285 S3 writer.AmazonS3Config `json:"s3" yaml:"s3"` 286 SFTP SFTPConfig `json:"sftp" yaml:"sftp"` 287 SNS writer.SNSConfig `json:"sns" yaml:"sns"` 288 SQL SQLConfig `json:"sql" yaml:"sql"` 289 SQS writer.AmazonSQSConfig `json:"sqs" yaml:"sqs"` 290 STDOUT STDOUTConfig `json:"stdout" yaml:"stdout"` 291 Subprocess SubprocessConfig `json:"subprocess" yaml:"subprocess"` 292 Switch SwitchConfig `json:"switch" yaml:"switch"` 293 SyncResponse struct{} `json:"sync_response" yaml:"sync_response"` 294 TableStorage writer.AzureTableStorageConfig `json:"table_storage" yaml:"table_storage"` 295 TCP writer.TCPConfig `json:"tcp" yaml:"tcp"` 296 Try TryConfig `json:"try" yaml:"try"` 297 UDP writer.UDPConfig `json:"udp" yaml:"udp"` 298 Socket writer.SocketConfig `json:"socket" yaml:"socket"` 299 Websocket writer.WebsocketConfig `json:"websocket" yaml:"websocket"` 300 ZMQ4 *writer.ZMQ4Config `json:"zmq4,omitempty" yaml:"zmq4,omitempty"` 301 Processors []processor.Config `json:"processors" yaml:"processors"` 302 } 303 304 // NewConfig returns a configuration struct fully populated with default values. 305 // Deprecated: Do not add new components here. Instead, use the public plugin 306 // APIs. Examples can be found in: ./internal/impl 307 func NewConfig() Config { 308 return Config{ 309 Label: "", 310 Type: "stdout", 311 AMQP: writer.NewAMQPConfig(), 312 AMQP09: writer.NewAMQPConfig(), 313 AMQP1: writer.NewAMQP1Config(), 314 AWSDynamoDB: writer.NewDynamoDBConfig(), 315 AWSKinesis: writer.NewKinesisConfig(), 316 AWSKinesisFirehose: writer.NewKinesisFirehoseConfig(), 317 AWSS3: writer.NewAmazonS3Config(), 318 AWSSNS: writer.NewSNSConfig(), 319 AWSSQS: writer.NewAmazonSQSConfig(), 320 AzureBlobStorage: writer.NewAzureBlobStorageConfig(), 321 AzureQueueStorage: writer.NewAzureQueueStorageConfig(), 322 AzureTableStorage: writer.NewAzureTableStorageConfig(), 323 BlobStorage: writer.NewAzureBlobStorageConfig(), 324 Broker: NewBrokerConfig(), 325 Cache: writer.NewCacheConfig(), 326 Cassandra: NewCassandraConfig(), 327 Drop: writer.NewDropConfig(), 328 DropOn: NewDropOnConfig(), 329 DropOnError: NewDropOnErrorConfig(), 330 Dynamic: NewDynamicConfig(), 331 DynamoDB: writer.NewDynamoDBConfig(), 332 Elasticsearch: writer.NewElasticsearchConfig(), 333 Fallback: NewTryConfig(), 334 File: NewFileConfig(), 335 Files: writer.NewFilesConfig(), 336 GCPCloudStorage: NewGCPCloudStorageConfig(), 337 GCPPubSub: writer.NewGCPPubSubConfig(), 338 HDFS: writer.NewHDFSConfig(), 339 HTTPClient: writer.NewHTTPClientConfig(), 340 HTTPServer: NewHTTPServerConfig(), 341 Inproc: NewInprocConfig(), 342 Kafka: writer.NewKafkaConfig(), 343 Kinesis: writer.NewKinesisConfig(), 344 KinesisFirehose: writer.NewKinesisFirehoseConfig(), 345 MQTT: writer.NewMQTTConfig(), 346 MongoDB: NewMongoDBConfig(), 347 Nanomsg: writer.NewNanomsgConfig(), 348 NATS: writer.NewNATSConfig(), 349 NATSJetStream: NewNATSJetStreamConfig(), 350 NATSStream: writer.NewNATSStreamConfig(), 351 NSQ: writer.NewNSQConfig(), 352 Plugin: nil, 353 Pulsar: NewPulsarConfig(), 354 RedisHash: writer.NewRedisHashConfig(), 355 RedisList: writer.NewRedisListConfig(), 356 RedisPubSub: writer.NewRedisPubSubConfig(), 357 RedisStreams: writer.NewRedisStreamsConfig(), 358 Reject: NewRejectConfig(), 359 Resource: "", 360 Retry: NewRetryConfig(), 361 S3: writer.NewAmazonS3Config(), 362 SFTP: NewSFTPConfig(), 363 SNS: writer.NewSNSConfig(), 364 SQL: NewSQLConfig(), 365 SQS: writer.NewAmazonSQSConfig(), 366 STDOUT: NewSTDOUTConfig(), 367 Subprocess: NewSubprocessConfig(), 368 Switch: NewSwitchConfig(), 369 SyncResponse: struct{}{}, 370 TableStorage: writer.NewAzureTableStorageConfig(), 371 TCP: writer.NewTCPConfig(), 372 Try: NewTryConfig(), 373 UDP: writer.NewUDPConfig(), 374 Socket: writer.NewSocketConfig(), 375 Websocket: writer.NewWebsocketConfig(), 376 ZMQ4: writer.NewZMQ4Config(), 377 Processors: []processor.Config{}, 378 } 379 } 380 381 //------------------------------------------------------------------------------ 382 383 // SanitiseConfig returns a sanitised version of the Config, meaning sections 384 // that aren't relevant to behaviour are removed. 385 func SanitiseConfig(conf Config) (interface{}, error) { 386 return conf.Sanitised(false) 387 } 388 389 // Sanitised returns a sanitised version of the config, meaning sections that 390 // aren't relevant to behaviour are removed. Also optionally removes deprecated 391 // fields. 392 func (conf Config) Sanitised(removeDeprecated bool) (interface{}, error) { 393 outputMap, err := config.SanitizeComponent(conf) 394 if err != nil { 395 return nil, err 396 } 397 if spec, exists := pluginSpecs[conf.Type]; exists { 398 if spec.confSanitiser != nil { 399 outputMap["plugin"] = spec.confSanitiser(conf.Plugin) 400 } 401 } 402 if err := docs.SanitiseComponentConfig( 403 docs.TypeOutput, 404 map[string]interface{}(outputMap), 405 docs.ShouldDropDeprecated(removeDeprecated), 406 ); err != nil { 407 return nil, err 408 } 409 return outputMap, nil 410 } 411 412 //------------------------------------------------------------------------------ 413 414 // UnmarshalYAML ensures that when parsing configs that are in a map or slice 415 // the default values are still applied. 416 func (conf *Config) UnmarshalYAML(value *yaml.Node) error { 417 type confAlias Config 418 aliased := confAlias(NewConfig()) 419 420 err := value.Decode(&aliased) 421 if err != nil { 422 return fmt.Errorf("line %v: %v", value.Line, err) 423 } 424 425 var spec docs.ComponentSpec 426 if aliased.Type, spec, err = docs.GetInferenceCandidateFromYAML(nil, docs.TypeOutput, aliased.Type, value); err != nil { 427 return fmt.Errorf("line %v: %w", value.Line, err) 428 } 429 430 if spec.Plugin { 431 pluginNode, err := docs.GetPluginConfigYAML(aliased.Type, value) 432 if err != nil { 433 return fmt.Errorf("line %v: %v", value.Line, err) 434 } 435 if spec, exists := pluginSpecs[aliased.Type]; exists && spec.confConstructor != nil { 436 conf := spec.confConstructor() 437 if err = pluginNode.Decode(conf); err != nil { 438 return fmt.Errorf("line %v: %v", value.Line, err) 439 } 440 aliased.Plugin = conf 441 } else { 442 aliased.Plugin = &pluginNode 443 } 444 } else { 445 aliased.Plugin = nil 446 } 447 448 *conf = Config(aliased) 449 return nil 450 } 451 452 //------------------------------------------------------------------------------ 453 454 // New creates an output type based on an output configuration. 455 func New( 456 conf Config, 457 mgr types.Manager, 458 log log.Modular, 459 stats metrics.Type, 460 pipelines ...types.PipelineConstructorFunc, 461 ) (Type, error) { 462 if mgrV2, ok := mgr.(interface { 463 NewOutput(Config, ...types.PipelineConstructorFunc) (types.Output, error) 464 }); ok { 465 return mgrV2.NewOutput(conf, pipelines...) 466 } 467 if c, ok := Constructors[conf.Type]; ok { 468 return c.constructor(conf, mgr, log, stats, pipelines...) 469 } 470 if c, ok := pluginSpecs[conf.Type]; ok { 471 return c.constructor(conf, mgr, log, stats, pipelines...) 472 } 473 return nil, types.ErrInvalidOutputType 474 } 475 476 //------------------------------------------------------------------------------