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 //------------------------------------------------------------------------------