github.com/Jeffail/benthos/v3@v3.65.0/lib/cache/constructor.go (about) 1 package cache 2 3 import ( 4 "bytes" 5 "fmt" 6 "sort" 7 "strings" 8 9 "github.com/Jeffail/benthos/v3/internal/component/cache" 10 "github.com/Jeffail/benthos/v3/internal/docs" 11 "github.com/Jeffail/benthos/v3/lib/log" 12 "github.com/Jeffail/benthos/v3/lib/metrics" 13 "github.com/Jeffail/benthos/v3/lib/types" 14 "github.com/Jeffail/benthos/v3/lib/util/config" 15 16 "gopkg.in/yaml.v3" 17 ) 18 19 //------------------------------------------------------------------------------ 20 21 type cacheConstructor func(conf Config, mgr types.Manager, log log.Modular, stats metrics.Type) (types.Cache, error) 22 23 // TypeSpec is a constructor and a usage description for each cache type. 24 type TypeSpec struct { 25 constructor cacheConstructor 26 27 Summary string 28 Description string 29 Footnotes string 30 config docs.FieldSpec 31 FieldSpecs docs.FieldSpecs 32 Status docs.Status 33 SupportsPerKeyTTL bool 34 Version string 35 } 36 37 // ConstructorFunc is a func signature able to construct a cache. 38 type ConstructorFunc func(Config, types.Manager, log.Modular, metrics.Type) (types.Cache, error) 39 40 // WalkConstructors iterates each component constructor. 41 func WalkConstructors(fn func(ConstructorFunc, docs.ComponentSpec)) { 42 inferred := docs.ComponentFieldsFromConf(NewConfig()) 43 for k, v := range Constructors { 44 conf := v.config 45 if len(v.FieldSpecs) > 0 { 46 conf = docs.FieldComponent().WithChildren(v.FieldSpecs.DefaultAndTypeFrom(inferred[k])...) 47 } else { 48 conf.Children = conf.Children.DefaultAndTypeFrom(inferred[k]) 49 } 50 spec := docs.ComponentSpec{ 51 Type: docs.TypeCache, 52 Name: k, 53 Summary: v.Summary, 54 Description: v.Description, 55 Footnotes: v.Footnotes, 56 Config: conf, 57 Status: v.Status, 58 Version: v.Version, 59 } 60 spec.Description = cache.Description(v.SupportsPerKeyTTL, spec.Description) 61 fn(ConstructorFunc(v.constructor), spec) 62 } 63 for k, v := range pluginSpecs { 64 spec := docs.ComponentSpec{ 65 Type: docs.TypeCache, 66 Name: k, 67 Status: docs.StatusExperimental, 68 Plugin: true, 69 Config: docs.FieldComponent().Unlinted(), 70 } 71 fn(ConstructorFunc(v.constructor), spec) 72 } 73 } 74 75 // Constructors is a map of all cache types with their specs. 76 var Constructors = map[string]TypeSpec{} 77 78 //------------------------------------------------------------------------------ 79 80 // String constants representing each cache type. 81 // Deprecated: Do not add new components here. Instead, use the public plugin 82 // APIs. Examples can be found in: ./internal/impl 83 const ( 84 TypeAWSDynamoDB = "aws_dynamodb" 85 TypeAWSS3 = "aws_s3" 86 TypeDynamoDB = "dynamodb" 87 TypeFile = "file" 88 TypeMemcached = "memcached" 89 TypeMemory = "memory" 90 TypeMongoDB = "mongodb" 91 TypeMultilevel = "multilevel" 92 TypeRedis = "redis" 93 TypeRistretto = "ristretto" 94 TypeS3 = "s3" 95 ) 96 97 //------------------------------------------------------------------------------ 98 99 // Config is the all encompassing configuration struct for all cache types. 100 // Deprecated: Do not add new components here. Instead, use the public plugin 101 // APIs. Examples can be found in: ./internal/impl 102 type Config struct { 103 Label string `json:"label" yaml:"label"` 104 Type string `json:"type" yaml:"type"` 105 AWSDynamoDB DynamoDBConfig `json:"aws_dynamodb" yaml:"aws_dynamodb"` 106 AWSS3 S3Config `json:"aws_s3" yaml:"aws_s3"` 107 DynamoDB DynamoDBConfig `json:"dynamodb" yaml:"dynamodb"` 108 File FileConfig `json:"file" yaml:"file"` 109 Memcached MemcachedConfig `json:"memcached" yaml:"memcached"` 110 Memory MemoryConfig `json:"memory" yaml:"memory"` 111 MongoDB MongoDBConfig `json:"mongodb" yaml:"mongodb"` 112 Multilevel MultilevelConfig `json:"multilevel" yaml:"multilevel"` 113 Plugin interface{} `json:"plugin,omitempty" yaml:"plugin,omitempty"` 114 Redis RedisConfig `json:"redis" yaml:"redis"` 115 Ristretto RistrettoConfig `json:"ristretto" yaml:"ristretto"` 116 S3 S3Config `json:"s3" yaml:"s3"` 117 } 118 119 // NewConfig returns a configuration struct fully populated with default values. 120 func NewConfig() Config { 121 return Config{ 122 Label: "", 123 Type: "memory", 124 AWSDynamoDB: NewDynamoDBConfig(), 125 AWSS3: NewS3Config(), 126 DynamoDB: NewDynamoDBConfig(), 127 File: NewFileConfig(), 128 Memcached: NewMemcachedConfig(), 129 Memory: NewMemoryConfig(), 130 MongoDB: NewMongoDBConfig(), 131 Multilevel: NewMultilevelConfig(), 132 Plugin: nil, 133 Redis: NewRedisConfig(), 134 Ristretto: NewRistrettoConfig(), 135 S3: NewS3Config(), 136 } 137 } 138 139 //------------------------------------------------------------------------------ 140 141 // SanitiseConfig creates a sanitised version of a config. 142 func SanitiseConfig(conf Config) (interface{}, error) { 143 return conf.Sanitised(false) 144 } 145 146 // Sanitised returns a sanitised version of the config, meaning sections that 147 // aren't relevant to behaviour are removed. Also optionally removes deprecated 148 // fields. 149 func (conf Config) Sanitised(removeDeprecated bool) (interface{}, error) { 150 outputMap, err := config.SanitizeComponent(conf) 151 if err != nil { 152 return nil, err 153 } 154 if spec, exists := pluginSpecs[conf.Type]; exists { 155 if spec.confSanitiser != nil { 156 outputMap["plugin"] = spec.confSanitiser(conf.Plugin) 157 } 158 } 159 if err := docs.SanitiseComponentConfig( 160 docs.TypeCache, 161 map[string]interface{}(outputMap), 162 docs.ShouldDropDeprecated(removeDeprecated), 163 ); err != nil { 164 return nil, err 165 } 166 return outputMap, nil 167 } 168 169 //------------------------------------------------------------------------------ 170 171 // UnmarshalYAML ensures that when parsing configs that are in a map or slice 172 // the default values are still applied. 173 func (conf *Config) UnmarshalYAML(value *yaml.Node) error { 174 type confAlias Config 175 aliased := confAlias(NewConfig()) 176 177 err := value.Decode(&aliased) 178 if err != nil { 179 return fmt.Errorf("line %v: %v", value.Line, err) 180 } 181 182 var spec docs.ComponentSpec 183 if aliased.Type, spec, err = docs.GetInferenceCandidateFromYAML(nil, docs.TypeCache, aliased.Type, value); err != nil { 184 return fmt.Errorf("line %v: %w", value.Line, err) 185 } 186 187 if spec.Plugin { 188 pluginNode, err := docs.GetPluginConfigYAML(aliased.Type, value) 189 if err != nil { 190 return fmt.Errorf("line %v: %v", value.Line, err) 191 } 192 if spec, exists := pluginSpecs[aliased.Type]; exists && spec.confConstructor != nil { 193 conf := spec.confConstructor() 194 if err = pluginNode.Decode(conf); err != nil { 195 return fmt.Errorf("line %v: %v", value.Line, err) 196 } 197 aliased.Plugin = conf 198 } else { 199 aliased.Plugin = &pluginNode 200 } 201 } else { 202 aliased.Plugin = nil 203 } 204 205 *conf = Config(aliased) 206 return nil 207 } 208 209 //------------------------------------------------------------------------------ 210 211 var header = "This document was generated with `benthos --list-caches`" + ` 212 213 A cache is a key/value store which can be used by certain processors for 214 applications such as deduplication. Caches are listed with unique labels which 215 are referred to by processors that may share them. 216 217 Caches are configured as resources: 218 219 ` + "```yaml" + ` 220 resources: 221 caches: 222 foobar: 223 memcached: 224 addresses: 225 - localhost:11211 226 ttl: 60 227 ` + "```" + ` 228 229 And any components that use caches have a field used to refer to a cache 230 resource: 231 232 ` + "```yaml" + ` 233 pipeline: 234 processors: 235 - dedupe: 236 cache: foobar 237 hash: xxhash 238 ` + "```" + `` 239 240 // Descriptions returns a formatted string of descriptions for each type. 241 func Descriptions() string { 242 // Order our cache types alphabetically 243 names := []string{} 244 for name := range Constructors { 245 names = append(names, name) 246 } 247 sort.Strings(names) 248 249 buf := bytes.Buffer{} 250 buf.WriteString("Caches\n") 251 buf.WriteString(strings.Repeat("=", 6)) 252 buf.WriteString("\n\n") 253 buf.WriteString(header) 254 buf.WriteString("\n\n") 255 256 buf.WriteString("### Contents\n\n") 257 for i, name := range names { 258 buf.WriteString(fmt.Sprintf("%v. [`%v`](#%v)\n", i+1, name, name)) 259 } 260 buf.WriteString("\n") 261 262 // Append each description 263 for i, name := range names { 264 var confBytes []byte 265 266 conf := NewConfig() 267 conf.Type = name 268 if confSanit, err := SanitiseConfig(conf); err == nil { 269 confBytes, _ = config.MarshalYAML(confSanit) 270 } 271 272 buf.WriteString("## ") 273 buf.WriteString("`" + name + "`") 274 buf.WriteString("\n") 275 if confBytes != nil { 276 buf.WriteString("\n``` yaml\n") 277 buf.Write(confBytes) 278 buf.WriteString("```\n") 279 } 280 buf.WriteString(Constructors[name].Description) 281 buf.WriteString("\n") 282 if i != (len(names) - 1) { 283 buf.WriteString("\n") 284 buf.WriteString("---\n") 285 } 286 } 287 return buf.String() 288 } 289 290 // New creates a cache type based on an cache configuration. 291 func New( 292 conf Config, 293 mgr types.Manager, 294 log log.Modular, 295 stats metrics.Type, 296 ) (types.Cache, error) { 297 if mgrV2, ok := mgr.(interface { 298 NewCache(conf Config) (types.Cache, error) 299 }); ok { 300 return mgrV2.NewCache(conf) 301 } 302 if c, ok := Constructors[conf.Type]; ok { 303 cache, err := c.constructor(conf, mgr, log, stats) 304 if err != nil { 305 return nil, fmt.Errorf("failed to create cache '%v': %v", conf.Type, err) 306 } 307 return cache, nil 308 } 309 if c, ok := pluginSpecs[conf.Type]; ok { 310 rl, err := c.constructor(conf, mgr, log, stats) 311 if err != nil { 312 return nil, fmt.Errorf("failed to create cache '%v': %v", conf.Type, err) 313 } 314 return rl, nil 315 } 316 return nil, types.ErrInvalidCacheType 317 } 318 319 //------------------------------------------------------------------------------