github.com/Jeffail/benthos/v3@v3.65.0/lib/buffer/constructor.go (about)

     1  package buffer
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/Jeffail/benthos/v3/internal/docs"
    10  	"github.com/Jeffail/benthos/v3/lib/log"
    11  	"github.com/Jeffail/benthos/v3/lib/metrics"
    12  	"github.com/Jeffail/benthos/v3/lib/types"
    13  	"github.com/Jeffail/benthos/v3/lib/util/config"
    14  	yaml "gopkg.in/yaml.v3"
    15  )
    16  
    17  //------------------------------------------------------------------------------
    18  
    19  // TypeSpec is a constructor and usage description for each buffer type.
    20  type TypeSpec struct {
    21  	constructor func(conf Config, mgr types.Manager, log log.Modular, stats metrics.Type) (Type, error)
    22  
    23  	Summary     string
    24  	Description string
    25  	Footnotes   string
    26  	config      docs.FieldSpec
    27  	FieldSpecs  docs.FieldSpecs
    28  	Status      docs.Status
    29  	Version     string
    30  }
    31  
    32  // ConstructorFunc is a func signature able to construct a buffer.
    33  type ConstructorFunc func(Config, types.Manager, log.Modular, metrics.Type) (Type, error)
    34  
    35  // WalkConstructors iterates each component constructor.
    36  func WalkConstructors(fn func(ConstructorFunc, docs.ComponentSpec)) {
    37  	inferred := docs.ComponentFieldsFromConf(NewConfig())
    38  	for k, v := range Constructors {
    39  		conf := v.config
    40  		if len(v.FieldSpecs) > 0 {
    41  			conf = docs.FieldComponent().WithChildren(v.FieldSpecs.DefaultAndTypeFrom(inferred[k])...)
    42  		} else {
    43  			conf.Children = conf.Children.DefaultAndTypeFrom(inferred[k])
    44  		}
    45  		spec := docs.ComponentSpec{
    46  			Type:        docs.TypeBuffer,
    47  			Name:        k,
    48  			Categories:  []string{"Utility"},
    49  			Summary:     v.Summary,
    50  			Description: v.Description,
    51  			Footnotes:   v.Footnotes,
    52  			Config:      conf,
    53  			Status:      v.Status,
    54  			Version:     v.Version,
    55  		}
    56  		fn(ConstructorFunc(v.constructor), spec)
    57  	}
    58  }
    59  
    60  // Constructors is a map of all buffer types with their specs.
    61  var Constructors = map[string]TypeSpec{}
    62  
    63  //------------------------------------------------------------------------------
    64  
    65  // String constants representing each buffer type.
    66  const (
    67  	TypeMemory = "memory"
    68  	TypeNone   = "none"
    69  )
    70  
    71  //------------------------------------------------------------------------------
    72  
    73  // Config is the all encompassing configuration struct for all buffer types.
    74  // Deprecated: Do not add new components here. Instead, use the public plugin
    75  // APIs. Examples can be found in: ./internal/impl
    76  type Config struct {
    77  	Type   string       `json:"type" yaml:"type"`
    78  	Memory MemoryConfig `json:"memory" yaml:"memory"`
    79  	None   struct{}     `json:"none" yaml:"none"`
    80  	Plugin interface{}  `json:"plugin,omitempty" yaml:"plugin,omitempty"`
    81  }
    82  
    83  // NewConfig returns a configuration struct fully populated with default values.
    84  func NewConfig() Config {
    85  	return Config{
    86  		Type:   "none",
    87  		Memory: NewMemoryConfig(),
    88  		None:   struct{}{},
    89  		Plugin: nil,
    90  	}
    91  }
    92  
    93  // SanitiseConfig returns a sanitised version of the Config, meaning sections
    94  // that aren't relevant to behaviour are removed.
    95  func SanitiseConfig(conf Config) (interface{}, error) {
    96  	return conf.Sanitised(false)
    97  }
    98  
    99  // Sanitised returns a sanitised version of the config, meaning sections that
   100  // aren't relevant to behaviour are removed. Also optionally removes deprecated
   101  // fields.
   102  func (conf Config) Sanitised(removeDeprecated bool) (interface{}, error) {
   103  	outputMap, err := config.SanitizeComponent(conf)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	if err := docs.SanitiseComponentConfig(
   108  		docs.TypeBuffer,
   109  		map[string]interface{}(outputMap),
   110  		docs.ShouldDropDeprecated(removeDeprecated),
   111  	); err != nil {
   112  		return nil, err
   113  	}
   114  	return outputMap, nil
   115  }
   116  
   117  //------------------------------------------------------------------------------
   118  
   119  // UnmarshalYAML ensures that when parsing configs that are in a map or slice
   120  // the default values are still applied.
   121  func (conf *Config) UnmarshalYAML(value *yaml.Node) error {
   122  	type confAlias Config
   123  	aliased := confAlias(NewConfig())
   124  
   125  	err := value.Decode(&aliased)
   126  	if err != nil {
   127  		return fmt.Errorf("line %v: %v", value.Line, err)
   128  	}
   129  
   130  	var spec docs.ComponentSpec
   131  	if aliased.Type, spec, err = docs.GetInferenceCandidateFromYAML(nil, docs.TypeBuffer, aliased.Type, value); err != nil {
   132  		return fmt.Errorf("line %v: %w", value.Line, err)
   133  	}
   134  
   135  	if spec.Plugin {
   136  		pluginNode, err := docs.GetPluginConfigYAML(aliased.Type, value)
   137  		if err != nil {
   138  			return fmt.Errorf("line %v: %v", value.Line, err)
   139  		}
   140  		aliased.Plugin = &pluginNode
   141  	} else {
   142  		aliased.Plugin = nil
   143  	}
   144  
   145  	*conf = Config(aliased)
   146  	return nil
   147  }
   148  
   149  //------------------------------------------------------------------------------
   150  
   151  var header = "This document was generated with `benthos --list-buffers`" + `
   152  
   153  Benthos uses a transaction based model for guaranteeing delivery of messages
   154  without the need for a buffer. This ensures that messages are never acknowledged
   155  from a source until the message has left the target sink.
   156  
   157  However, sometimes the transaction model is undesired, in which case there are a
   158  range of buffer options available which decouple input sources from the rest of
   159  the Benthos pipeline.
   160  
   161  Buffers can therefore solve a number of typical streaming problems but come at
   162  the cost of weakening the delivery guarantees of your pipeline. Common problems
   163  that might warrant use of a buffer are:
   164  
   165  - Input sources can periodically spike beyond the capacity of your output sinks.
   166  - You want to use parallel [processing pipelines](/docs/configuration/processing_pipelines).
   167  - You have more outputs than inputs and wish to distribute messages across them
   168    in order to maximize overall throughput.
   169  - Your input source needs occasional protection against back pressure from your
   170    sink, e.g. during restarts. Please keep in mind that all buffers have an
   171    eventual limit.
   172  
   173  If you believe that a problem you have would be solved by a buffer the next step
   174  is to choose an implementation based on the throughput and delivery guarantees
   175  you need. In order to help here are some simplified tables outlining the
   176  different options and their qualities:
   177  
   178  #### Performance
   179  
   180  | Type      | Throughput | Consumers | Capacity |
   181  | --------- | ---------- | --------- | -------- |
   182  | Memory    | Highest    | Parallel  | RAM      |
   183  
   184  #### Delivery Guarantees
   185  
   186  | Event     | Shutdown  | Crash     | Disk Corruption |
   187  | --------- | --------- | --------- | --------------- |
   188  | Memory    | Flushed\* | Lost      | Lost            |
   189  
   190  \* Makes a best attempt at flushing the remaining messages before closing
   191    gracefully.`
   192  
   193  // Descriptions returns a formatted string of collated descriptions of each type.
   194  func Descriptions() string {
   195  	// Order our buffer types alphabetically
   196  	names := []string{}
   197  	for name := range Constructors {
   198  		names = append(names, name)
   199  	}
   200  	sort.Strings(names)
   201  
   202  	buf := bytes.Buffer{}
   203  	buf.WriteString("Buffers\n")
   204  	buf.WriteString(strings.Repeat("=", 7))
   205  	buf.WriteString("\n\n")
   206  	buf.WriteString(header)
   207  	buf.WriteString("\n\n")
   208  
   209  	buf.WriteString("### Contents\n\n")
   210  	for i, name := range names {
   211  		buf.WriteString(fmt.Sprintf("%v. [`%v`](#%v)\n", i+1, name, name))
   212  	}
   213  	buf.WriteString("\n")
   214  
   215  	// Append each description
   216  	for i, name := range names {
   217  		var confBytes []byte
   218  
   219  		conf := NewConfig()
   220  		conf.Type = name
   221  		if confSanit, err := SanitiseConfig(conf); err == nil {
   222  			confBytes, _ = config.MarshalYAML(confSanit)
   223  		}
   224  
   225  		buf.WriteString("## ")
   226  		buf.WriteString("`" + name + "`")
   227  		buf.WriteString("\n")
   228  		if confBytes != nil {
   229  			buf.WriteString("\n``` yaml\n")
   230  			buf.Write(confBytes)
   231  			buf.WriteString("```\n")
   232  		}
   233  		buf.WriteString(Constructors[name].Description)
   234  		buf.WriteString("\n")
   235  		if i != (len(names) - 1) {
   236  			buf.WriteString("\n---\n")
   237  		}
   238  	}
   239  	return buf.String()
   240  }
   241  
   242  // New creates a buffer type based on a buffer configuration.
   243  func New(conf Config, mgr types.Manager, log log.Modular, stats metrics.Type) (Type, error) {
   244  	if mgrV2, ok := mgr.(interface {
   245  		NewBuffer(conf Config) (Type, error)
   246  	}); ok {
   247  		return mgrV2.NewBuffer(conf)
   248  	}
   249  	if c, ok := Constructors[conf.Type]; ok {
   250  		return c.constructor(conf, mgr, log, stats)
   251  	}
   252  	return nil, types.ErrInvalidBufferType
   253  }
   254  
   255  //------------------------------------------------------------------------------