233 lines
5.7 KiB
Go
233 lines
5.7 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/influxdata/toml"
|
||
|
"github.com/influxdata/toml/ast"
|
||
|
|
||
|
"github.com/influxdata/telegraf/config"
|
||
|
)
|
||
|
|
||
|
type instance struct {
|
||
|
category string
|
||
|
name string
|
||
|
enabled bool
|
||
|
dataformat []string
|
||
|
}
|
||
|
|
||
|
type selection struct {
|
||
|
plugins map[string][]instance
|
||
|
}
|
||
|
|
||
|
func ImportConfigurations(files, dirs []string) (*selection, int, error) {
|
||
|
sel := &selection{
|
||
|
plugins: make(map[string][]instance),
|
||
|
}
|
||
|
|
||
|
// Gather all configuration files
|
||
|
var filenames []string
|
||
|
filenames = append(filenames, files...)
|
||
|
|
||
|
for _, dir := range dirs {
|
||
|
// Walk the directory and get the packages
|
||
|
elements, err := os.ReadDir(dir)
|
||
|
if err != nil {
|
||
|
return nil, 0, fmt.Errorf("reading directory %q failed: %w", dir, err)
|
||
|
}
|
||
|
|
||
|
for _, element := range elements {
|
||
|
if element.IsDir() || filepath.Ext(element.Name()) != ".conf" {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
filenames = append(filenames, filepath.Join(dir, element.Name()))
|
||
|
}
|
||
|
}
|
||
|
if len(filenames) == 0 {
|
||
|
return sel, 0, errors.New("no configuration files given or found")
|
||
|
}
|
||
|
|
||
|
// Do the actual import
|
||
|
err := sel.importFiles(filenames)
|
||
|
return sel, len(filenames), err
|
||
|
}
|
||
|
|
||
|
func (s *selection) Filter(p packageCollection) (*packageCollection, error) {
|
||
|
enabled := packageCollection{
|
||
|
packages: make(map[string][]packageInfo),
|
||
|
}
|
||
|
|
||
|
implicitlyConfigured := make(map[string]bool)
|
||
|
for category, pkgs := range p.packages {
|
||
|
for _, pkg := range pkgs {
|
||
|
key := category + "." + pkg.Plugin
|
||
|
instances, found := s.plugins[key]
|
||
|
if !found {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// The package was configured so add it to the enabled list
|
||
|
enabled.packages[category] = append(enabled.packages[category], pkg)
|
||
|
|
||
|
// Check if the instances configured a data-format and decide if it
|
||
|
// is a parser or serializer depending on the plugin type.
|
||
|
// If no data-format was configured, check the default settings in
|
||
|
// case this plugin supports a data-format setting but the user
|
||
|
// didn't set it.
|
||
|
for _, instance := range instances {
|
||
|
for _, dataformat := range instance.dataformat {
|
||
|
switch category {
|
||
|
case "inputs":
|
||
|
implicitlyConfigured["parsers."+dataformat] = true
|
||
|
case "processors":
|
||
|
implicitlyConfigured["parsers."+dataformat] = true
|
||
|
// The execd processor requires both a parser and serializer
|
||
|
if pkg.Plugin == "execd" {
|
||
|
implicitlyConfigured["serializers."+dataformat] = true
|
||
|
}
|
||
|
case "outputs":
|
||
|
implicitlyConfigured["serializers."+dataformat] = true
|
||
|
}
|
||
|
}
|
||
|
if len(instance.dataformat) == 0 {
|
||
|
if pkg.DefaultParser != "" {
|
||
|
implicitlyConfigured["parsers."+pkg.DefaultParser] = true
|
||
|
}
|
||
|
if pkg.DefaultSerializer != "" {
|
||
|
implicitlyConfigured["serializers."+pkg.DefaultSerializer] = true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Iterate over all plugins AGAIN to add the implicitly configured packages
|
||
|
// such as parsers and serializers
|
||
|
for category, pkgs := range p.packages {
|
||
|
for _, pkg := range pkgs {
|
||
|
key := category + "." + pkg.Plugin
|
||
|
|
||
|
// Skip the plugins that were explicitly configured as we already
|
||
|
// added them above.
|
||
|
if _, found := s.plugins[key]; found {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Add the package if it was implicitly configured e.g. by a
|
||
|
// 'data_format' setting or by a default value for the data-format
|
||
|
if _, implicit := implicitlyConfigured[key]; implicit {
|
||
|
enabled.packages[category] = append(enabled.packages[category], pkg)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check if all packages in the config were covered
|
||
|
available := make(map[string]bool)
|
||
|
for category, pkgs := range p.packages {
|
||
|
for _, pkg := range pkgs {
|
||
|
available[category+"."+pkg.Plugin] = true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var unknown []string
|
||
|
for pkg := range s.plugins {
|
||
|
if !available[pkg] {
|
||
|
unknown = append(unknown, pkg)
|
||
|
}
|
||
|
}
|
||
|
for pkg := range implicitlyConfigured {
|
||
|
if !available[pkg] {
|
||
|
unknown = append(unknown, pkg)
|
||
|
}
|
||
|
}
|
||
|
if len(unknown) > 0 {
|
||
|
return nil, fmt.Errorf("configured but unknown packages %q", strings.Join(unknown, ","))
|
||
|
}
|
||
|
|
||
|
return &enabled, nil
|
||
|
}
|
||
|
|
||
|
func (s *selection) importFiles(configurations []string) error {
|
||
|
for _, cfg := range configurations {
|
||
|
buf, _, err := config.LoadConfigFile(cfg)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("reading %q failed: %w", cfg, err)
|
||
|
}
|
||
|
|
||
|
if err := s.extractPluginsFromConfig(buf); err != nil {
|
||
|
return fmt.Errorf("extracting plugins from %q failed: %w", cfg, err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (s *selection) extractPluginsFromConfig(buf []byte) error {
|
||
|
table, err := toml.Parse(trimBOM(buf))
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("parsing TOML failed: %w", err)
|
||
|
}
|
||
|
|
||
|
for category, subtbl := range table.Fields {
|
||
|
// Check if we should handle the category, i.e. it contains plugins
|
||
|
// to configure.
|
||
|
var valid bool
|
||
|
for _, c := range categories {
|
||
|
if c == category {
|
||
|
valid = true
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
if !valid {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
categoryTbl, ok := subtbl.(*ast.Table)
|
||
|
if !ok {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
for name, data := range categoryTbl.Fields {
|
||
|
key := category + "." + name
|
||
|
cfg := instance{
|
||
|
category: category,
|
||
|
name: name,
|
||
|
enabled: true,
|
||
|
}
|
||
|
|
||
|
// We need to check the data_format field to get all required
|
||
|
// parsers and serializers
|
||
|
pluginTables, ok := data.([]*ast.Table)
|
||
|
if ok {
|
||
|
for _, subsubtbl := range pluginTables {
|
||
|
var dataformat string
|
||
|
for field, fieldData := range subsubtbl.Fields {
|
||
|
if field != "data_format" {
|
||
|
continue
|
||
|
}
|
||
|
kv := fieldData.(*ast.KeyValue)
|
||
|
option := kv.Value.(*ast.String)
|
||
|
dataformat = option.Value
|
||
|
}
|
||
|
if dataformat != "" {
|
||
|
cfg.dataformat = append(cfg.dataformat, dataformat)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
s.plugins[key] = append(s.plugins[key], cfg)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func trimBOM(f []byte) []byte {
|
||
|
return bytes.TrimPrefix(f, []byte("\xef\xbb\xbf"))
|
||
|
}
|