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  }