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