Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
102
tools/custom_builder/README.md
Normal file
102
tools/custom_builder/README.md
Normal file
|
@ -0,0 +1,102 @@
|
|||
# Telegraf customization tool
|
||||
|
||||
Telegraf's `custom_builder` is a tool to select the plugins compiled into the
|
||||
Telegraf binary. By doing so, Telegraf can become smaller, saving both disk
|
||||
space and memory if only a sub-set of plugins is selected.
|
||||
|
||||
## Requirements
|
||||
|
||||
For compiling the customized binary you need the
|
||||
[Golang language](https://go.dev/) as well as the `make` build system.
|
||||
The minimum required version of Golang can be found in the *Build From Source*
|
||||
section of the `README.md` file of your version. Both the `go` and the `make`
|
||||
command must be available in your path.
|
||||
|
||||
## Downloading code
|
||||
|
||||
The first step is to download the Telegraf repository for the version you are
|
||||
planning to customize. In the example below, we want to use `v1.29.5` but you
|
||||
might also use other versions or `master`.
|
||||
|
||||
```shell
|
||||
# git clone --branch v1.29.5 --single-branch https://github.com/influxdata/telegraf.git
|
||||
...
|
||||
# cd telegraf
|
||||
```
|
||||
|
||||
Alternatively, you can download the source tarball or zip-archive of a
|
||||
[Telegraf release](https://github.com/influxdata/telegraf/releases).
|
||||
|
||||
## Building
|
||||
|
||||
To build `custom_builder` run the following command:
|
||||
|
||||
```shell
|
||||
# make build_tools
|
||||
```
|
||||
|
||||
The resulting binary is located in the `tools/custom_builder` folder.
|
||||
|
||||
## Running
|
||||
|
||||
The easiest way of building a customized Telegraf is to use your Telegraf
|
||||
configuration file(s). Assuming your configuration is in
|
||||
`/etc/telegraf/telegraf.conf` you can run
|
||||
|
||||
```shell
|
||||
# ./tools/custom_builder/custom_builder --config /etc/telegraf/telegraf.conf
|
||||
```
|
||||
|
||||
to build a Telegraf binary tailored to your configuration. You can also specify
|
||||
a configuration directory similar to Telegraf itself. To additionally use the
|
||||
configurations in `/etc/telegraf/telegraf.d` run
|
||||
|
||||
```shell
|
||||
# ./tools/custom_builder/custom_builder \
|
||||
--config /etc/telegraf/telegraf.conf \
|
||||
--config-dir /etc/telegraf/telegraf.d
|
||||
```
|
||||
|
||||
Configurations can also be retrieved from remote locations just like for
|
||||
Telegraf.
|
||||
|
||||
```shell
|
||||
# ./tools/custom_builder/custom_builder --config http://myserver/telegraf.conf
|
||||
```
|
||||
|
||||
will download the configuration from `myserver`.
|
||||
|
||||
The `--config` and `--config-dir` option can be used multiple times. In case
|
||||
you want to deploy Telegraf to multiple systems with different configurations,
|
||||
simply specify the super-set of all configurations you have. `custom_builder`
|
||||
will figure out the list for you
|
||||
|
||||
```shell
|
||||
# ./tools/custom_builder/custom_builder \
|
||||
--config system1/telegraf.conf \
|
||||
--config system2/telegraf.conf \
|
||||
--config ... \
|
||||
--config systemN/telegraf.conf \
|
||||
--config-dir system1/telegraf.d \
|
||||
--config-dir system2/telegraf.d \
|
||||
--config-dir ... \
|
||||
--config-dir systemN/telegraf.d
|
||||
```
|
||||
|
||||
The Telegraf customization uses
|
||||
[Golang's build-tags](https://pkg.go.dev/go/build#hdr-Build_Constraints) to
|
||||
select the set of plugins. To see which tags are set use the `--tags` flag.
|
||||
|
||||
To get more help run
|
||||
|
||||
```shell
|
||||
# ./tools/custom_builder/custom_builder --help
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
Please make sure to include all `parsers` and `serializers` you intend to use
|
||||
and check the enabled-plugins list.
|
||||
|
||||
Additional plugins can potentially be enabled automatically due to dependencies
|
||||
without being shown in the enabled-plugins list.
|
232
tools/custom_builder/config.go
Normal file
232
tools/custom_builder/config.go
Normal file
|
@ -0,0 +1,232 @@
|
|||
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"))
|
||||
}
|
178
tools/custom_builder/main.go
Normal file
178
tools/custom_builder/main.go
Normal file
|
@ -0,0 +1,178 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var buildTargets = []string{"build"}
|
||||
|
||||
var categories = []string{
|
||||
"aggregators",
|
||||
"inputs",
|
||||
"outputs",
|
||||
"parsers",
|
||||
"processors",
|
||||
"secretstores",
|
||||
"serializers",
|
||||
}
|
||||
|
||||
const description = `
|
||||
This is a tool build Telegraf with a custom set of plugins. The plugins are
|
||||
select according to the specified Telegraf configuration files. This allows
|
||||
to shrink the binary size by only selecting the plugins you really need.
|
||||
A more detailed documentation is available at
|
||||
http://github.com/influxdata/telegraf/tools/custom_builder/README.md
|
||||
`
|
||||
|
||||
const examples = `
|
||||
The following command with customize Telegraf to fit the configuration found
|
||||
at the default locations
|
||||
|
||||
custom_builder --config /etc/telegraf/telegraf.conf --config-dir /etc/telegraf/telegraf.d
|
||||
|
||||
You can the --config and --config-dir multiple times
|
||||
|
||||
custom_builder --config global.conf --config myinputs.conf --config myoutputs.conf
|
||||
|
||||
or use one or more remote address(es) to load the config
|
||||
|
||||
custom_builder --config global.conf --config http://myserver/plugins.conf
|
||||
|
||||
Combinations of local and remote config as well as config directories are
|
||||
possible.
|
||||
`
|
||||
|
||||
func usage() {
|
||||
fmt.Fprint(flag.CommandLine.Output(), description)
|
||||
fmt.Fprintln(flag.CommandLine.Output(), "")
|
||||
fmt.Fprintln(flag.CommandLine.Output(), "Usage:")
|
||||
fmt.Fprintln(flag.CommandLine.Output(), " custom_builder [flags]")
|
||||
fmt.Fprintln(flag.CommandLine.Output(), "")
|
||||
fmt.Fprintln(flag.CommandLine.Output(), "Flags:")
|
||||
flag.PrintDefaults()
|
||||
fmt.Fprintln(flag.CommandLine.Output(), "")
|
||||
fmt.Fprintln(flag.CommandLine.Output(), "Examples:")
|
||||
fmt.Fprint(flag.CommandLine.Output(), examples)
|
||||
fmt.Fprintln(flag.CommandLine.Output(), "")
|
||||
}
|
||||
|
||||
type cmdConfig struct {
|
||||
dryrun bool
|
||||
showtags bool
|
||||
migrations bool
|
||||
quiet bool
|
||||
root string
|
||||
configFiles []string
|
||||
configDirs []string
|
||||
}
|
||||
|
||||
func main() {
|
||||
var cfg cmdConfig
|
||||
flag.Func("config",
|
||||
"Import plugins from configuration file (can be used multiple times)",
|
||||
func(s string) error {
|
||||
cfg.configFiles = append(cfg.configFiles, s)
|
||||
return nil
|
||||
},
|
||||
)
|
||||
flag.Func("config-dir",
|
||||
"Import plugins from configs in the given directory (can be used multiple times)",
|
||||
func(s string) error {
|
||||
cfg.configDirs = append(cfg.configDirs, s)
|
||||
return nil
|
||||
},
|
||||
)
|
||||
flag.BoolVar(&cfg.dryrun, "dry-run", false, "Skip the actual building step")
|
||||
flag.BoolVar(&cfg.quiet, "quiet", false, "Print fewer log messages")
|
||||
flag.BoolVar(&cfg.migrations, "migrations", false, "Include configuration migrations")
|
||||
flag.BoolVar(&cfg.showtags, "tags", false, "Show build-tags used")
|
||||
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
tagset, err := process(&cfg)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
if len(tagset) == 0 {
|
||||
log.Fatalln("Nothing selected!")
|
||||
}
|
||||
tags := "custom,"
|
||||
if cfg.migrations {
|
||||
tags += "migrations,"
|
||||
}
|
||||
tags += strings.Join(tagset, ",")
|
||||
if cfg.showtags {
|
||||
fmt.Printf("Build tags: %s\n", tags)
|
||||
}
|
||||
|
||||
if !cfg.dryrun {
|
||||
// Perform the build
|
||||
var out bytes.Buffer
|
||||
makeCmd := exec.Command("make", buildTargets...)
|
||||
makeCmd.Env = append(os.Environ(), "BUILDTAGS="+tags)
|
||||
makeCmd.Stdout = &out
|
||||
makeCmd.Stderr = &out
|
||||
|
||||
if !cfg.quiet {
|
||||
log.Println("Running build...")
|
||||
}
|
||||
if err := makeCmd.Run(); err != nil {
|
||||
fmt.Println(out.String())
|
||||
log.Fatalf("Running make failed: %v", err)
|
||||
}
|
||||
if !cfg.quiet {
|
||||
fmt.Println(out.String())
|
||||
}
|
||||
} else if !cfg.quiet {
|
||||
log.Println("DRY-RUN: Skipping build.")
|
||||
}
|
||||
}
|
||||
|
||||
func process(cmdcfg *cmdConfig) ([]string, error) {
|
||||
// Check configuration options
|
||||
if len(cmdcfg.configFiles) == 0 && len(cmdcfg.configDirs) == 0 {
|
||||
return nil, errors.New("no configuration specified")
|
||||
}
|
||||
|
||||
// Collect all available plugins
|
||||
packages := packageCollection{root: cmdcfg.root}
|
||||
if err := packages.CollectAvailable(); err != nil {
|
||||
return nil, fmt.Errorf("collecting plugins failed: %w", err)
|
||||
}
|
||||
|
||||
// Import the plugin list from Telegraf configuration files
|
||||
log.Println("Importing configuration file(s)...")
|
||||
cfg, nfiles, err := ImportConfigurations(cmdcfg.configFiles, cmdcfg.configDirs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("importing configuration(s) failed: %w", err)
|
||||
}
|
||||
if !cmdcfg.quiet {
|
||||
log.Printf("Found %d configuration files...", nfiles)
|
||||
}
|
||||
|
||||
// Check if we do have a config
|
||||
if nfiles == 0 {
|
||||
return nil, errors.New("no configuration files loaded")
|
||||
}
|
||||
|
||||
// Process the plugin list with the given config. This will
|
||||
// only keep the plugins that adhere to the filtering criteria.
|
||||
enabled, err := cfg.Filter(packages)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("filtering packages failed: %w", err)
|
||||
}
|
||||
if !cmdcfg.quiet {
|
||||
enabled.Print()
|
||||
}
|
||||
|
||||
// Extract the build-tags
|
||||
return enabled.ExtractTags(), nil
|
||||
}
|
56
tools/custom_builder/main_test.go
Normal file
56
tools/custom_builder/main_test.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCases(t *testing.T) {
|
||||
// Silence the output
|
||||
log.SetOutput(io.Discard)
|
||||
|
||||
// Get all directories in testdata
|
||||
folders, err := os.ReadDir("testcases")
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, f := range folders {
|
||||
// Only handle folders
|
||||
if !f.IsDir() {
|
||||
continue
|
||||
}
|
||||
configFilename := filepath.Join("testcases", f.Name(), "telegraf.conf")
|
||||
expecedFilename := filepath.Join("testcases", f.Name(), "expected.tags")
|
||||
|
||||
t.Run(f.Name(), func(t *testing.T) {
|
||||
// Read the expected output
|
||||
file, err := os.Open(expecedFilename)
|
||||
require.NoError(t, err)
|
||||
defer file.Close()
|
||||
|
||||
var expected []string
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
expected = append(expected, scanner.Text())
|
||||
}
|
||||
require.NoError(t, scanner.Err())
|
||||
|
||||
// Configure the command
|
||||
cfg := &cmdConfig{
|
||||
dryrun: true,
|
||||
quiet: true,
|
||||
configFiles: []string{configFilename},
|
||||
root: "../..",
|
||||
}
|
||||
|
||||
actual, err := process(cfg)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
345
tools/custom_builder/packages.go
Normal file
345
tools/custom_builder/packages.go
Normal file
|
@ -0,0 +1,345 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/influxdata/telegraf/filter"
|
||||
)
|
||||
|
||||
// Define the categories we can handle and package filters
|
||||
var packageFilter = filter.MustCompile([]string{
|
||||
"*/all",
|
||||
"*/*_test",
|
||||
"inputs/example",
|
||||
"inputs/main",
|
||||
})
|
||||
|
||||
type packageInfo struct {
|
||||
Category string
|
||||
Plugin string
|
||||
Path string
|
||||
Tag string
|
||||
DefaultParser string
|
||||
DefaultSerializer string
|
||||
}
|
||||
|
||||
type packageCollection struct {
|
||||
root string
|
||||
packages map[string][]packageInfo
|
||||
}
|
||||
|
||||
// Define the package exceptions
|
||||
var exceptions = map[string][]packageInfo{
|
||||
"parsers": {
|
||||
{
|
||||
Category: "parsers",
|
||||
Plugin: "influx_upstream",
|
||||
Path: "plugins/parsers/influx/influx_upstream",
|
||||
Tag: "parsers.influx",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func (p *packageCollection) collectPackagesForCategory(category string) error {
|
||||
var entries []packageInfo
|
||||
pluginDir := filepath.Join(p.root, "plugins", category)
|
||||
|
||||
// Add exceptional packages if any
|
||||
if pkgs, found := exceptions[category]; found {
|
||||
entries = append(entries, pkgs...)
|
||||
}
|
||||
|
||||
// Walk the directory and get the packages
|
||||
elements, err := os.ReadDir(pluginDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, element := range elements {
|
||||
path := filepath.Join(pluginDir, element.Name())
|
||||
if !element.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
var fset token.FileSet
|
||||
pkgs, err := parser.ParseDir(&fset, path, sourceFileFilter, parser.ParseComments)
|
||||
if err != nil {
|
||||
log.Printf("parsing directory %q failed: %v", path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for name, pkg := range pkgs {
|
||||
if packageFilter.Match(category + "/" + name) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract the names of the plugins registered by this package
|
||||
registeredNames := extractRegisteredNames(pkg, category)
|
||||
if len(registeredNames) == 0 {
|
||||
log.Printf("WARN: Could not extract information from package %q", name)
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract potential default parsers for input and processor packages
|
||||
// as well as serializers for the output package
|
||||
var defaultParser, defaultSerializer string
|
||||
switch category {
|
||||
case "inputs":
|
||||
dataformat, err := extractDefaultDataFormat(path)
|
||||
if err != nil {
|
||||
log.Printf("Getting default data-format for %s.%s failed: %v", category, name, err)
|
||||
}
|
||||
defaultParser = dataformat
|
||||
case "processors":
|
||||
dataformat, err := extractDefaultDataFormat(path)
|
||||
if err != nil {
|
||||
log.Printf("Getting default data-format for %s.%s failed: %v", category, name, err)
|
||||
}
|
||||
defaultParser = dataformat
|
||||
// The execd processor requires both a parser and serializer
|
||||
if name == "execd" {
|
||||
defaultSerializer = dataformat
|
||||
}
|
||||
case "outputs":
|
||||
dataformat, err := extractDefaultDataFormat(path)
|
||||
if err != nil {
|
||||
log.Printf("Getting default data-format for %s.%s failed: %v", category, name, err)
|
||||
}
|
||||
defaultSerializer = dataformat
|
||||
}
|
||||
|
||||
for _, plugin := range registeredNames {
|
||||
path := filepath.Join("plugins", category, element.Name())
|
||||
tag := category + "." + element.Name()
|
||||
entries = append(entries, packageInfo{
|
||||
Category: category,
|
||||
Plugin: plugin,
|
||||
Path: filepath.ToSlash(path),
|
||||
Tag: tag,
|
||||
DefaultParser: defaultParser,
|
||||
DefaultSerializer: defaultSerializer,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
p.packages[category] = entries
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *packageCollection) CollectAvailable() error {
|
||||
p.packages = make(map[string][]packageInfo)
|
||||
|
||||
for _, category := range categories {
|
||||
if err := p.collectPackagesForCategory(category); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *packageCollection) ExtractTags() []string {
|
||||
var tags []string
|
||||
for category, pkgs := range p.packages {
|
||||
_ = category
|
||||
for _, pkg := range pkgs {
|
||||
tags = append(tags, pkg.Tag)
|
||||
}
|
||||
}
|
||||
sort.Strings(tags)
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
func (p *packageCollection) Print() {
|
||||
fmt.Println("-------------------------------------------------------------------------------")
|
||||
fmt.Println("Enabled plugins:")
|
||||
fmt.Println("-------------------------------------------------------------------------------")
|
||||
for _, category := range categories {
|
||||
pkgs := p.packages[category]
|
||||
sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].Plugin < pkgs[j].Plugin })
|
||||
|
||||
fmt.Printf("%s (%d):\n", category, len(pkgs))
|
||||
for _, pkg := range pkgs {
|
||||
fmt.Printf(" %-30s %s\n", pkg.Plugin, pkg.Path)
|
||||
}
|
||||
fmt.Println("-------------------------------------------------------------------------------")
|
||||
}
|
||||
}
|
||||
|
||||
func sourceFileFilter(d fs.FileInfo) bool {
|
||||
return strings.HasSuffix(d.Name(), ".go") && !strings.HasSuffix(d.Name(), "_test.go")
|
||||
}
|
||||
|
||||
func findFunctionDecl(file *ast.File, name string) *ast.FuncDecl {
|
||||
for _, decl := range file.Decls {
|
||||
d, ok := decl.(*ast.FuncDecl)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if d.Name.Name == name && d.Recv == nil {
|
||||
return d
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findAddStatements(decl *ast.FuncDecl, pluginType string) []*ast.CallExpr {
|
||||
var statements []*ast.CallExpr
|
||||
for _, stmt := range decl.Body.List {
|
||||
s, ok := stmt.(*ast.ExprStmt)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
call, ok := s.X.(*ast.CallExpr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
fun, ok := call.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
e, ok := fun.X.(*ast.Ident)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if e.Name == pluginType && (fun.Sel.Name == "Add" || fun.Sel.Name == "AddStreaming") {
|
||||
statements = append(statements, call)
|
||||
}
|
||||
}
|
||||
|
||||
return statements
|
||||
}
|
||||
|
||||
func extractPluginInfo(file *ast.File, pluginType string, declarations map[string]string) ([]string, error) {
|
||||
var registeredNames []string
|
||||
|
||||
decl := findFunctionDecl(file, "init")
|
||||
if decl == nil {
|
||||
return nil, nil
|
||||
}
|
||||
calls := findAddStatements(decl, pluginType)
|
||||
if len(calls) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
for _, call := range calls {
|
||||
switch arg := call.Args[0].(type) {
|
||||
case *ast.Ident:
|
||||
resval, found := declarations[arg.Name]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("cannot resolve registered name variable %q", arg.Name)
|
||||
}
|
||||
registeredNames = append(registeredNames, strings.Trim(resval, "\""))
|
||||
case *ast.BasicLit:
|
||||
if arg.Kind != token.STRING {
|
||||
return nil, errors.New("registered name is not a string")
|
||||
}
|
||||
registeredNames = append(registeredNames, strings.Trim(arg.Value, "\""))
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled argument type: %v (%T)", arg, arg)
|
||||
}
|
||||
}
|
||||
return registeredNames, nil
|
||||
}
|
||||
|
||||
//nolint:staticcheck // Use deprecated ast.Package for now
|
||||
func extractPackageDeclarations(pkg *ast.Package) map[string]string {
|
||||
declarations := make(map[string]string)
|
||||
|
||||
for _, file := range pkg.Files {
|
||||
for _, d := range file.Decls {
|
||||
gendecl, ok := d.(*ast.GenDecl)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, spec := range gendecl.Specs {
|
||||
spec, ok := spec.(*ast.ValueSpec)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, id := range spec.Names {
|
||||
valspec, ok := id.Obj.Decl.(*ast.ValueSpec)
|
||||
if !ok || len(valspec.Values) != 1 {
|
||||
continue
|
||||
}
|
||||
valdecl, ok := valspec.Values[0].(*ast.BasicLit)
|
||||
if !ok || valdecl.Kind != token.STRING {
|
||||
continue
|
||||
}
|
||||
declarations[id.Name] = strings.Trim(valdecl.Value, "\"")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return declarations
|
||||
}
|
||||
|
||||
//nolint:staticcheck // Use deprecated ast.Package for now
|
||||
func extractRegisteredNames(pkg *ast.Package, pluginType string) []string {
|
||||
var registeredNames []string
|
||||
|
||||
// Extract all declared variables of all files. This might be necessary when
|
||||
// using references across multiple files
|
||||
declarations := extractPackageDeclarations(pkg)
|
||||
|
||||
// Find the registry Add statement and extract all registered names
|
||||
for fn, file := range pkg.Files {
|
||||
names, err := extractPluginInfo(file, pluginType, declarations)
|
||||
if err != nil {
|
||||
log.Printf("%q error: %v", fn, err)
|
||||
continue
|
||||
}
|
||||
registeredNames = append(registeredNames, names...)
|
||||
}
|
||||
return registeredNames
|
||||
}
|
||||
|
||||
func extractDefaultDataFormat(pluginDir string) (string, error) {
|
||||
re := regexp.MustCompile(`^\s*#?\s*data_format\s*=\s*"(.*)"\s*$`)
|
||||
|
||||
// Exception for exec input which uses JSON by default
|
||||
if filepath.ToSlash(pluginDir) == "plugins/inputs/exec" {
|
||||
return "json", nil
|
||||
}
|
||||
|
||||
// Walk all config files in the package directory
|
||||
elements, err := os.ReadDir(pluginDir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, element := range elements {
|
||||
path := filepath.Join(pluginDir, element.Name())
|
||||
if element.IsDir() || filepath.Ext(element.Name()) != ".conf" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Read the config and search for a "data_format" entry
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
match := re.FindStringSubmatch(scanner.Text())
|
||||
if len(match) == 2 {
|
||||
return match[1], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
5
tools/custom_builder/testcases/issue_13592/expected.tags
Normal file
5
tools/custom_builder/testcases/issue_13592/expected.tags
Normal file
|
@ -0,0 +1,5 @@
|
|||
inputs.disk
|
||||
inputs.mem
|
||||
inputs.swap
|
||||
inputs.system
|
||||
outputs.datadog
|
65
tools/custom_builder/testcases/issue_13592/telegraf.conf
Normal file
65
tools/custom_builder/testcases/issue_13592/telegraf.conf
Normal file
|
@ -0,0 +1,65 @@
|
|||
## Telegraf Configuration for ThinClients
|
||||
## /etc/telegraf/telegraf.conf
|
||||
|
||||
[global_tags]
|
||||
service_name = "thinclient"
|
||||
env = "prod"
|
||||
team = "planetexpress"
|
||||
|
||||
## Configuration for telegraf agent
|
||||
[agent]
|
||||
## Data input and output settings
|
||||
interval = "10s"
|
||||
round_interval = true
|
||||
metric_batch_size = 1000
|
||||
metric_buffer_limit = 10000
|
||||
collection_jitter = "0s"
|
||||
flush_interval = "10s"
|
||||
flush_jitter = "5s"
|
||||
|
||||
## Logging configuration
|
||||
debug = false
|
||||
quiet = false
|
||||
# emtpy string means log to stderr
|
||||
logfile = ""
|
||||
|
||||
## host configuration
|
||||
# if emtpty use os.hostname()
|
||||
hostname = ""
|
||||
|
||||
omit_hostname = false
|
||||
|
||||
# Configuration for sending metrics to Datadog
|
||||
[[outputs.datadog]]
|
||||
## Datadog API key
|
||||
apikey = "${datadog_secret}"
|
||||
|
||||
## Connection timeout.
|
||||
timeout = "5s"
|
||||
|
||||
|
||||
## Write URL override; useful for debugging.
|
||||
url = "${datadog_url}"
|
||||
|
||||
## Metrics to log
|
||||
|
||||
[[inputs.system]]
|
||||
name_prefix = "dg.systemengineering.thinclient."
|
||||
# default configuration; getting uptime values.
|
||||
|
||||
[[inputs.mem]]
|
||||
name_prefix = "dg.systemengineering.thinclient."
|
||||
# no configuration
|
||||
|
||||
[[inputs.disk]]
|
||||
name_prefix = "dg.systemengineering.thinclient."
|
||||
## By default stats will be gathered for all mount points.
|
||||
## Set mount_points will restrict the stats to only the specified mount points.
|
||||
mount_points = ["/"]
|
||||
|
||||
[[inputs.swap]]
|
||||
name_prefix = "dg.systemengineering.thinclient."
|
||||
## Monitoring SWAP (zswap) usage
|
||||
|
||||
## Ignore mount points by filesystem type.
|
||||
#ignore_fs = ["tmpfs", "devtmpfs", "devfs", "iso9660", "overlay", "aufs", "squashfs"]
|
4
tools/custom_builder/testcases/issue_15627/expected.tags
Normal file
4
tools/custom_builder/testcases/issue_15627/expected.tags
Normal file
|
@ -0,0 +1,4 @@
|
|||
inputs.mqtt_consumer
|
||||
outputs.influxdb_v2
|
||||
parsers.json_v2
|
||||
parsers.value
|
39
tools/custom_builder/testcases/issue_15627/telegraf.conf
Normal file
39
tools/custom_builder/testcases/issue_15627/telegraf.conf
Normal file
|
@ -0,0 +1,39 @@
|
|||
[[inputs.mqtt_consumer]]
|
||||
name_override = "qr_mqtt_message"
|
||||
servers = ["tcp://mosquitto:1883"]
|
||||
topics = [
|
||||
"<REDACTED>"
|
||||
]
|
||||
|
||||
qos = 2
|
||||
persistent_session = false
|
||||
client_id = "telegraf_qr_code"
|
||||
|
||||
data_format = "json_v2"
|
||||
|
||||
[[inputs.mqtt_consumer.json_v2]]
|
||||
[[inputs.mqtt_consumer.json_v2.object]]
|
||||
path = "message.data"
|
||||
tags = ["data"]
|
||||
|
||||
[[inputs.mqtt_consumer]]
|
||||
name_override = "raw_mqtt_message"
|
||||
servers = ["tcp://mosquitto:1883"]
|
||||
|
||||
# Capture the content as a string since we do not know the format of it...
|
||||
data_format = "value"
|
||||
data_type = "string"
|
||||
|
||||
# Capture all topics and store the topic as a tag with name "topic"...
|
||||
topics = ["#"]
|
||||
topic_tag = "topic"
|
||||
|
||||
qos = 2
|
||||
persistent_session = false
|
||||
client_id = "telegraf_generic"
|
||||
|
||||
[[outputs.influxdb_v2]]
|
||||
urls = ["http://influxdb:8086"]
|
||||
token = "${INFLUX_TOKEN}"
|
||||
organization = "test"
|
||||
bucket = "test_bucket"
|
Loading…
Add table
Add a link
Reference in a new issue