1
0
Fork 0
telegraf/tools/custom_builder/packages.go
Daniel Baumann 4978089aab
Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-24 07:26:29 +02:00

345 lines
8.6 KiB
Go

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
}