1
0
Fork 0
telegraf/config/migration.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

261 lines
6.8 KiB
Go

package config
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"log"
"sort"
"strings"
"github.com/influxdata/toml"
"github.com/influxdata/toml/ast"
"github.com/influxdata/telegraf/migrations"
_ "github.com/influxdata/telegraf/migrations/all" // register all migrations
)
type section struct {
name string
begin int
content *ast.Table
raw *bytes.Buffer
}
func splitToSections(root *ast.Table) ([]section, error) {
var sections []section
for name, elements := range root.Fields {
switch name {
case "inputs", "outputs", "processors", "aggregators":
category, ok := elements.(*ast.Table)
if !ok {
return nil, fmt.Errorf("%q is not a table (%T)", name, category)
}
for plugin, elements := range category.Fields {
tbls, ok := elements.([]*ast.Table)
if !ok {
return nil, fmt.Errorf("elements of \"%s.%s\" is not a list of tables (%T)", name, plugin, elements)
}
for _, tbl := range tbls {
s := section{
name: name + "." + tbl.Name,
begin: tbl.Line,
content: tbl,
raw: &bytes.Buffer{},
}
sections = append(sections, s)
}
}
default:
tbl, ok := elements.(*ast.Table)
if !ok {
return nil, fmt.Errorf("%q is not a table (%T)", name, elements)
}
s := section{
name: name,
begin: tbl.Line,
content: tbl,
raw: &bytes.Buffer{},
}
sections = append(sections, s)
}
}
// Sort the TOML elements by begin (line-number)
sort.SliceStable(sections, func(i, j int) bool { return sections[i].begin < sections[j].begin })
return sections, nil
}
func assignTextToSections(data []byte, sections []section) ([]section, error) {
// Now assign the raw text to each section
if sections[0].begin > 0 {
sections = append([]section{{
name: "header",
begin: 0,
raw: &bytes.Buffer{},
}}, sections...)
}
var lineno int
scanner := bufio.NewScanner(bytes.NewBuffer(data))
for idx, next := range sections[1:] {
var buf bytes.Buffer
for lineno < next.begin-1 {
if !scanner.Scan() {
break
}
lineno++
line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, "#") {
buf.Write(scanner.Bytes())
buf.WriteString("\n")
continue
} else if buf.Len() > 0 {
if _, err := io.Copy(sections[idx].raw, &buf); err != nil {
return nil, fmt.Errorf("copying buffer failed: %w", err)
}
buf.Reset()
}
sections[idx].raw.Write(scanner.Bytes())
sections[idx].raw.WriteString("\n")
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("splitting by line failed: %w", err)
}
// If a comment is directly in front of the next section, without
// newline, the comment is assigned to the next section.
if buf.Len() > 0 {
if _, err := io.Copy(sections[idx+1].raw, &buf); err != nil {
return nil, fmt.Errorf("copying buffer failed: %w", err)
}
buf.Reset()
}
}
// Write the remaining to the last section
for scanner.Scan() {
sections[len(sections)-1].raw.Write(scanner.Bytes())
sections[len(sections)-1].raw.WriteString("\n")
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("splitting by line failed: %w", err)
}
return sections, nil
}
func ApplyMigrations(data []byte) ([]byte, uint64, error) {
root, err := toml.Parse(data)
if err != nil {
return nil, 0, fmt.Errorf("parsing failed: %w", err)
}
// Split the configuration into sections containing the location
// in the file.
sections, err := splitToSections(root)
if err != nil {
return nil, 0, fmt.Errorf("splitting to sections failed: %w", err)
}
if len(sections) == 0 {
return nil, 0, errors.New("no TOML configuration found")
}
// Assign the configuration text to the corresponding segments
sections, err = assignTextToSections(data, sections)
if err != nil {
return nil, 0, fmt.Errorf("assigning text failed: %w", err)
}
var applied uint64
// Do the actual global section migration(s)
for idx, s := range sections {
if strings.Contains(s.name, ".") {
continue
}
log.Printf("D! applying global migrations to section %q in line %d...", s.name, s.begin)
for _, migrate := range migrations.GlobalMigrations {
result, msg, err := migrate(s.name, s.content)
if err != nil {
if errors.Is(err, migrations.ErrNotApplicable) {
continue
}
return nil, 0, fmt.Errorf("migrating options of %q (line %d) failed: %w", s.name, s.begin, err)
}
if msg != "" {
log.Printf("I! Global section %q in line %d: %s", s.name, s.begin, msg)
}
s.raw = bytes.NewBuffer(result)
applied++
}
sections[idx] = s
}
// Do the actual plugin migration(s)
for idx, s := range sections {
migrate, found := migrations.PluginMigrations[s.name]
if !found {
continue
}
log.Printf("D! migrating plugin %q in line %d...", s.name, s.begin)
result, msg, err := migrate(s.content)
if err != nil {
return nil, 0, fmt.Errorf("migrating %q (line %d) failed: %w", s.name, s.begin, err)
}
if msg != "" {
log.Printf("I! Plugin %q in line %d: %s", s.name, s.begin, msg)
}
s.raw = bytes.NewBuffer(result)
tbl, err := toml.Parse(s.raw.Bytes())
if err != nil {
return nil, 0, fmt.Errorf("reparsing migrated %q (line %d) failed: %w", s.name, s.begin, err)
}
s.content = tbl
sections[idx] = s
applied++
}
// Do the actual plugin option migration(s)
for idx, s := range sections {
migrate, found := migrations.PluginOptionMigrations[s.name]
if !found {
continue
}
log.Printf("D! migrating options of plugin %q in line %d...", s.name, s.begin)
result, msg, err := migrate(s.content)
if err != nil {
if errors.Is(err, migrations.ErrNotApplicable) {
continue
}
return nil, 0, fmt.Errorf("migrating options of %q (line %d) failed: %w", s.name, s.begin, err)
}
if msg != "" {
log.Printf("I! Plugin %q in line %d: %s", s.name, s.begin, msg)
}
s.raw = bytes.NewBuffer(result)
sections[idx] = s
applied++
}
// Do general migrations applying to all plugins
for idx, s := range sections {
parts := strings.Split(s.name, ".")
if len(parts) != 2 {
continue
}
log.Printf("D! applying general migrations to plugin %q in line %d...", s.name, s.begin)
category, name := parts[0], parts[1]
for _, migrate := range migrations.GeneralMigrations {
result, msg, err := migrate(category, name, s.content)
if err != nil {
if errors.Is(err, migrations.ErrNotApplicable) {
continue
}
return nil, 0, fmt.Errorf("migrating options of %q (line %d) failed: %w", s.name, s.begin, err)
}
if msg != "" {
log.Printf("I! Plugin %q in line %d: %s", s.name, s.begin, msg)
}
s.raw = bytes.NewBuffer(result)
applied++
}
sections[idx] = s
}
// Reconstruct the config file from the sections
var buf bytes.Buffer
for _, s := range sections {
_, err = s.raw.WriteTo(&buf)
if err != nil {
return nil, applied, fmt.Errorf("joining output failed: %w", err)
}
}
return buf.Bytes(), applied, nil
}