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 }