github.com/Jeffail/benthos/v3@v3.65.0/public/service/stream_builder.go (about) 1 package service 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "net/http" 9 "os" 10 11 "github.com/Jeffail/benthos/v3/internal/bundle" 12 "github.com/Jeffail/benthos/v3/internal/bundle/tracing" 13 "github.com/Jeffail/benthos/v3/internal/docs" 14 "github.com/Jeffail/benthos/v3/lib/api" 15 "github.com/Jeffail/benthos/v3/lib/buffer" 16 "github.com/Jeffail/benthos/v3/lib/cache" 17 "github.com/Jeffail/benthos/v3/lib/config" 18 "github.com/Jeffail/benthos/v3/lib/input" 19 "github.com/Jeffail/benthos/v3/lib/log" 20 "github.com/Jeffail/benthos/v3/lib/manager" 21 "github.com/Jeffail/benthos/v3/lib/message" 22 "github.com/Jeffail/benthos/v3/lib/metrics" 23 "github.com/Jeffail/benthos/v3/lib/output" 24 "github.com/Jeffail/benthos/v3/lib/processor" 25 "github.com/Jeffail/benthos/v3/lib/ratelimit" 26 "github.com/Jeffail/benthos/v3/lib/response" 27 "github.com/Jeffail/benthos/v3/lib/stream" 28 "github.com/Jeffail/benthos/v3/lib/types" 29 "github.com/Jeffail/benthos/v3/lib/util/text" 30 "github.com/Jeffail/gabs/v2" 31 "github.com/gofrs/uuid" 32 "gopkg.in/yaml.v3" 33 ) 34 35 // StreamBuilder provides methods for building a Benthos stream configuration. 36 // When parsing Benthos configs this builder follows the schema and field 37 // defaults of a standard Benthos configuration. Environment variable 38 // interpolations are also parsed and resolved the same as regular configs. 39 // 40 // Benthos streams register HTTP endpoints by default that expose metrics and 41 // ready checks. If your intention is to execute multiple streams in the same 42 // process then it is recommended that you disable the HTTP server in config, or 43 // use `SetHTTPMux` with prefixed multiplexers in order to share it across the 44 // streams. 45 type StreamBuilder struct { 46 http api.Config 47 threads int 48 inputs []input.Config 49 buffer buffer.Config 50 processors []processor.Config 51 outputs []output.Config 52 resources manager.ResourceConfig 53 metrics metrics.Config 54 logger log.Config 55 56 producerChan chan types.Transaction 57 producerID string 58 consumerFunc MessageBatchHandlerFunc 59 consumerID string 60 61 apiMut manager.APIReg 62 customLogger log.Modular 63 64 env *Environment 65 lintingDisabled bool 66 } 67 68 // NewStreamBuilder creates a new StreamBuilder. 69 func NewStreamBuilder() *StreamBuilder { 70 return &StreamBuilder{ 71 http: api.NewConfig(), 72 buffer: buffer.NewConfig(), 73 resources: manager.NewResourceConfig(), 74 metrics: metrics.NewConfig(), 75 logger: log.NewConfig(), 76 env: globalEnvironment, 77 } 78 } 79 80 func (s *StreamBuilder) getLintContext() docs.LintContext { 81 ctx := docs.NewLintContext() 82 ctx.DocsProvider = s.env.internal 83 ctx.BloblangEnv = s.env.getBloblangParserEnv().Deactivated() 84 return ctx 85 } 86 87 //------------------------------------------------------------------------------ 88 89 // DisableLinting configures the stream builder to no longer lint YAML configs, 90 // allowing you to add snippets of config to the builder without failing on 91 // linting rules. 92 func (s *StreamBuilder) DisableLinting() { 93 s.lintingDisabled = true 94 } 95 96 // SetThreads configures the number of pipeline processor threads should be 97 // configured. By default the number will be zero, which means the thread count 98 // will match the number of logical CPUs on the machine. 99 func (s *StreamBuilder) SetThreads(n int) { 100 s.threads = n 101 } 102 103 // PrintLogger is a simple Print based interface implemented by custom loggers. 104 type PrintLogger interface { 105 Printf(format string, v ...interface{}) 106 Println(v ...interface{}) 107 } 108 109 // SetPrintLogger sets a custom logger supporting a simple Print based interface 110 // to be used by stream components. This custom logger will override any logging 111 // fields set via config. 112 func (s *StreamBuilder) SetPrintLogger(l PrintLogger) { 113 s.customLogger = log.Wrap(l) 114 } 115 116 // HTTPMultiplexer is an interface supported by most HTTP multiplexers. 117 type HTTPMultiplexer interface { 118 HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) 119 } 120 121 type muxWrapper struct { 122 m HTTPMultiplexer 123 } 124 125 func (w *muxWrapper) RegisterEndpoint(path, desc string, h http.HandlerFunc) { 126 w.m.HandleFunc(path, h) 127 } 128 129 // SetHTTPMux sets an HTTP multiplexer to be used by stream components when 130 // registering endpoints instead of a new server spawned following the `http` 131 // fields of a Benthos config. 132 func (s *StreamBuilder) SetHTTPMux(m HTTPMultiplexer) { 133 s.apiMut = &muxWrapper{m} 134 } 135 136 //------------------------------------------------------------------------------ 137 138 // AddProducerFunc adds an input to the builder that allows you to write 139 // messages directly into the stream with a closure function. If any other input 140 // has or will be added to the stream builder they will be automatically 141 // composed within a broker when the pipeline is built. 142 // 143 // The returned MessageHandlerFunc can be called concurrently from any number of 144 // goroutines, and each call will block until the message is successfully 145 // delivered downstream, was rejected (or otherwise could not be delivered) or 146 // the context is cancelled. 147 // 148 // Only one producer func can be added to a stream builder, and subsequent calls 149 // will return an error. 150 func (s *StreamBuilder) AddProducerFunc() (MessageHandlerFunc, error) { 151 if s.producerChan != nil { 152 return nil, errors.New("unable to add multiple producer funcs to a stream builder") 153 } 154 155 uuid, err := uuid.NewV4() 156 if err != nil { 157 return nil, fmt.Errorf("failed to generate a producer uuid: %w", err) 158 } 159 160 tChan := make(chan types.Transaction) 161 s.producerChan = tChan 162 s.producerID = uuid.String() 163 164 conf := input.NewConfig() 165 conf.Type = input.TypeInproc 166 conf.Inproc = input.InprocConfig(s.producerID) 167 s.inputs = append(s.inputs, conf) 168 169 return func(ctx context.Context, m *Message) error { 170 tmpMsg := message.New(nil) 171 tmpMsg.Append(m.part) 172 resChan := make(chan types.Response) 173 select { 174 case tChan <- types.NewTransaction(tmpMsg, resChan): 175 case <-ctx.Done(): 176 return ctx.Err() 177 } 178 select { 179 case res := <-resChan: 180 return res.Error() 181 case <-ctx.Done(): 182 return ctx.Err() 183 } 184 }, nil 185 } 186 187 // AddBatchProducerFunc adds an input to the builder that allows you to write 188 // message batches directly into the stream with a closure function. If any 189 // other input has or will be added to the stream builder they will be 190 // automatically composed within a broker when the pipeline is built. 191 // 192 // The returned MessageBatchHandlerFunc can be called concurrently from any 193 // number of goroutines, and each call will block until all messages within the 194 // batch are successfully delivered downstream, were rejected (or otherwise 195 // could not be delivered) or the context is cancelled. 196 // 197 // Only one producer func can be added to a stream builder, and subsequent calls 198 // will return an error. 199 func (s *StreamBuilder) AddBatchProducerFunc() (MessageBatchHandlerFunc, error) { 200 if s.producerChan != nil { 201 return nil, errors.New("unable to add multiple producer funcs to a stream builder") 202 } 203 204 uuid, err := uuid.NewV4() 205 if err != nil { 206 return nil, fmt.Errorf("failed to generate a producer uuid: %w", err) 207 } 208 209 tChan := make(chan types.Transaction) 210 s.producerChan = tChan 211 s.producerID = uuid.String() 212 213 conf := input.NewConfig() 214 conf.Type = input.TypeInproc 215 conf.Inproc = input.InprocConfig(s.producerID) 216 s.inputs = append(s.inputs, conf) 217 218 return func(ctx context.Context, b MessageBatch) error { 219 tmpMsg := message.New(nil) 220 for _, m := range b { 221 tmpMsg.Append(m.part) 222 } 223 resChan := make(chan types.Response) 224 select { 225 case tChan <- types.NewTransaction(tmpMsg, resChan): 226 case <-ctx.Done(): 227 return ctx.Err() 228 } 229 select { 230 case res := <-resChan: 231 return res.Error() 232 case <-ctx.Done(): 233 return ctx.Err() 234 } 235 }, nil 236 } 237 238 // AddInputYAML parses an input YAML configuration and adds it to the builder. 239 // If more than one input configuration is added they will automatically be 240 // composed within a broker when the pipeline is built. 241 func (s *StreamBuilder) AddInputYAML(conf string) error { 242 nconf, err := getYAMLNode([]byte(conf)) 243 if err != nil { 244 return err 245 } 246 247 if err := s.lintYAMLComponent(nconf, docs.TypeInput); err != nil { 248 return err 249 } 250 251 iconf := input.NewConfig() 252 if err := nconf.Decode(&iconf); err != nil { 253 return err 254 } 255 256 s.inputs = append(s.inputs, iconf) 257 return nil 258 } 259 260 // AddProcessorYAML parses a processor YAML configuration and adds it to the 261 // builder to be executed within the pipeline.processors section, after all 262 // prior added processor configs. 263 func (s *StreamBuilder) AddProcessorYAML(conf string) error { 264 nconf, err := getYAMLNode([]byte(conf)) 265 if err != nil { 266 return err 267 } 268 269 if err := s.lintYAMLComponent(nconf, docs.TypeProcessor); err != nil { 270 return err 271 } 272 273 pconf := processor.NewConfig() 274 if err := nconf.Decode(&pconf); err != nil { 275 return err 276 } 277 278 s.processors = append(s.processors, pconf) 279 return nil 280 } 281 282 // AddConsumerFunc adds an output to the builder that executes a closure 283 // function argument for each message. If more than one output configuration is 284 // added they will automatically be composed within a fan out broker when the 285 // pipeline is built. 286 // 287 // The provided MessageHandlerFunc may be called from any number of goroutines, 288 // and therefore it is recommended to implement some form of throttling or mutex 289 // locking in cases where the call is non-blocking. 290 // 291 // Only one consumer can be added to a stream builder, and subsequent calls will 292 // return an error. 293 func (s *StreamBuilder) AddConsumerFunc(fn MessageHandlerFunc) error { 294 if s.consumerFunc != nil { 295 return errors.New("unable to add multiple producer funcs to a stream builder") 296 } 297 298 uuid, err := uuid.NewV4() 299 if err != nil { 300 return fmt.Errorf("failed to generate a consumer uuid: %w", err) 301 } 302 303 s.consumerFunc = func(c context.Context, mb MessageBatch) error { 304 for _, m := range mb { 305 if err := fn(c, m); err != nil { 306 return err 307 } 308 } 309 return nil 310 } 311 s.consumerID = uuid.String() 312 313 conf := output.NewConfig() 314 conf.Type = output.TypeInproc 315 conf.Inproc = output.InprocConfig(s.consumerID) 316 s.outputs = append(s.outputs, conf) 317 318 return nil 319 } 320 321 // AddBatchConsumerFunc adds an output to the builder that executes a closure 322 // function argument for each message batch. If more than one output 323 // configuration is added they will automatically be composed within a fan out 324 // broker when the pipeline is built. 325 // 326 // The provided MessageBatchHandlerFunc may be called from any number of 327 // goroutines, and therefore it is recommended to implement some form of 328 // throttling or mutex locking in cases where the call is non-blocking. 329 // 330 // Only one consumer can be added to a stream builder, and subsequent calls will 331 // return an error. 332 // 333 // Message batches must be created by upstream components (inputs, buffers, etc) 334 // otherwise message batches received by this consumer will have a single 335 // message contents. 336 func (s *StreamBuilder) AddBatchConsumerFunc(fn MessageBatchHandlerFunc) error { 337 if s.consumerFunc != nil { 338 return errors.New("unable to add multiple producer funcs to a stream builder") 339 } 340 341 uuid, err := uuid.NewV4() 342 if err != nil { 343 return fmt.Errorf("failed to generate a consumer uuid: %w", err) 344 } 345 346 s.consumerFunc = fn 347 s.consumerID = uuid.String() 348 349 conf := output.NewConfig() 350 conf.Type = output.TypeInproc 351 conf.Inproc = output.InprocConfig(s.consumerID) 352 s.outputs = append(s.outputs, conf) 353 354 return nil 355 } 356 357 // AddOutputYAML parses an output YAML configuration and adds it to the builder. 358 // If more than one output configuration is added they will automatically be 359 // composed within a fan out broker when the pipeline is built. 360 func (s *StreamBuilder) AddOutputYAML(conf string) error { 361 nconf, err := getYAMLNode([]byte(conf)) 362 if err != nil { 363 return err 364 } 365 366 if err := s.lintYAMLComponent(nconf, docs.TypeOutput); err != nil { 367 return err 368 } 369 370 oconf := output.NewConfig() 371 if err := nconf.Decode(&oconf); err != nil { 372 return err 373 } 374 375 s.outputs = append(s.outputs, oconf) 376 return nil 377 } 378 379 // AddCacheYAML parses a cache YAML configuration and adds it to the builder as 380 // a resource. 381 func (s *StreamBuilder) AddCacheYAML(conf string) error { 382 nconf, err := getYAMLNode([]byte(conf)) 383 if err != nil { 384 return err 385 } 386 387 if err := s.lintYAMLComponent(nconf, docs.TypeCache); err != nil { 388 return err 389 } 390 391 cconf := cache.NewConfig() 392 if err := nconf.Decode(&cconf); err != nil { 393 return err 394 } 395 if cconf.Label == "" { 396 return errors.New("a label must be specified for cache resources") 397 } 398 for _, cc := range s.resources.ResourceCaches { 399 if cc.Label == cconf.Label { 400 return fmt.Errorf("label %v collides with a previously defined resource", cc.Label) 401 } 402 } 403 404 s.resources.ResourceCaches = append(s.resources.ResourceCaches, cconf) 405 return nil 406 } 407 408 // AddRateLimitYAML parses a rate limit YAML configuration and adds it to the 409 // builder as a resource. 410 func (s *StreamBuilder) AddRateLimitYAML(conf string) error { 411 nconf, err := getYAMLNode([]byte(conf)) 412 if err != nil { 413 return err 414 } 415 416 if err := s.lintYAMLComponent(nconf, docs.TypeRateLimit); err != nil { 417 return err 418 } 419 420 rconf := ratelimit.NewConfig() 421 if err := nconf.Decode(&rconf); err != nil { 422 return err 423 } 424 if rconf.Label == "" { 425 return errors.New("a label must be specified for rate limit resources") 426 } 427 for _, rl := range s.resources.ResourceRateLimits { 428 if rl.Label == rconf.Label { 429 return fmt.Errorf("label %v collides with a previously defined resource", rl.Label) 430 } 431 } 432 433 s.resources.ResourceRateLimits = append(s.resources.ResourceRateLimits, rconf) 434 return nil 435 } 436 437 // AddResourcesYAML parses resource configurations and adds them to the config. 438 func (s *StreamBuilder) AddResourcesYAML(conf string) error { 439 node, err := getYAMLNode([]byte(conf)) 440 if err != nil { 441 return err 442 } 443 444 if err := s.lintYAMLSpec(manager.Spec(), node); err != nil { 445 return err 446 } 447 448 rconf := manager.NewResourceConfig() 449 if err := node.Decode(&rconf); err != nil { 450 return err 451 } 452 453 return s.resources.AddFrom(&rconf) 454 } 455 456 //------------------------------------------------------------------------------ 457 458 // SetYAML parses a full Benthos config and uses it to configure the builder. If 459 // any inputs, processors, outputs, resources, etc, have previously been added 460 // to the builder they will be overridden by this new config. 461 func (s *StreamBuilder) SetYAML(conf string) error { 462 if s.producerChan != nil { 463 return errors.New("attempted to override inputs config after adding a func producer") 464 } 465 if s.consumerFunc != nil { 466 return errors.New("attempted to override outputs config after adding a func consumer") 467 } 468 469 node, err := getYAMLNode([]byte(conf)) 470 if err != nil { 471 return err 472 } 473 474 if err := s.lintYAMLSpec(config.Spec(), node); err != nil { 475 return err 476 } 477 478 sconf := config.New() 479 if err := node.Decode(&sconf); err != nil { 480 return err 481 } 482 483 s.setFromConfig(sconf) 484 return nil 485 } 486 487 // SetFields modifies the config by setting one or more fields identified by a 488 // dot path to a value. The argument must be a variadic list of pairs, where the 489 // first element is a string containing the target field dot path, and the 490 // second element is a typed value to set the field to. 491 func (s *StreamBuilder) SetFields(pathValues ...interface{}) error { 492 if s.producerChan != nil { 493 return errors.New("attempted to override config after adding a func producer") 494 } 495 if s.consumerFunc != nil { 496 return errors.New("attempted to override config after adding a func consumer") 497 } 498 if len(pathValues)%2 != 0 { 499 return errors.New("invalid odd number of pathValues provided") 500 } 501 502 var rootNode yaml.Node 503 if err := rootNode.Encode(s.buildConfig()); err != nil { 504 return err 505 } 506 507 if err := config.Spec().SanitiseYAML(&rootNode, docs.SanitiseConfig{ 508 RemoveTypeField: true, 509 RemoveDeprecated: false, 510 DocsProvider: s.env.internal, 511 }); err != nil { 512 return err 513 } 514 515 for i := 0; i < len(pathValues)-1; i += 2 { 516 var valueNode yaml.Node 517 if err := valueNode.Encode(pathValues[i+1]); err != nil { 518 return err 519 } 520 pathString, ok := pathValues[i].(string) 521 if !ok { 522 return fmt.Errorf("variadic pair element %v should be a string, got a %T", i, pathValues[i]) 523 } 524 if err := config.Spec().SetYAMLPath(s.env.internal, &rootNode, &valueNode, gabs.DotPathToSlice(pathString)...); err != nil { 525 return err 526 } 527 } 528 529 if err := s.lintYAMLSpec(config.Spec(), &rootNode); err != nil { 530 return err 531 } 532 533 sconf := config.New() 534 if err := rootNode.Decode(&sconf); err != nil { 535 return err 536 } 537 538 s.setFromConfig(sconf) 539 return nil 540 } 541 542 func (s *StreamBuilder) setFromConfig(sconf config.Type) { 543 s.http = sconf.HTTP 544 s.inputs = []input.Config{sconf.Input} 545 s.buffer = sconf.Buffer 546 s.processors = sconf.Pipeline.Processors 547 s.threads = sconf.Pipeline.Threads 548 s.outputs = []output.Config{sconf.Output} 549 s.resources = sconf.ResourceConfig 550 s.logger = sconf.Logger 551 s.metrics = sconf.Metrics 552 } 553 554 // SetBufferYAML parses a buffer YAML configuration and sets it to the builder 555 // to be placed between the input and the pipeline (processors) sections. This 556 // config will replace any prior configured buffer. 557 func (s *StreamBuilder) SetBufferYAML(conf string) error { 558 nconf, err := getYAMLNode([]byte(conf)) 559 if err != nil { 560 return err 561 } 562 563 if err := s.lintYAMLComponent(nconf, docs.TypeBuffer); err != nil { 564 return err 565 } 566 567 bconf := buffer.NewConfig() 568 if err := nconf.Decode(&bconf); err != nil { 569 return err 570 } 571 572 s.buffer = bconf 573 return nil 574 } 575 576 // SetMetricsYAML parses a metrics YAML configuration and adds it to the builder 577 // such that all stream components emit metrics through it. 578 func (s *StreamBuilder) SetMetricsYAML(conf string) error { 579 nconf, err := getYAMLNode([]byte(conf)) 580 if err != nil { 581 return err 582 } 583 584 if err := s.lintYAMLComponent(nconf, docs.TypeMetrics); err != nil { 585 return err 586 } 587 588 mconf := metrics.NewConfig() 589 if err := nconf.Decode(&mconf); err != nil { 590 return err 591 } 592 593 s.metrics = mconf 594 return nil 595 } 596 597 // SetLoggerYAML parses a logger YAML configuration and adds it to the builder 598 // such that all stream components emit logs through it. 599 func (s *StreamBuilder) SetLoggerYAML(conf string) error { 600 node, err := getYAMLNode([]byte(conf)) 601 if err != nil { 602 return err 603 } 604 605 if err := s.lintYAMLSpec(log.Spec(), node); err != nil { 606 return err 607 } 608 609 lconf := log.NewConfig() 610 if err := node.Decode(&lconf); err != nil { 611 return err 612 } 613 614 s.logger = lconf 615 return nil 616 } 617 618 //------------------------------------------------------------------------------ 619 620 // AsYAML prints a YAML representation of the stream config as it has been 621 // currently built. 622 func (s *StreamBuilder) AsYAML() (string, error) { 623 conf := s.buildConfig() 624 625 var node yaml.Node 626 if err := node.Encode(conf); err != nil { 627 return "", err 628 } 629 630 if err := config.Spec().SanitiseYAML(&node, docs.SanitiseConfig{ 631 RemoveTypeField: true, 632 RemoveDeprecated: false, 633 DocsProvider: s.env.internal, 634 }); err != nil { 635 return "", err 636 } 637 638 b, err := yaml.Marshal(node) 639 if err != nil { 640 return "", err 641 } 642 return string(b), nil 643 } 644 645 //------------------------------------------------------------------------------ 646 647 func (s *StreamBuilder) runConsumerFunc(mgr *manager.Type) error { 648 if s.consumerFunc == nil { 649 return nil 650 } 651 tChan, err := mgr.GetPipe(s.consumerID) 652 if err != nil { 653 return err 654 } 655 go func() { 656 for { 657 tran, open := <-tChan 658 if !open { 659 return 660 } 661 batch := make(MessageBatch, tran.Payload.Len()) 662 _ = tran.Payload.Iter(func(i int, part types.Part) error { 663 batch[i] = newMessageFromPart(part) 664 return nil 665 }) 666 err := s.consumerFunc(context.Background(), batch) 667 var res types.Response 668 if err != nil { 669 res = response.NewError(err) 670 } else { 671 res = response.NewAck() 672 } 673 tran.ResponseChan <- res 674 } 675 }() 676 return nil 677 } 678 679 // Build a Benthos stream pipeline according to the components specified by this 680 // stream builder. 681 func (s *StreamBuilder) Build() (*Stream, error) { 682 return s.buildWithEnv(s.env.internal) 683 } 684 685 // BuildTraced creates a Benthos stream pipeline according to the components 686 // specified by this stream builder, where each major component (input, 687 // processor, output) is wrapped with a tracing module that, during the lifetime 688 // of the stream, aggregates tracing events into the returned *TracingSummary. 689 // Once the stream has ended the TracingSummary can be queried for events that 690 // occurred. 691 // 692 // Experimental: The behaviour of this method could change outside of major 693 // version releases. 694 func (s *StreamBuilder) BuildTraced() (*Stream, *TracingSummary, error) { 695 tenv, summary := tracing.TracedBundle(s.env.internal) 696 strm, err := s.buildWithEnv(tenv) 697 return strm, &TracingSummary{summary}, err 698 } 699 700 func (s *StreamBuilder) buildWithEnv(env *bundle.Environment) (*Stream, error) { 701 conf := s.buildConfig() 702 703 logger := s.customLogger 704 if logger == nil { 705 var err error 706 if logger, err = log.NewV2(os.Stdout, s.logger); err != nil { 707 return nil, err 708 } 709 } 710 711 stats, err := metrics.New(s.metrics, metrics.OptSetLogger(logger)) 712 if err != nil { 713 return nil, err 714 } 715 716 apiMut := s.apiMut 717 if apiMut == nil { 718 var sanitNode yaml.Node 719 err := sanitNode.Encode(conf) 720 if err == nil { 721 _ = config.Spec().SanitiseYAML(&sanitNode, docs.SanitiseConfig{ 722 RemoveTypeField: true, 723 DocsProvider: env, 724 }) 725 } 726 if apiMut, err = api.New("", "", s.http, sanitNode, logger, stats); err != nil { 727 return nil, fmt.Errorf("unable to create stream HTTP server due to: %w. Tip: you can disable the server with `http.enabled` set to `false`, or override the configured server with SetHTTPMux", err) 728 } 729 } 730 731 if wHandlerFunc, ok := stats.(metrics.WithHandlerFunc); ok { 732 apiMut.RegisterEndpoint( 733 "/stats", "Returns service metrics.", 734 wHandlerFunc.HandlerFunc(), 735 ) 736 apiMut.RegisterEndpoint( 737 "/metrics", "Returns service metrics.", 738 wHandlerFunc.HandlerFunc(), 739 ) 740 } 741 742 mgr, err := manager.NewV2( 743 conf.ResourceConfig, apiMut, logger, stats, 744 manager.OptSetEnvironment(env), 745 manager.OptSetBloblangEnvironment(s.env.getBloblangParserEnv()), 746 ) 747 if err != nil { 748 return nil, err 749 } 750 751 if s.producerChan != nil { 752 mgr.SetPipe(s.producerID, s.producerChan) 753 } 754 755 return newStream(conf.Config, mgr, stats, logger, func() { 756 if err := s.runConsumerFunc(mgr); err != nil { 757 logger.Errorf("Failed to run func consumer: %v", err) 758 } 759 }), nil 760 } 761 762 type builderConfig struct { 763 HTTP *api.Config `yaml:"http,omitempty"` 764 stream.Config `yaml:",inline"` 765 manager.ResourceConfig `yaml:",inline"` 766 Metrics metrics.Config `yaml:"metrics"` 767 Logger *log.Config `yaml:"logger,omitempty"` 768 } 769 770 func (s *StreamBuilder) buildConfig() builderConfig { 771 conf := builderConfig{ 772 Config: stream.NewConfig(), 773 } 774 775 if s.apiMut == nil { 776 conf.HTTP = &s.http 777 } 778 779 if len(s.inputs) == 1 { 780 conf.Input = s.inputs[0] 781 } else if len(s.inputs) > 1 { 782 conf.Input.Type = input.TypeBroker 783 conf.Input.Broker.Inputs = s.inputs 784 } 785 786 conf.Buffer = s.buffer 787 788 conf.Pipeline.Threads = s.threads 789 conf.Pipeline.Processors = s.processors 790 791 if len(s.outputs) == 1 { 792 conf.Output = s.outputs[0] 793 } else if len(s.outputs) > 1 { 794 conf.Output.Type = output.TypeBroker 795 conf.Output.Broker.Outputs = s.outputs 796 } 797 798 conf.ResourceConfig = s.resources 799 conf.Metrics = s.metrics 800 if s.customLogger == nil { 801 conf.Logger = &s.logger 802 } 803 return conf 804 } 805 806 //------------------------------------------------------------------------------ 807 808 func getYAMLNode(b []byte) (*yaml.Node, error) { 809 b = text.ReplaceEnvVariables(b) 810 var nconf yaml.Node 811 if err := yaml.Unmarshal(b, &nconf); err != nil { 812 return nil, err 813 } 814 if nconf.Kind == yaml.DocumentNode && len(nconf.Content) > 0 { 815 return nconf.Content[0], nil 816 } 817 return &nconf, nil 818 } 819 820 // Lint represents a configuration file linting error. 821 type Lint struct { 822 Line int 823 What string 824 } 825 826 // LintError is an error type that represents one or more configuration file 827 // linting errors that were encountered. 828 type LintError []Lint 829 830 // Error returns an error string. 831 func (e LintError) Error() string { 832 var lintsCollapsed bytes.Buffer 833 for i, l := range e { 834 if i > 0 { 835 lintsCollapsed.WriteString("\n") 836 } 837 fmt.Fprintf(&lintsCollapsed, "line %v: %v", l.Line, l.What) 838 } 839 return fmt.Sprintf("lint errors: %v", lintsCollapsed.String()) 840 } 841 842 func lintsToErr(lints []docs.Lint) error { 843 if len(lints) == 0 { 844 return nil 845 } 846 var e LintError 847 for _, l := range lints { 848 e = append(e, Lint{Line: l.Line, What: l.What}) 849 } 850 return e 851 } 852 853 func (s *StreamBuilder) lintYAMLSpec(spec docs.FieldSpecs, node *yaml.Node) error { 854 if s.lintingDisabled { 855 return nil 856 } 857 return lintsToErr(spec.LintYAML(s.getLintContext(), node)) 858 } 859 860 func (s *StreamBuilder) lintYAMLComponent(node *yaml.Node, ctype docs.Type) error { 861 if s.lintingDisabled { 862 return nil 863 } 864 return lintsToErr(docs.LintYAML(s.getLintContext(), ctype, node)) 865 }