github.com/Jeffail/benthos/v3@v3.65.0/internal/template/template.go (about)

     1  package template
     2  
     3  import (
     4  	"fmt"
     5  	"io/fs"
     6  	"sync"
     7  
     8  	"github.com/Jeffail/benthos/v3/internal/bloblang/mapping"
     9  	"github.com/Jeffail/benthos/v3/internal/bundle"
    10  	"github.com/Jeffail/benthos/v3/internal/component/metrics"
    11  	"github.com/Jeffail/benthos/v3/internal/docs"
    12  	"github.com/Jeffail/benthos/v3/lib/cache"
    13  	"github.com/Jeffail/benthos/v3/lib/input"
    14  	"github.com/Jeffail/benthos/v3/lib/manager"
    15  	"github.com/Jeffail/benthos/v3/lib/message"
    16  	"github.com/Jeffail/benthos/v3/lib/output"
    17  	"github.com/Jeffail/benthos/v3/lib/processor"
    18  	"github.com/Jeffail/benthos/v3/lib/ratelimit"
    19  	"github.com/Jeffail/benthos/v3/lib/types"
    20  	"github.com/Jeffail/benthos/v3/template"
    21  	"gopkg.in/yaml.v3"
    22  )
    23  
    24  var initNativeOnce sync.Once
    25  
    26  func initNativeTemplates() {
    27  	if werr := fs.WalkDir(template.NativeTemplates, ".", func(path string, d fs.DirEntry, err error) error {
    28  		if err != nil {
    29  			return err
    30  		}
    31  		if d.IsDir() {
    32  			return nil
    33  		}
    34  		tBytes, err := fs.ReadFile(template.NativeTemplates, path)
    35  		if err != nil {
    36  			return err
    37  		}
    38  
    39  		var conf Config
    40  		if err = yaml.Unmarshal(tBytes, &conf); err != nil {
    41  			return fmt.Errorf("failed to parse template '%v': %w", path, err)
    42  		}
    43  
    44  		tmpl, err := conf.compile()
    45  		if err != nil {
    46  			return fmt.Errorf("failed to compile template %v: %w", path, err)
    47  		}
    48  
    49  		if err := registerTemplate(tmpl); err != nil {
    50  			return fmt.Errorf("failed to register template %v: %w", path, err)
    51  		}
    52  
    53  		return nil
    54  	}); werr != nil {
    55  		panic(werr)
    56  	}
    57  }
    58  
    59  // InitTemplates parses and registers native templates, as well as templates
    60  // at paths provided, and returns any linting errors that occur.
    61  func InitTemplates(templatesPaths ...string) ([]string, error) {
    62  	initNativeOnce.Do(initNativeTemplates)
    63  
    64  	var lints []string
    65  	for _, tPath := range templatesPaths {
    66  		tmplConf, tLints, err := ReadConfig(tPath)
    67  		if err != nil {
    68  			return nil, fmt.Errorf("template %v: %w", tPath, err)
    69  		}
    70  		for _, l := range tLints {
    71  			lints = append(lints, fmt.Sprintf("template file %v: %v", tPath, l))
    72  		}
    73  
    74  		tmpl, err := tmplConf.compile()
    75  		if err != nil {
    76  			return nil, fmt.Errorf("template %v: %w", tPath, err)
    77  		}
    78  
    79  		if err := registerTemplate(tmpl); err != nil {
    80  			return nil, fmt.Errorf("template %v: %w", tPath, err)
    81  		}
    82  	}
    83  	return lints, nil
    84  }
    85  
    86  //------------------------------------------------------------------------------
    87  
    88  // Compiled is a template that has been compiled from a config.
    89  type compiled struct {
    90  	spec           docs.ComponentSpec
    91  	mapping        *mapping.Executor
    92  	metricsMapping *metrics.Mapping
    93  }
    94  
    95  // ExpandToNode attempts to apply the template to a provided YAML node and
    96  // returns the new expanded configuration.
    97  func (c *compiled) ExpandToNode(node *yaml.Node) (*yaml.Node, error) {
    98  	generic, err := c.spec.Config.Children.YAMLToMap(node, docs.ToValueConfig{})
    99  	if err != nil {
   100  		return nil, fmt.Errorf("invalid config for template component: %w", err)
   101  	}
   102  
   103  	msg := message.New(nil)
   104  	part := message.NewPart(nil)
   105  	if err := part.SetJSON(generic); err != nil {
   106  		return nil, err
   107  	}
   108  	msg.Append(part)
   109  
   110  	newPart, err := c.mapping.MapPart(0, msg)
   111  	if err != nil {
   112  		return nil, fmt.Errorf("mapping failed for template component: %w", err)
   113  	}
   114  
   115  	resultGeneric, err := newPart.JSON()
   116  	if err != nil {
   117  		return nil, fmt.Errorf("mapping for template component resulted in invalid config: %w", err)
   118  	}
   119  
   120  	var resultNode yaml.Node
   121  	if err := resultNode.Encode(resultGeneric); err != nil {
   122  		return nil, fmt.Errorf("mapping for template component resulted in invalid yaml: %w", err)
   123  	}
   124  
   125  	return &resultNode, nil
   126  }
   127  
   128  //------------------------------------------------------------------------------
   129  
   130  // RegisterTemplate attempts to add a template component to the global list of
   131  // component types.
   132  func registerTemplate(tmpl *compiled) error {
   133  	switch tmpl.spec.Type {
   134  	case docs.TypeCache:
   135  		return registerCacheTemplate(tmpl, bundle.AllCaches)
   136  	case docs.TypeInput:
   137  		return registerInputTemplate(tmpl, bundle.AllInputs)
   138  	case docs.TypeOutput:
   139  		return registerOutputTemplate(tmpl, bundle.AllOutputs)
   140  	case docs.TypeProcessor:
   141  		return registerProcessorTemplate(tmpl, bundle.AllProcessors)
   142  	case docs.TypeRateLimit:
   143  		return registerRateLimitTemplate(tmpl, bundle.AllRateLimits)
   144  	}
   145  	return fmt.Errorf("unable to register template for component type %v", tmpl.spec.Type)
   146  }
   147  
   148  // WithMetricsMapping attempts to wrap the metrics of a manager with a metrics
   149  // mapping.
   150  func WithMetricsMapping(nm bundle.NewManagement, m *metrics.Mapping) bundle.NewManagement {
   151  	if t, ok := nm.(*manager.Type); ok {
   152  		return t.WithMetricsMapping(m)
   153  	}
   154  	return nm
   155  }
   156  
   157  func registerCacheTemplate(tmpl *compiled, set *bundle.CacheSet) error {
   158  	return set.Add(func(c cache.Config, nm bundle.NewManagement) (types.Cache, error) {
   159  		newNode, err := tmpl.ExpandToNode(c.Plugin.(*yaml.Node))
   160  		if err != nil {
   161  			return nil, err
   162  		}
   163  
   164  		conf := cache.NewConfig()
   165  		if err := newNode.Decode(&conf); err != nil {
   166  			return nil, err
   167  		}
   168  
   169  		if tmpl.metricsMapping != nil {
   170  			nm = WithMetricsMapping(nm, tmpl.metricsMapping)
   171  		}
   172  		return nm.NewCache(conf)
   173  	}, tmpl.spec)
   174  }
   175  
   176  func registerInputTemplate(tmpl *compiled, set *bundle.InputSet) error {
   177  	return set.Add(func(b bool, c input.Config, nm bundle.NewManagement, pcf ...types.PipelineConstructorFunc) (input.Type, error) {
   178  		newNode, err := tmpl.ExpandToNode(c.Plugin.(*yaml.Node))
   179  		if err != nil {
   180  			return nil, err
   181  		}
   182  
   183  		conf := input.NewConfig()
   184  		if err := newNode.Decode(&conf); err != nil {
   185  			return nil, err
   186  		}
   187  
   188  		// Tempate processors inserted _before_ configured processors.
   189  		conf.Processors = append(conf.Processors, c.Processors...)
   190  
   191  		if tmpl.metricsMapping != nil {
   192  			nm = WithMetricsMapping(nm, tmpl.metricsMapping)
   193  		}
   194  		return nm.NewInput(conf, b, pcf...)
   195  	}, tmpl.spec)
   196  }
   197  
   198  func registerOutputTemplate(tmpl *compiled, set *bundle.OutputSet) error {
   199  	return set.Add(func(c output.Config, nm bundle.NewManagement, pcf ...types.PipelineConstructorFunc) (output.Type, error) {
   200  		newNode, err := tmpl.ExpandToNode(c.Plugin.(*yaml.Node))
   201  		if err != nil {
   202  			return nil, err
   203  		}
   204  
   205  		conf := output.NewConfig()
   206  		if err := newNode.Decode(&conf); err != nil {
   207  			return nil, err
   208  		}
   209  
   210  		// Tempate processors inserted _after_ configured processors.
   211  		conf.Processors = append(c.Processors, conf.Processors...)
   212  
   213  		if tmpl.metricsMapping != nil {
   214  			nm = WithMetricsMapping(nm, tmpl.metricsMapping)
   215  		}
   216  		return nm.NewOutput(conf, pcf...)
   217  	}, tmpl.spec)
   218  }
   219  
   220  func registerProcessorTemplate(tmpl *compiled, set *bundle.ProcessorSet) error {
   221  	return set.Add(func(c processor.Config, nm bundle.NewManagement) (processor.Type, error) {
   222  		newNode, err := tmpl.ExpandToNode(c.Plugin.(*yaml.Node))
   223  		if err != nil {
   224  			return nil, err
   225  		}
   226  
   227  		conf := processor.NewConfig()
   228  		if err := newNode.Decode(&conf); err != nil {
   229  			return nil, err
   230  		}
   231  
   232  		if tmpl.metricsMapping != nil {
   233  			nm = WithMetricsMapping(nm, tmpl.metricsMapping)
   234  		}
   235  		return nm.NewProcessor(conf)
   236  	}, tmpl.spec)
   237  }
   238  
   239  func registerRateLimitTemplate(tmpl *compiled, set *bundle.RateLimitSet) error {
   240  	return set.Add(func(c ratelimit.Config, nm bundle.NewManagement) (types.RateLimit, error) {
   241  		newNode, err := tmpl.ExpandToNode(c.Plugin.(*yaml.Node))
   242  		if err != nil {
   243  			return nil, err
   244  		}
   245  
   246  		conf := ratelimit.NewConfig()
   247  		if err := newNode.Decode(&conf); err != nil {
   248  			return nil, err
   249  		}
   250  
   251  		if tmpl.metricsMapping != nil {
   252  			nm = WithMetricsMapping(nm, tmpl.metricsMapping)
   253  		}
   254  		return nm.NewRateLimit(conf)
   255  	}, tmpl.spec)
   256  }