github.com/Jeffail/benthos/v3@v3.65.0/lib/output/resource.go (about)

     1  package output
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     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/types"
    12  )
    13  
    14  func init() {
    15  	Constructors[TypeResource] = TypeSpec{
    16  		constructor: fromSimpleConstructor(NewResource),
    17  		Summary: `
    18  Resource is an output type that runs a resource output by its name.`,
    19  		Description: `
    20  This output allows you to reference the same configured output resource in multiple places, and can also tidy up large nested configs. For example, the config:
    21  
    22  ` + "```yaml" + `
    23  output:
    24    broker:
    25      pattern: fan_out
    26      outputs:
    27      - kafka:
    28          addresses: [ TODO ]
    29          topic: foo
    30      - gcp_pubsub:
    31          project: bar
    32          topic: baz
    33  ` + "```" + `
    34  
    35  Could also be expressed as:
    36  
    37  ` + "``` yaml" + `
    38  output:
    39    broker:
    40      pattern: fan_out
    41      outputs:
    42      - resource: foo
    43      - resource: bar
    44  
    45  output_resources:
    46    - label: foo
    47      kafka:
    48        addresses: [ TODO ]
    49        topic: foo
    50  
    51    - label: bar
    52      gcp_pubsub:
    53        project: bar
    54        topic: baz
    55   ` + "```" + `
    56  
    57  You can find out more about resources [in this document.](/docs/configuration/resources)`,
    58  		Categories: []Category{
    59  			CategoryUtility,
    60  		},
    61  		config: docs.FieldComponent().HasType(docs.FieldTypeString).HasDefault(""),
    62  	}
    63  }
    64  
    65  //------------------------------------------------------------------------------
    66  
    67  // Resource is a processor that returns the result of a output resource.
    68  type Resource struct {
    69  	mgr   types.Manager
    70  	name  string
    71  	log   log.Modular
    72  	stats metrics.Type
    73  
    74  	transactions <-chan types.Transaction
    75  
    76  	ctx  context.Context
    77  	done func()
    78  
    79  	mErrNotFound metrics.StatCounter
    80  }
    81  
    82  // NewResource returns a resource output.
    83  func NewResource(
    84  	conf Config, mgr types.Manager, log log.Modular, stats metrics.Type,
    85  ) (Type, error) {
    86  	if err := interop.ProbeOutput(context.Background(), mgr, conf.Resource); err != nil {
    87  		return nil, err
    88  	}
    89  	ctx, done := context.WithCancel(context.Background())
    90  	return &Resource{
    91  		mgr:          mgr,
    92  		name:         conf.Resource,
    93  		log:          log,
    94  		stats:        stats,
    95  		ctx:          ctx,
    96  		done:         done,
    97  		mErrNotFound: stats.GetCounter("error_not_found"),
    98  	}, nil
    99  }
   100  
   101  //------------------------------------------------------------------------------
   102  
   103  func (r *Resource) loop() {
   104  	// Metrics paths
   105  	var (
   106  		mCount = r.stats.GetCounter("count")
   107  	)
   108  
   109  	var ts *types.Transaction
   110  	for {
   111  		if ts == nil {
   112  			select {
   113  			case t, open := <-r.transactions:
   114  				if !open {
   115  					r.done()
   116  					return
   117  				}
   118  				ts = &t
   119  			case <-r.ctx.Done():
   120  				return
   121  			}
   122  		}
   123  		mCount.Incr(1)
   124  
   125  		var err error
   126  		if oerr := interop.AccessOutput(context.Background(), r.mgr, r.name, func(o types.OutputWriter) {
   127  			err = o.WriteTransaction(r.ctx, *ts)
   128  		}); oerr != nil {
   129  			err = oerr
   130  		}
   131  		if err != nil {
   132  			r.log.Debugf("Failed to obtain output resource '%v': %v", r.name, err)
   133  			r.mErrNotFound.Incr(1)
   134  			select {
   135  			case <-time.After(time.Second):
   136  			case <-r.ctx.Done():
   137  				return
   138  			}
   139  		} else {
   140  			ts = nil
   141  		}
   142  	}
   143  }
   144  
   145  //------------------------------------------------------------------------------
   146  
   147  // Consume assigns a messages channel for the output to read.
   148  func (r *Resource) Consume(ts <-chan types.Transaction) error {
   149  	if r.transactions != nil {
   150  		return types.ErrAlreadyStarted
   151  	}
   152  	r.transactions = ts
   153  	go r.loop()
   154  	return nil
   155  }
   156  
   157  // Connected returns a boolean indicating whether this output is currently
   158  // connected to its target.
   159  func (r *Resource) Connected() (isConnected bool) {
   160  	var err error
   161  	if err = interop.AccessOutput(context.Background(), r.mgr, r.name, func(o types.OutputWriter) {
   162  		isConnected = o.Connected()
   163  	}); err != nil {
   164  		r.log.Debugf("Failed to obtain output resource '%v': %v", r.name, err)
   165  		r.mErrNotFound.Incr(1)
   166  	}
   167  	return
   168  }
   169  
   170  // CloseAsync shuts down the output and stops processing requests.
   171  func (r *Resource) CloseAsync() {
   172  	r.done()
   173  }
   174  
   175  // WaitForClose blocks until the output has closed down.
   176  func (r *Resource) WaitForClose(timeout time.Duration) error {
   177  	select {
   178  	case <-r.ctx.Done():
   179  	case <-time.After(timeout):
   180  		return types.ErrTimeout
   181  	}
   182  	return nil
   183  }
   184  
   185  //------------------------------------------------------------------------------