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
1
cmd/telegraf/README.md
Symbolic link
1
cmd/telegraf/README.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../docs/COMMANDS_AND_FLAGS.md
|
104
cmd/telegraf/agent.conf
Normal file
104
cmd/telegraf/agent.conf
Normal file
|
@ -0,0 +1,104 @@
|
|||
# Configuration for telegraf agent
|
||||
[agent]
|
||||
## Default data collection interval for all inputs
|
||||
interval = "10s"
|
||||
## Rounds collection interval to 'interval'
|
||||
## ie, if interval="10s" then always collect on :00, :10, :20, etc.
|
||||
round_interval = true
|
||||
|
||||
## Telegraf will send metrics to outputs in batches of at most
|
||||
## metric_batch_size metrics.
|
||||
## This controls the size of writes that Telegraf sends to output plugins.
|
||||
metric_batch_size = 1000
|
||||
|
||||
## Maximum number of unwritten metrics per output. Increasing this value
|
||||
## allows for longer periods of output downtime without dropping metrics at the
|
||||
## cost of higher maximum memory usage.
|
||||
metric_buffer_limit = 10000
|
||||
|
||||
## Collection jitter is used to jitter the collection by a random amount.
|
||||
## Each plugin will sleep for a random time within jitter before collecting.
|
||||
## This can be used to avoid many plugins querying things like sysfs at the
|
||||
## same time, which can have a measurable effect on the system.
|
||||
collection_jitter = "0s"
|
||||
|
||||
## Collection offset is used to shift the collection by the given amount.
|
||||
## This can be be used to avoid many plugins querying constraint devices
|
||||
## at the same time by manually scheduling them in time.
|
||||
# collection_offset = "0s"
|
||||
|
||||
## Default flushing interval for all outputs. Maximum flush_interval will be
|
||||
## flush_interval + flush_jitter
|
||||
flush_interval = "10s"
|
||||
## Jitter the flush interval by a random amount. This is primarily to avoid
|
||||
## large write spikes for users running a large number of telegraf instances.
|
||||
## ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s
|
||||
flush_jitter = "0s"
|
||||
|
||||
## Collected metrics are rounded to the precision specified. Precision is
|
||||
## specified as an interval with an integer + unit (e.g. 0s, 10ms, 2us, 4s).
|
||||
## Valid time units are "ns", "us" (or "µs"), "ms", "s".
|
||||
##
|
||||
## By default or when set to "0s", precision will be set to the same
|
||||
## timestamp order as the collection interval, with the maximum being 1s:
|
||||
## ie, when interval = "10s", precision will be "1s"
|
||||
## when interval = "250ms", precision will be "1ms"
|
||||
##
|
||||
## Precision will NOT be used for service inputs. It is up to each individual
|
||||
## service input to set the timestamp at the appropriate precision.
|
||||
precision = "0s"
|
||||
|
||||
## Log at debug level.
|
||||
# debug = false
|
||||
## Log only error level messages.
|
||||
# quiet = false
|
||||
|
||||
## Log format controls the way messages are logged and can be one of "text",
|
||||
## "structured" or, on Windows, "eventlog".
|
||||
# logformat = "text"
|
||||
|
||||
## Message key for structured logs, to override the default of "msg".
|
||||
## Ignored if `logformat` is not "structured".
|
||||
# structured_log_message_key = "message"
|
||||
|
||||
## Name of the file to be logged to or stderr if unset or empty. This
|
||||
## setting is ignored for the "eventlog" format.
|
||||
# logfile = ""
|
||||
|
||||
## The logfile will be rotated after the time interval specified. When set
|
||||
## to 0 no time based rotation is performed. Logs are rotated only when
|
||||
## written to, if there is no log activity rotation may be delayed.
|
||||
# logfile_rotation_interval = "0h"
|
||||
|
||||
## The logfile will be rotated when it becomes larger than the specified
|
||||
## size. When set to 0 no size based rotation is performed.
|
||||
# logfile_rotation_max_size = "0MB"
|
||||
|
||||
## Maximum number of rotated archives to keep, any older logs are deleted.
|
||||
## If set to -1, no archives are removed.
|
||||
# logfile_rotation_max_archives = 5
|
||||
|
||||
## Pick a timezone to use when logging or type 'local' for local time.
|
||||
## Example: America/Chicago
|
||||
# log_with_timezone = ""
|
||||
|
||||
## Override default hostname, if empty use os.Hostname()
|
||||
# hostname = ""
|
||||
## If set to true, do no set the "host" tag in the telegraf agent.
|
||||
# omit_hostname = false
|
||||
|
||||
## Method of translating SNMP objects. Can be "netsnmp" (deprecated) which
|
||||
## translates by calling external programs snmptranslate and snmptable,
|
||||
## or "gosmi" which translates using the built-in gosmi library.
|
||||
# snmp_translator = "netsnmp"
|
||||
|
||||
## Name of the file to load the state of plugins from and store the state to.
|
||||
## If uncommented and not empty, this file will be used to save the state of
|
||||
## stateful plugins on termination of Telegraf. If the file exists on start,
|
||||
## the state in the file will be restored for the plugins.
|
||||
# statefile = ""
|
||||
|
||||
## Flag to skip running processors after aggregators
|
||||
## By default, processors are run a second time after aggregators. Changing
|
||||
## this setting to true will skip the second run of processors.
|
||||
# skip_processors_after_aggregators = false
|
240
cmd/telegraf/cmd_config.go
Normal file
240
cmd/telegraf/cmd_config.go
Normal file
|
@ -0,0 +1,240 @@
|
|||
// Command handling for configuration "config" command
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/influxdata/telegraf/agent"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/logger"
|
||||
"github.com/influxdata/telegraf/migrations"
|
||||
)
|
||||
|
||||
func getConfigCommands(configHandlingFlags []cli.Flag, outputBuffer io.Writer) []*cli.Command {
|
||||
return []*cli.Command{
|
||||
{
|
||||
Name: "config",
|
||||
Usage: "commands for generating and migrating configurations",
|
||||
Flags: configHandlingFlags,
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
// The sub_Filters are populated when the filter flags are set after the subcommand config
|
||||
// e.g. telegraf config --section-filter inputs
|
||||
filters := processFilterFlags(cCtx)
|
||||
|
||||
printSampleConfig(outputBuffer, filters)
|
||||
return nil
|
||||
},
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "check",
|
||||
Usage: "check configuration file(s) for issues",
|
||||
Description: `
|
||||
The 'check' command reads the configuration files specified via '--config' or
|
||||
'--config-directory' and tries to initialize, but not start, the plugins.
|
||||
Syntax and semantic errors detectable without starting the plugins will
|
||||
be reported.
|
||||
If no configuration file is explicitly specified the command reads the
|
||||
default locations and uses those configuration files.
|
||||
|
||||
To check the file 'mysettings.conf' use
|
||||
|
||||
> telegraf config check --config mysettings.conf
|
||||
`,
|
||||
Flags: configHandlingFlags,
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
// Setup logging
|
||||
logConfig := &logger.Config{Debug: cCtx.Bool("debug")}
|
||||
if err := logger.SetupLogging(logConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Collect the given configuration files
|
||||
configFiles := cCtx.StringSlice("config")
|
||||
configDir := cCtx.StringSlice("config-directory")
|
||||
for _, fConfigDirectory := range configDir {
|
||||
files, err := config.WalkDirectory(fConfigDirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configFiles = append(configFiles, files...)
|
||||
}
|
||||
|
||||
// If no "config" or "config-directory" flag(s) was
|
||||
// provided we should load default configuration files
|
||||
if len(configFiles) == 0 {
|
||||
paths, err := config.GetDefaultConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configFiles = paths
|
||||
}
|
||||
|
||||
// Load the config and try to initialize the plugins
|
||||
c := config.NewConfig()
|
||||
c.Agent.Quiet = cCtx.Bool("quiet")
|
||||
if err := c.LoadAll(configFiles...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ag := agent.NewAgent(c)
|
||||
|
||||
// Set the default for processor skipping
|
||||
if c.Agent.SkipProcessorsAfterAggregators == nil {
|
||||
msg := `The default value of 'skip_processors_after_aggregators' will change to 'true' with Telegraf v1.40.0! `
|
||||
msg += `If you need the current default behavior, please explicitly set the option to 'false'!`
|
||||
log.Print("W! [agent] ", color.YellowString(msg))
|
||||
skipProcessorsAfterAggregators := false
|
||||
c.Agent.SkipProcessorsAfterAggregators = &skipProcessorsAfterAggregators
|
||||
}
|
||||
|
||||
return ag.InitPlugins()
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "create",
|
||||
Usage: "create a full sample configuration and show it",
|
||||
Description: `
|
||||
The 'create' produces a full configuration containing all plugins as an example
|
||||
and shows it on the console. You may apply 'section' or 'plugin' filtering
|
||||
to reduce the output to the plugins you need
|
||||
|
||||
Create the full configuration
|
||||
|
||||
> telegraf config create
|
||||
|
||||
To produce a configuration only containing a Modbus input plugin and an
|
||||
InfluxDB v2 output plugin use
|
||||
|
||||
> telegraf config create --section-filter "inputs:outputs" --input-filter "modbus" --output-filter "influxdb_v2"
|
||||
`,
|
||||
Flags: configHandlingFlags,
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
filters := processFilterFlags(cCtx)
|
||||
|
||||
printSampleConfig(outputBuffer, filters)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "migrate",
|
||||
Usage: "migrate deprecated plugins and options of the configuration(s)",
|
||||
Description: `
|
||||
The 'migrate' command reads the configuration files specified via '--config' or
|
||||
'--config-directory' and tries to migrate plugins or options that are currently
|
||||
deprecated using the recommended replacements. If no configuration file is
|
||||
explicitly specified the command reads the default locations and uses those
|
||||
configuration files. Migrated files are stored with a '.migrated' suffix at the
|
||||
location of the inputs. If you are migrating remote configurations the migrated
|
||||
configurations is stored in the current directory using the filename of the URL
|
||||
with a '.migrated' suffix.
|
||||
It is highly recommended to test those migrated configurations before using
|
||||
those files unattended!
|
||||
|
||||
To migrate the file 'mysettings.conf' use
|
||||
|
||||
> telegraf config migrate --config mysettings.conf
|
||||
`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "force",
|
||||
Usage: "forces overwriting of an existing migration file",
|
||||
},
|
||||
},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
// Setup logging
|
||||
logConfig := &logger.Config{Debug: cCtx.Bool("debug")}
|
||||
if err := logger.SetupLogging(logConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if we have migrations at all. There might be
|
||||
// none if you run a custom build without migrations
|
||||
// enabled.
|
||||
if len(migrations.PluginMigrations) == 0 {
|
||||
return errors.New("no migrations available")
|
||||
}
|
||||
log.Printf("%d plugin migration(s) available", len(migrations.PluginMigrations))
|
||||
|
||||
// Collect the given configuration files
|
||||
configFiles := cCtx.StringSlice("config")
|
||||
configDir := cCtx.StringSlice("config-directory")
|
||||
for _, fConfigDirectory := range configDir {
|
||||
files, err := config.WalkDirectory(fConfigDirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configFiles = append(configFiles, files...)
|
||||
}
|
||||
|
||||
// If no "config" or "config-directory" flag(s) was
|
||||
// provided we should load default configuration files
|
||||
if len(configFiles) == 0 {
|
||||
paths, err := config.GetDefaultConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configFiles = paths
|
||||
}
|
||||
|
||||
for _, fn := range configFiles {
|
||||
log.Printf("D! Trying to migrate %q...", fn)
|
||||
|
||||
// Read and parse the config file
|
||||
data, remote, err := config.LoadConfigFile(fn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening input %q failed: %w", fn, err)
|
||||
}
|
||||
|
||||
out, applied, err := config.ApplyMigrations(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Do not write a migration file if nothing was done
|
||||
if applied == 0 {
|
||||
log.Printf("I! No migration applied for %q", fn)
|
||||
continue
|
||||
}
|
||||
|
||||
// Construct the output filename
|
||||
// For remote locations we just save the filename
|
||||
// with the migrated suffix.
|
||||
outfn := fn + ".migrated"
|
||||
if remote {
|
||||
u, err := url.Parse(fn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing remote config URL %q failed: %w", fn, err)
|
||||
}
|
||||
outfn = filepath.Base(u.Path) + ".migrated"
|
||||
}
|
||||
|
||||
log.Printf("I! %d migration applied for %q, writing result as %q", applied, fn, outfn)
|
||||
|
||||
// Make sure the file does not exist yet if we should not overwrite
|
||||
if !cCtx.Bool("force") {
|
||||
if _, err := os.Stat(outfn); !errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("output file %q already exists", outfn)
|
||||
}
|
||||
}
|
||||
|
||||
// Write the output file
|
||||
if err := os.WriteFile(outfn, out, 0640); err != nil {
|
||||
return fmt.Errorf("writing output %q failed: %w", outfn, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
192
cmd/telegraf/cmd_plugins.go
Normal file
192
cmd/telegraf/cmd_plugins.go
Normal file
|
@ -0,0 +1,192 @@
|
|||
// Command handling for configuration "plugins" command
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/influxdata/telegraf/plugins/aggregators"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
"github.com/influxdata/telegraf/plugins/outputs"
|
||||
"github.com/influxdata/telegraf/plugins/parsers"
|
||||
"github.com/influxdata/telegraf/plugins/processors"
|
||||
"github.com/influxdata/telegraf/plugins/secretstores"
|
||||
"github.com/influxdata/telegraf/plugins/serializers"
|
||||
)
|
||||
|
||||
func pluginNames[M ~map[string]V, V any](m M, prefix string) []byte {
|
||||
names := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
names = append(names, fmt.Sprintf("%s.%s\n", prefix, k))
|
||||
}
|
||||
sort.Strings(names)
|
||||
return []byte(strings.Join(names, ""))
|
||||
}
|
||||
|
||||
func getPluginCommands(outputBuffer io.Writer) []*cli.Command {
|
||||
return []*cli.Command{
|
||||
{
|
||||
Name: "plugins",
|
||||
Usage: "commands for printing available plugins",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "deprecated",
|
||||
Usage: "print only deprecated plugins",
|
||||
},
|
||||
},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
if cCtx.Bool("deprecated") {
|
||||
outputBuffer.Write(pluginNames(inputs.Deprecations, "inputs"))
|
||||
outputBuffer.Write(pluginNames(outputs.Deprecations, "outputs"))
|
||||
outputBuffer.Write(pluginNames(processors.Deprecations, "processors"))
|
||||
outputBuffer.Write(pluginNames(aggregators.Deprecations, "aggregators"))
|
||||
outputBuffer.Write(pluginNames(secretstores.Deprecations, "secretstores"))
|
||||
outputBuffer.Write(pluginNames(parsers.Deprecations, "parsers"))
|
||||
outputBuffer.Write(pluginNames(serializers.Deprecations, "serializers"))
|
||||
} else {
|
||||
outputBuffer.Write(pluginNames(inputs.Inputs, "inputs"))
|
||||
outputBuffer.Write(pluginNames(outputs.Outputs, "outputs"))
|
||||
outputBuffer.Write(pluginNames(processors.Processors, "processors"))
|
||||
outputBuffer.Write(pluginNames(aggregators.Aggregators, "aggregators"))
|
||||
outputBuffer.Write(pluginNames(secretstores.SecretStores, "secretstores"))
|
||||
outputBuffer.Write(pluginNames(parsers.Parsers, "parsers"))
|
||||
outputBuffer.Write(pluginNames(serializers.Serializers, "serializers"))
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "inputs",
|
||||
Usage: "Print available input plugins",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "deprecated",
|
||||
Usage: "print only deprecated plugins",
|
||||
},
|
||||
},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
if cCtx.Bool("deprecated") {
|
||||
outputBuffer.Write(pluginNames(inputs.Deprecations, "inputs"))
|
||||
} else {
|
||||
outputBuffer.Write(pluginNames(inputs.Inputs, "inputs"))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "outputs",
|
||||
Usage: "Print available output plugins",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "deprecated",
|
||||
Usage: "print only deprecated plugins",
|
||||
},
|
||||
},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
if cCtx.Bool("deprecated") {
|
||||
outputBuffer.Write(pluginNames(outputs.Deprecations, "outputs"))
|
||||
} else {
|
||||
outputBuffer.Write(pluginNames(outputs.Outputs, "outputs"))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "processors",
|
||||
Usage: "Print available processor plugins",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "deprecated",
|
||||
Usage: "print only deprecated plugins",
|
||||
},
|
||||
},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
if cCtx.Bool("deprecated") {
|
||||
outputBuffer.Write(pluginNames(processors.Deprecations, "processors"))
|
||||
} else {
|
||||
outputBuffer.Write(pluginNames(processors.Processors, "processors"))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "aggregators",
|
||||
Usage: "Print available aggregator plugins",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "deprecated",
|
||||
Usage: "print only deprecated plugins",
|
||||
},
|
||||
},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
if cCtx.Bool("deprecated") {
|
||||
outputBuffer.Write(pluginNames(aggregators.Deprecations, "aggregators"))
|
||||
} else {
|
||||
outputBuffer.Write(pluginNames(aggregators.Aggregators, "aggregators"))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "secretstores",
|
||||
Usage: "Print available secretstore plugins",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "deprecated",
|
||||
Usage: "print only deprecated plugins",
|
||||
},
|
||||
},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
if cCtx.Bool("deprecated") {
|
||||
outputBuffer.Write(pluginNames(secretstores.Deprecations, "secretstores"))
|
||||
} else {
|
||||
outputBuffer.Write(pluginNames(secretstores.SecretStores, "secretstores"))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "parsers",
|
||||
Usage: "Print available parser plugins",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "deprecated",
|
||||
Usage: "print only deprecated plugins",
|
||||
},
|
||||
},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
if cCtx.Bool("deprecated") {
|
||||
outputBuffer.Write(pluginNames(parsers.Deprecations, "parsers"))
|
||||
} else {
|
||||
outputBuffer.Write(pluginNames(parsers.Parsers, "parsers"))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "serializers",
|
||||
Usage: "Print available serializer plugins",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "deprecated",
|
||||
Usage: "print only deprecated plugins",
|
||||
},
|
||||
},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
if cCtx.Bool("deprecated") {
|
||||
outputBuffer.Write(pluginNames(serializers.Deprecations, "serializers"))
|
||||
} else {
|
||||
outputBuffer.Write(pluginNames(serializers.Serializers, "serializers"))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
261
cmd/telegraf/cmd_secretstore.go
Normal file
261
cmd/telegraf/cmd_secretstore.go
Normal file
|
@ -0,0 +1,261 @@
|
|||
// Command handling for secret-stores' "secrets" command
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/awnumar/memguard"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
func processFilterOnlySecretStoreFlags(ctx *cli.Context) Filters {
|
||||
sectionFilters := []string{"inputs", "outputs", "processors", "aggregators"}
|
||||
inputFilters := []string{"-"}
|
||||
outputFilters := []string{"-"}
|
||||
processorFilters := []string{"-"}
|
||||
aggregatorFilters := []string{"-"}
|
||||
|
||||
// Only load the secret-stores
|
||||
var secretstore string
|
||||
if len(ctx.Lineage()) >= 2 {
|
||||
parent := ctx.Lineage()[1] // ancestor contexts in order from child to parent
|
||||
secretstore = parent.String("secretstore-filter")
|
||||
}
|
||||
|
||||
// If both the parent and command filters are defined, append them together
|
||||
secretstore = appendFilter(secretstore, ctx.String("secretstore-filter"))
|
||||
secretstoreFilters := deleteEmpty(strings.Split(secretstore, ":"))
|
||||
return Filters{sectionFilters, inputFilters, outputFilters, aggregatorFilters, processorFilters, secretstoreFilters}
|
||||
}
|
||||
|
||||
func getSecretStoreCommands(m App) []*cli.Command {
|
||||
return []*cli.Command{
|
||||
{
|
||||
Name: "secrets",
|
||||
Usage: "commands for listing, adding and removing secrets on all known secret-stores",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "list known secrets and secret-stores",
|
||||
Description: `
|
||||
The 'list' command requires passing in your configuration file
|
||||
containing the secret-store definitions you want to access. To get a
|
||||
list of available secret-store plugins, please have a look at
|
||||
https://github.com/influxdata/telegraf/tree/master/plugins/secretstores.
|
||||
|
||||
For help on how to define secret-stores, check the documentation of the
|
||||
different plugins.
|
||||
|
||||
Assuming you use the default configuration file location, you can run
|
||||
the following command to list the keys of all known secrets in ALL
|
||||
available stores
|
||||
|
||||
> telegraf secrets list
|
||||
|
||||
To get the keys of all known secrets in a particular store, you can run
|
||||
|
||||
> telegraf secrets list mystore
|
||||
|
||||
To also reveal the actual secret, i.e. the value, you can pass the
|
||||
'--reveal-secret' flag.
|
||||
`,
|
||||
ArgsUsage: "[secret-store ID]...[secret-store ID]",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "reveal-secret",
|
||||
Usage: "also print the secret value",
|
||||
},
|
||||
},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
// Only load the secret-stores
|
||||
filters := processFilterOnlySecretStoreFlags(cCtx)
|
||||
g := GlobalFlags{
|
||||
config: cCtx.StringSlice("config"),
|
||||
configDir: cCtx.StringSlice("config-directory"),
|
||||
plugindDir: cCtx.String("plugin-directory"),
|
||||
password: cCtx.String("password"),
|
||||
debug: cCtx.Bool("debug"),
|
||||
}
|
||||
w := WindowFlags{}
|
||||
m.Init(nil, filters, g, w)
|
||||
|
||||
args := cCtx.Args()
|
||||
var storeIDs []string
|
||||
if args.Present() {
|
||||
storeIDs = args.Slice()
|
||||
} else {
|
||||
ids, err := m.ListSecretStores()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to determine secret-store IDs: %w", err)
|
||||
}
|
||||
storeIDs = ids
|
||||
}
|
||||
sort.Strings(storeIDs)
|
||||
|
||||
reveal := cCtx.Bool("reveal-secret")
|
||||
for _, storeID := range storeIDs {
|
||||
store, err := m.GetSecretStore(storeID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get secret-store %q: %w", storeID, err)
|
||||
}
|
||||
keys, err := store.List()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get secrets from store %q: %w", storeID, err)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
fmt.Printf("Known secrets for store %q:\n", storeID)
|
||||
for _, k := range keys {
|
||||
var v []byte
|
||||
if reveal {
|
||||
if v, err = store.Get(k); err != nil {
|
||||
return fmt.Errorf("unable to get value of secret %q from store %q: %w", k, storeID, err)
|
||||
}
|
||||
}
|
||||
fmt.Printf(" %-30s %s\n", k, string(v))
|
||||
memguard.WipeBytes(v)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "get",
|
||||
Usage: "retrieves value of given secret from given store",
|
||||
Description: `
|
||||
The 'get' command requires passing in your configuration file
|
||||
containing the secret-store definitions you want to access. To get a
|
||||
list of available secret-store plugins, please have a look at
|
||||
https://github.com/influxdata/telegraf/tree/master/plugins/secretstores.
|
||||
and use the 'secrets list' command to get the IDs of available stores and
|
||||
key(s) of available secrets.
|
||||
|
||||
For help on how to define secret-stores, check the documentation of the
|
||||
different plugins.
|
||||
|
||||
Assuming you use the default configuration file location, you can run
|
||||
the following command to retrieve a secret from a secret store
|
||||
available stores
|
||||
|
||||
> telegraf secrets get mystore mysecretkey
|
||||
|
||||
This will fetch the secret with the key 'mysecretkey' from the secret-store
|
||||
with the ID 'mystore'.
|
||||
`,
|
||||
ArgsUsage: "<secret-store ID> <secret key>",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
// Only load the secret-stores
|
||||
filters := processFilterOnlySecretStoreFlags(cCtx)
|
||||
g := GlobalFlags{
|
||||
config: cCtx.StringSlice("config"),
|
||||
configDir: cCtx.StringSlice("config-directory"),
|
||||
plugindDir: cCtx.String("plugin-directory"),
|
||||
password: cCtx.String("password"),
|
||||
debug: cCtx.Bool("debug"),
|
||||
}
|
||||
w := WindowFlags{}
|
||||
m.Init(nil, filters, g, w)
|
||||
|
||||
args := cCtx.Args()
|
||||
if !args.Present() || args.Len() != 2 {
|
||||
return errors.New("invalid number of arguments")
|
||||
}
|
||||
|
||||
storeID := args.First()
|
||||
key := args.Get(1)
|
||||
|
||||
store, err := m.GetSecretStore(storeID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get secret-store: %w", err)
|
||||
}
|
||||
value, err := store.Get(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get secret: %w", err)
|
||||
}
|
||||
fmt.Printf("%s:%s = %s\n", storeID, key, value)
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "set",
|
||||
Usage: "create or modify a secret in the given store",
|
||||
Description: `
|
||||
The 'set' command requires passing in your configuration file
|
||||
containing the secret-store definitions you want to access. To get a
|
||||
list of available secret-store plugins, please have a look at
|
||||
https://github.com/influxdata/telegraf/tree/master/plugins/secretstores.
|
||||
and use the 'secrets list' command to get the IDs of available stores and keys.
|
||||
|
||||
For help on how to define secret-stores, check the documentation of the
|
||||
different plugins.
|
||||
|
||||
Assuming you use the default configuration file location, you can run
|
||||
the following command to create a secret in anm available secret-store
|
||||
|
||||
> telegraf secrets set mystore mysecretkey mysecretvalue
|
||||
|
||||
This will create a secret with the key 'mysecretkey' in the secret-store
|
||||
with the ID 'mystore' with the value being set to 'mysecretvalue'. If a
|
||||
secret with that key ('mysecretkey') already existed in that store, its
|
||||
value will be modified.
|
||||
|
||||
When you leave out the value of the secret like
|
||||
|
||||
> telegraf secrets set mystore mysecretkey
|
||||
|
||||
you will be prompted to enter the value of the secret.
|
||||
`,
|
||||
ArgsUsage: "<secret-store ID> <secret key>",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
// Only load the secret-stores
|
||||
filters := processFilterOnlySecretStoreFlags(cCtx)
|
||||
g := GlobalFlags{
|
||||
config: cCtx.StringSlice("config"),
|
||||
configDir: cCtx.StringSlice("config-directory"),
|
||||
plugindDir: cCtx.String("plugin-directory"),
|
||||
password: cCtx.String("password"),
|
||||
debug: cCtx.Bool("debug"),
|
||||
}
|
||||
w := WindowFlags{}
|
||||
m.Init(nil, filters, g, w)
|
||||
|
||||
args := cCtx.Args()
|
||||
if !args.Present() || args.Len() < 2 {
|
||||
return errors.New("invalid number of arguments")
|
||||
}
|
||||
|
||||
storeID := args.First()
|
||||
key := args.Get(1)
|
||||
value := args.Get(2)
|
||||
if value == "" {
|
||||
fmt.Printf("Enter secret value: ")
|
||||
b, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println()
|
||||
value = string(b)
|
||||
}
|
||||
|
||||
store, err := m.GetSecretStore(storeID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get secret-store: %w", err)
|
||||
}
|
||||
if err := store.Set(key, value); err != nil {
|
||||
return fmt.Errorf("unable to set secret: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
203
cmd/telegraf/cmd_win_service.go
Normal file
203
cmd/telegraf/cmd_win_service.go
Normal file
|
@ -0,0 +1,203 @@
|
|||
//go:build windows
|
||||
|
||||
// Command handling for configuration "service" command
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func cliFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "service",
|
||||
Usage: "operate on the service (windows only)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "service-name",
|
||||
Value: "telegraf",
|
||||
Usage: "service name (windows only)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "service-display-name",
|
||||
Value: "Telegraf Data Collector Service",
|
||||
Usage: "service display name (windows only)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "service-restart-delay",
|
||||
Value: "5m",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "service-auto-restart",
|
||||
Usage: "auto restart service on failure (windows only)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "console",
|
||||
Usage: "run as console application (windows only)",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getServiceCommands(outputBuffer io.Writer) []*cli.Command {
|
||||
return []*cli.Command{
|
||||
{
|
||||
Name: "service",
|
||||
Usage: "commands for operate on the Windows service",
|
||||
Flags: nil,
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "install",
|
||||
Usage: "install Telegraf as a Windows service",
|
||||
Description: `
|
||||
The 'install' command with create a Windows service for automatically starting
|
||||
Telegraf with the specified configuration and service parameters. If no
|
||||
configuration(s) is specified the service will use the file in
|
||||
"C:\Program Files\Telegraf\telegraf.conf".
|
||||
|
||||
To install Telegraf as a service use
|
||||
|
||||
> telegraf service install
|
||||
|
||||
In case you are planning to start multiple Telegraf instances as a service,
|
||||
you must use distinctive service-names for each instance. To install two
|
||||
services with different configurations use
|
||||
|
||||
> telegraf --config "C:\Program Files\Telegraf\telegraf-machine.conf" --service-name telegraf-machine service install
|
||||
> telegraf --config "C:\Program Files\Telegraf\telegraf-service.conf" --service-name telegraf-service service install
|
||||
`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "display-name",
|
||||
Value: "Telegraf Data Collector Service",
|
||||
Usage: "service name as displayed in the service manager",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "restart-delay",
|
||||
Value: "5m",
|
||||
Usage: "duration for delaying the service restart on failure",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "auto-restart",
|
||||
Usage: "enable automatic service restart on failure",
|
||||
},
|
||||
},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
cfg := &serviceConfig{
|
||||
displayName: cCtx.String("display-name"),
|
||||
restartDelay: cCtx.String("restart-delay"),
|
||||
autoRestart: cCtx.Bool("auto-restart"),
|
||||
|
||||
configs: cCtx.StringSlice("config"),
|
||||
configDirs: cCtx.StringSlice("config-directory"),
|
||||
}
|
||||
name := cCtx.String("service-name")
|
||||
if err := installService(name, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(outputBuffer, "Successfully installed service %q\n", name)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "uninstall",
|
||||
Usage: "remove the Telegraf Windows service",
|
||||
Description: `
|
||||
The 'uninstall' command removes the Telegraf service with the given name. To
|
||||
remove a service use
|
||||
|
||||
> telegraf service uninstall
|
||||
|
||||
In case you specified a custom service-name during install use
|
||||
|
||||
> telegraf --service-name telegraf-machine service uninstall
|
||||
`,
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
name := cCtx.String("service-name")
|
||||
if err := uninstallService(name); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(outputBuffer, "Successfully uninstalled service %q\n", name)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "start",
|
||||
Usage: "start the Telegraf Windows service",
|
||||
Description: `
|
||||
The 'start' command triggers the start of the Windows service with the given
|
||||
name. To start the service either use the Windows service manager or run
|
||||
|
||||
> telegraf service start
|
||||
|
||||
In case you specified a custom service-name during install use
|
||||
|
||||
> telegraf --service-name telegraf-machine service start
|
||||
`,
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
name := cCtx.String("service-name")
|
||||
if err := startService(name); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(outputBuffer, "Successfully started service %q\n", name)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "stop",
|
||||
Usage: "stop the Telegraf Windows service",
|
||||
Description: `
|
||||
The 'stop' command triggers the stop of the Windows service with the given
|
||||
name and will wait until the service is actually stopped. To stop the service
|
||||
either use the Windows service manager or run
|
||||
|
||||
> telegraf service stop
|
||||
|
||||
In case you specified a custom service-name during install use
|
||||
|
||||
> telegraf --service-name telegraf-machine service stop
|
||||
`,
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
name := cCtx.String("service-name")
|
||||
if err := stopService(name); err != nil {
|
||||
if errors.Is(err, windows.ERROR_SERVICE_NOT_ACTIVE) {
|
||||
fmt.Fprintf(outputBuffer, "Service %q not started\n", name)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(outputBuffer, "Successfully stopped service %q\n", name)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "status",
|
||||
Usage: "query the Telegraf Windows service status",
|
||||
Description: `
|
||||
The 'status' command queries the current state of the Windows service with the
|
||||
given name. To query the service either check the Windows service manager or run
|
||||
|
||||
> telegraf service status
|
||||
|
||||
In case you specified a custom service-name during install use
|
||||
|
||||
> telegraf --service-name telegraf-machine service status
|
||||
`,
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
name := cCtx.String("service-name")
|
||||
status, err := queryService(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(outputBuffer, "Service %q is in %q state\n", name, status)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
17
cmd/telegraf/cmd_win_service_notwindows.go
Normal file
17
cmd/telegraf/cmd_win_service_notwindows.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
//go:build !windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func cliFlags() []cli.Flag {
|
||||
return make([]cli.Flag, 0)
|
||||
}
|
||||
|
||||
func getServiceCommands(io.Writer) []*cli.Command {
|
||||
return nil
|
||||
}
|
417
cmd/telegraf/main.go
Normal file
417
cmd/telegraf/main.go
Normal file
|
@ -0,0 +1,417 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/awnumar/memguard"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
"github.com/influxdata/telegraf/internal/goplugin"
|
||||
"github.com/influxdata/telegraf/logger"
|
||||
_ "github.com/influxdata/telegraf/plugins/aggregators/all"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
_ "github.com/influxdata/telegraf/plugins/inputs/all"
|
||||
"github.com/influxdata/telegraf/plugins/outputs"
|
||||
_ "github.com/influxdata/telegraf/plugins/outputs/all"
|
||||
_ "github.com/influxdata/telegraf/plugins/parsers/all"
|
||||
_ "github.com/influxdata/telegraf/plugins/processors/all"
|
||||
_ "github.com/influxdata/telegraf/plugins/secretstores/all"
|
||||
_ "github.com/influxdata/telegraf/plugins/serializers/all"
|
||||
)
|
||||
|
||||
type TelegrafConfig interface {
|
||||
CollectDeprecationInfos([]string, []string, []string, []string) map[string][]config.PluginDeprecationInfo
|
||||
PrintDeprecationList([]config.PluginDeprecationInfo)
|
||||
}
|
||||
|
||||
type Filters struct {
|
||||
section []string
|
||||
input []string
|
||||
output []string
|
||||
aggregator []string
|
||||
processor []string
|
||||
secretstore []string
|
||||
}
|
||||
|
||||
func appendFilter(a, b string) string {
|
||||
if a != "" && b != "" {
|
||||
return fmt.Sprintf("%s:%s", a, b)
|
||||
}
|
||||
if a != "" {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func processFilterFlags(ctx *cli.Context) Filters {
|
||||
var section, input, output, aggregator, processor, secretstore string
|
||||
|
||||
// Support defining filters before and after the command
|
||||
// The old style was:
|
||||
// ./telegraf --section-filter inputs --input-filter cpu config >test.conf
|
||||
// The new style is:
|
||||
// ./telegraf config --section-filter inputs --input-filter cpu >test.conf
|
||||
// To support the old style, check if the parent context has the filter flags defined
|
||||
if len(ctx.Lineage()) >= 2 {
|
||||
parent := ctx.Lineage()[1] // ancestor contexts in order from child to parent
|
||||
section = parent.String("section-filter")
|
||||
input = parent.String("input-filter")
|
||||
output = parent.String("output-filter")
|
||||
aggregator = parent.String("aggregator-filter")
|
||||
processor = parent.String("processor-filter")
|
||||
secretstore = parent.String("secretstore-filter")
|
||||
}
|
||||
|
||||
// If both the parent and command filters are defined, append them together
|
||||
section = appendFilter(section, ctx.String("section-filter"))
|
||||
input = appendFilter(input, ctx.String("input-filter"))
|
||||
output = appendFilter(output, ctx.String("output-filter"))
|
||||
aggregator = appendFilter(aggregator, ctx.String("aggregator-filter"))
|
||||
processor = appendFilter(processor, ctx.String("processor-filter"))
|
||||
secretstore = appendFilter(secretstore, ctx.String("secretstore-filter"))
|
||||
|
||||
sectionFilters := deleteEmpty(strings.Split(section, ":"))
|
||||
inputFilters := deleteEmpty(strings.Split(input, ":"))
|
||||
outputFilters := deleteEmpty(strings.Split(output, ":"))
|
||||
aggregatorFilters := deleteEmpty(strings.Split(aggregator, ":"))
|
||||
processorFilters := deleteEmpty(strings.Split(processor, ":"))
|
||||
secretstoreFilters := deleteEmpty(strings.Split(secretstore, ":"))
|
||||
return Filters{sectionFilters, inputFilters, outputFilters, aggregatorFilters, processorFilters, secretstoreFilters}
|
||||
}
|
||||
|
||||
func deleteEmpty(s []string) []string {
|
||||
var r []string
|
||||
for _, str := range s {
|
||||
if str != "" {
|
||||
r = append(r, str)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// runApp defines all the subcommands and flags for Telegraf
|
||||
// this abstraction is used for testing, so outputBuffer and args can be changed
|
||||
func runApp(args []string, outputBuffer io.Writer, pprof Server, c TelegrafConfig, m App) error {
|
||||
configHandlingFlags := []cli.Flag{
|
||||
&cli.StringSliceFlag{
|
||||
Name: "config",
|
||||
Usage: "configuration file to load",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "config-directory",
|
||||
Usage: "directory containing additional *.conf files",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "section-filter",
|
||||
Usage: "filter the sections to print, separator is ':'. " +
|
||||
"Valid values are 'agent', 'global_tags', 'outputs', 'processors', 'aggregators' and 'inputs'",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "input-filter",
|
||||
Usage: "filter the inputs to enable, separator is ':'",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "output-filter",
|
||||
Usage: "filter the outputs to enable, separator is ':'",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "aggregator-filter",
|
||||
Usage: "filter the aggregators to enable, separator is ':'",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "processor-filter",
|
||||
Usage: "filter the processors to enable, separator is ':'",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "secretstore-filter",
|
||||
Usage: "filter the secret-stores to enable, separator is ':'",
|
||||
},
|
||||
}
|
||||
|
||||
mainFlags := append(configHandlingFlags, cliFlags()...)
|
||||
|
||||
// This function is used when Telegraf is run with only flags
|
||||
action := func(cCtx *cli.Context) error {
|
||||
// We do not expect any arguments this is likely a misspelling of
|
||||
// a command...
|
||||
if cCtx.NArg() > 0 {
|
||||
return fmt.Errorf("unknown command %q", cCtx.Args().First())
|
||||
}
|
||||
|
||||
// Deprecated: Use execd instead
|
||||
// Load external plugins, if requested.
|
||||
if cCtx.String("plugin-directory") != "" {
|
||||
log.Printf("I! Loading external plugins from: %s", cCtx.String("plugin-directory"))
|
||||
if err := goplugin.LoadExternalPlugins(cCtx.String("plugin-directory")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// switch for flags which just do something and exit immediately
|
||||
switch {
|
||||
// print available input plugins
|
||||
case cCtx.Bool("deprecation-list"):
|
||||
filters := processFilterFlags(cCtx)
|
||||
infos := c.CollectDeprecationInfos(
|
||||
filters.input, filters.output, filters.aggregator, filters.processor,
|
||||
)
|
||||
outputBuffer.Write([]byte("Deprecated Input Plugins:\n"))
|
||||
c.PrintDeprecationList(infos["inputs"])
|
||||
outputBuffer.Write([]byte("Deprecated Output Plugins:\n"))
|
||||
c.PrintDeprecationList(infos["outputs"])
|
||||
outputBuffer.Write([]byte("Deprecated Processor Plugins:\n"))
|
||||
c.PrintDeprecationList(infos["processors"])
|
||||
outputBuffer.Write([]byte("Deprecated Aggregator Plugins:\n"))
|
||||
c.PrintDeprecationList(infos["aggregators"])
|
||||
return nil
|
||||
// print available output plugins
|
||||
case cCtx.Bool("output-list"):
|
||||
outputBuffer.Write([]byte("DEPRECATED: use telegraf plugins outputs\n"))
|
||||
outputBuffer.Write([]byte("Available Output Plugins:\n"))
|
||||
names := make([]string, 0, len(outputs.Outputs))
|
||||
for k := range outputs.Outputs {
|
||||
names = append(names, k)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, k := range names {
|
||||
fmt.Fprintf(outputBuffer, " %s\n", k)
|
||||
}
|
||||
return nil
|
||||
// print available input plugins
|
||||
case cCtx.Bool("input-list"):
|
||||
outputBuffer.Write([]byte("DEPRECATED: use telegraf plugins inputs\n"))
|
||||
outputBuffer.Write([]byte("Available Input Plugins:\n"))
|
||||
names := make([]string, 0, len(inputs.Inputs))
|
||||
for k := range inputs.Inputs {
|
||||
names = append(names, k)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, k := range names {
|
||||
fmt.Fprintf(outputBuffer, " %s\n", k)
|
||||
}
|
||||
return nil
|
||||
// print usage for a plugin, ie, 'telegraf --usage mysql'
|
||||
case cCtx.String("usage") != "":
|
||||
err := PrintInputConfig(cCtx.String("usage"), outputBuffer)
|
||||
err2 := PrintOutputConfig(cCtx.String("usage"), outputBuffer)
|
||||
if err != nil && err2 != nil {
|
||||
return fmt.Errorf("%w and %w", err, err2)
|
||||
}
|
||||
return nil
|
||||
// DEPRECATED
|
||||
case cCtx.Bool("version"):
|
||||
fmt.Fprintf(outputBuffer, "%s\n", internal.FormatFullVersion())
|
||||
return nil
|
||||
// DEPRECATED
|
||||
case cCtx.Bool("sample-config"):
|
||||
filters := processFilterFlags(cCtx)
|
||||
|
||||
printSampleConfig(outputBuffer, filters)
|
||||
return nil
|
||||
}
|
||||
|
||||
if cCtx.String("pprof-addr") != "" {
|
||||
pprof.Start(cCtx.String("pprof-addr"))
|
||||
}
|
||||
|
||||
filters := processFilterFlags(cCtx)
|
||||
|
||||
g := GlobalFlags{
|
||||
config: cCtx.StringSlice("config"),
|
||||
configDir: cCtx.StringSlice("config-directory"),
|
||||
testWait: cCtx.Int("test-wait"),
|
||||
configURLRetryAttempts: cCtx.Int("config-url-retry-attempts"),
|
||||
configURLWatchInterval: cCtx.Duration("config-url-watch-interval"),
|
||||
watchConfig: cCtx.String("watch-config"),
|
||||
watchInterval: cCtx.Duration("watch-interval"),
|
||||
pidFile: cCtx.String("pidfile"),
|
||||
plugindDir: cCtx.String("plugin-directory"),
|
||||
password: cCtx.String("password"),
|
||||
oldEnvBehavior: cCtx.Bool("old-env-behavior"),
|
||||
printPluginConfigSource: cCtx.Bool("print-plugin-config-source"),
|
||||
test: cCtx.Bool("test"),
|
||||
debug: cCtx.Bool("debug"),
|
||||
once: cCtx.Bool("once"),
|
||||
quiet: cCtx.Bool("quiet"),
|
||||
unprotected: cCtx.Bool("unprotected"),
|
||||
}
|
||||
|
||||
w := WindowFlags{
|
||||
service: cCtx.String("service"),
|
||||
serviceName: cCtx.String("service-name"),
|
||||
serviceDisplayName: cCtx.String("service-display-name"),
|
||||
serviceRestartDelay: cCtx.String("service-restart-delay"),
|
||||
serviceAutoRestart: cCtx.Bool("service-auto-restart"),
|
||||
console: cCtx.Bool("console"),
|
||||
}
|
||||
|
||||
m.Init(pprof.ErrChan(), filters, g, w)
|
||||
return m.Run()
|
||||
}
|
||||
|
||||
commands := append(
|
||||
getConfigCommands(configHandlingFlags, outputBuffer),
|
||||
getSecretStoreCommands(m)...,
|
||||
)
|
||||
commands = append(commands, getPluginCommands(outputBuffer)...)
|
||||
commands = append(commands, getServiceCommands(outputBuffer)...)
|
||||
|
||||
app := &cli.App{
|
||||
Name: "Telegraf",
|
||||
Usage: "The plugin-driven server agent for collecting & reporting metrics.",
|
||||
Writer: outputBuffer,
|
||||
Flags: append(
|
||||
[]cli.Flag{
|
||||
// Int flags
|
||||
&cli.IntFlag{
|
||||
Name: "test-wait",
|
||||
Usage: "wait up to this many seconds for service inputs to complete in test mode",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "config-url-retry-attempts",
|
||||
Usage: "Number of attempts to obtain a remote configuration via a URL during startup. " +
|
||||
"Set to -1 for unlimited attempts.",
|
||||
DefaultText: "3",
|
||||
},
|
||||
//
|
||||
// String flags
|
||||
&cli.StringFlag{
|
||||
Name: "usage",
|
||||
Usage: "print usage for a plugin, ie, 'telegraf --usage mysql'",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "pprof-addr",
|
||||
Usage: "pprof host/IP and port to listen on (e.g. 'localhost:6060')",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "watch-config",
|
||||
Usage: "monitoring config changes [notify, poll] of --config and --config-directory options. " +
|
||||
"Notify supports linux, *bsd, and macOS. Poll is required for Windows and checks every 250ms.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "pidfile",
|
||||
Usage: "file to write our pid to",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "password",
|
||||
Usage: "password to unlock secret-stores",
|
||||
},
|
||||
//
|
||||
// Bool flags
|
||||
&cli.BoolFlag{
|
||||
Name: "old-env-behavior",
|
||||
Usage: "switch back to pre v1.27 environment replacement behavior",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "print-plugin-config-source",
|
||||
Usage: "print the source for a given plugin",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "once",
|
||||
Usage: "run one gather and exit",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Usage: "turn on debug logging",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "quiet",
|
||||
Usage: "run in quiet mode",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "unprotected",
|
||||
Usage: "do not protect secrets in memory",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "test",
|
||||
Usage: "enable test mode: gather metrics, print them out, and exit. " +
|
||||
"Note: Test mode only runs inputs, not processors, aggregators, or outputs",
|
||||
},
|
||||
//
|
||||
// Duration flags
|
||||
&cli.DurationFlag{
|
||||
Name: "watch-interval",
|
||||
Usage: "Time duration to check for updates to config files specified by --config and " +
|
||||
"--config-directory options. Use with '--watch-config poll'",
|
||||
DefaultText: "disabled",
|
||||
},
|
||||
&cli.DurationFlag{
|
||||
Name: "config-url-watch-interval",
|
||||
Usage: "Time duration to check for updates to URL based configuration files",
|
||||
DefaultText: "disabled",
|
||||
},
|
||||
// TODO: Change "deprecation-list, input-list, output-list" flags to become a subcommand "list" that takes
|
||||
// "input,output,aggregator,processor, deprecated" as parameters
|
||||
&cli.BoolFlag{
|
||||
Name: "deprecation-list",
|
||||
Usage: "print all deprecated plugins or plugin options",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "input-list",
|
||||
Usage: "print available input plugins",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "output-list",
|
||||
Usage: "print available output plugins",
|
||||
},
|
||||
//
|
||||
// !!! The following flags are DEPRECATED !!!
|
||||
// Already covered with the subcommand `./telegraf version`
|
||||
&cli.BoolFlag{
|
||||
Name: "version",
|
||||
Usage: "DEPRECATED: display the version and exit",
|
||||
},
|
||||
// Already covered with the subcommand `./telegraf config`
|
||||
&cli.BoolFlag{
|
||||
Name: "sample-config",
|
||||
Usage: "DEPRECATED: print out full sample configuration",
|
||||
},
|
||||
// Using execd plugin to add external plugins is preferred (less size impact, easier for end user)
|
||||
&cli.StringFlag{
|
||||
Name: "plugin-directory",
|
||||
Usage: "DEPRECATED: path to directory containing external plugins",
|
||||
},
|
||||
// !!!
|
||||
}, mainFlags...),
|
||||
Action: action,
|
||||
Commands: append([]*cli.Command{
|
||||
{
|
||||
Name: "version",
|
||||
Usage: "print current version to stdout",
|
||||
Action: func(*cli.Context) error {
|
||||
fmt.Fprintf(outputBuffer, "%s\n", internal.FormatFullVersion())
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}, commands...),
|
||||
}
|
||||
|
||||
// Make sure we safely erase secrets
|
||||
defer memguard.Purge()
|
||||
defer logger.CloseLogging()
|
||||
|
||||
if err := app.Run(args); err != nil {
|
||||
log.Printf("E! %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// #13481: disables gh:99designs/keyring kwallet.go from connecting to dbus
|
||||
os.Setenv("DISABLE_KWALLET", "1")
|
||||
|
||||
agent := Telegraf{}
|
||||
pprof := NewPprofServer()
|
||||
c := config.NewConfig()
|
||||
if err := runApp(os.Args, os.Stdout, pprof, c, &agent); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
574
cmd/telegraf/main_test.go
Normal file
574
cmd/telegraf/main_test.go
Normal file
|
@ -0,0 +1,574 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
"github.com/influxdata/telegraf/plugins/outputs"
|
||||
)
|
||||
|
||||
var secrets = map[string]map[string][]byte{
|
||||
"yoda": {
|
||||
"episode1": []byte("member"),
|
||||
"episode2": []byte("member"),
|
||||
"episode3": []byte("member"),
|
||||
},
|
||||
"mace_windu": {
|
||||
"episode1": []byte("member"),
|
||||
"episode2": []byte("member"),
|
||||
"episode3": []byte("member"),
|
||||
},
|
||||
"oppo_rancisis": {
|
||||
"episode1": []byte("member"),
|
||||
"episode2": []byte("member"),
|
||||
},
|
||||
"coleman_kcaj": {
|
||||
"episode3": []byte("member"),
|
||||
},
|
||||
}
|
||||
|
||||
type MockTelegraf struct {
|
||||
GlobalFlags
|
||||
WindowFlags
|
||||
}
|
||||
|
||||
func NewMockTelegraf() *MockTelegraf {
|
||||
return &MockTelegraf{}
|
||||
}
|
||||
|
||||
func (m *MockTelegraf) Init(_ <-chan error, _ Filters, g GlobalFlags, w WindowFlags) {
|
||||
m.GlobalFlags = g
|
||||
m.WindowFlags = w
|
||||
}
|
||||
|
||||
func (*MockTelegraf) Run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*MockTelegraf) ListSecretStores() ([]string, error) {
|
||||
ids := make([]string, 0, len(secrets))
|
||||
for k := range secrets {
|
||||
ids = append(ids, k)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (*MockTelegraf) GetSecretStore(id string) (telegraf.SecretStore, error) {
|
||||
v, found := secrets[id]
|
||||
if !found {
|
||||
return nil, errors.New("unknown secret store")
|
||||
}
|
||||
s := &MockSecretStore{Secrets: v}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
type MockSecretStore struct {
|
||||
Secrets map[string][]byte
|
||||
}
|
||||
|
||||
func (*MockSecretStore) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*MockSecretStore) SampleConfig() string {
|
||||
return "I'm just a dummy"
|
||||
}
|
||||
|
||||
func (s *MockSecretStore) Get(key string) ([]byte, error) {
|
||||
v, found := s.Secrets[key]
|
||||
if !found {
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (s *MockSecretStore) Set(key, value string) error {
|
||||
if strings.HasPrefix(key, "darth") {
|
||||
return errors.New("don't join the dark side")
|
||||
}
|
||||
s.Secrets[key] = []byte(value)
|
||||
return nil
|
||||
}
|
||||
func (s *MockSecretStore) List() ([]string, error) {
|
||||
keys := make([]string, 0, len(s.Secrets))
|
||||
for k := range s.Secrets {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func (s *MockSecretStore) GetResolver(key string) (telegraf.ResolveFunc, error) {
|
||||
return func() ([]byte, bool, error) {
|
||||
v, err := s.Get(key)
|
||||
return v, false, err
|
||||
}, nil
|
||||
}
|
||||
|
||||
type MockConfig struct {
|
||||
Buffer io.Writer
|
||||
ExpectedDeprecatedPlugins map[string][]config.PluginDeprecationInfo
|
||||
}
|
||||
|
||||
func NewMockConfig(buffer io.Writer) *MockConfig {
|
||||
return &MockConfig{
|
||||
Buffer: buffer,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockConfig) CollectDeprecationInfos(_, _, _, _ []string) map[string][]config.PluginDeprecationInfo {
|
||||
return m.ExpectedDeprecatedPlugins
|
||||
}
|
||||
|
||||
func (m *MockConfig) PrintDeprecationList(plugins []config.PluginDeprecationInfo) {
|
||||
for _, p := range plugins {
|
||||
fmt.Fprintf(m.Buffer, "plugin name: %s\n", p.Name)
|
||||
}
|
||||
}
|
||||
|
||||
type MockServer struct {
|
||||
Address string
|
||||
}
|
||||
|
||||
func NewMockServer() *MockServer {
|
||||
return &MockServer{}
|
||||
}
|
||||
|
||||
func (m *MockServer) Start(_ string) {
|
||||
m.Address = "localhost:6060"
|
||||
}
|
||||
|
||||
func (*MockServer) ErrChan() <-chan error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestUsageFlag(t *testing.T) {
|
||||
tests := []struct {
|
||||
PluginName string
|
||||
ExpectedError string
|
||||
ExpectedOutput string
|
||||
}{
|
||||
{
|
||||
PluginName: "example",
|
||||
ExpectedError: "input example not found and output example not found",
|
||||
},
|
||||
{
|
||||
PluginName: "temp",
|
||||
ExpectedOutput: `
|
||||
# Read metrics about temperature
|
||||
[[inputs.temp]]
|
||||
## Desired output format (Linux only)
|
||||
## Available values are
|
||||
## v1 -- use pre-v1.22.4 sensor naming, e.g. coretemp_core0_input
|
||||
## v2 -- use v1.22.4+ sensor naming, e.g. coretemp_core_0_input
|
||||
# metric_format = "v2"
|
||||
|
||||
## Add device tag to distinguish devices with the same name (Linux only)
|
||||
# add_device_tag = false
|
||||
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
buf := new(bytes.Buffer)
|
||||
args := os.Args[0:1]
|
||||
args = append(args, "--usage", test.PluginName)
|
||||
err := runApp(args, buf, NewMockServer(), NewMockConfig(buf), NewMockTelegraf())
|
||||
if test.ExpectedError != "" {
|
||||
require.ErrorContains(t, err, test.ExpectedError)
|
||||
continue
|
||||
}
|
||||
require.NoError(t, err)
|
||||
// To run this test on windows and linux, remove windows carriage return
|
||||
o := strings.Replace(buf.String(), "\r", "", -1)
|
||||
require.Equal(t, test.ExpectedOutput, o)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputListFlag(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
args := os.Args[0:1]
|
||||
args = append(args, "--input-list")
|
||||
temp := inputs.Inputs
|
||||
inputs.Inputs = map[string]inputs.Creator{
|
||||
"test": func() telegraf.Input { return nil },
|
||||
}
|
||||
err := runApp(args, buf, NewMockServer(), NewMockConfig(buf), NewMockTelegraf())
|
||||
require.NoError(t, err)
|
||||
expectedOutput := `DEPRECATED: use telegraf plugins inputs
|
||||
Available Input Plugins:
|
||||
test
|
||||
`
|
||||
require.Equal(t, expectedOutput, buf.String())
|
||||
inputs.Inputs = temp
|
||||
}
|
||||
|
||||
func TestOutputListFlag(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
args := os.Args[0:1]
|
||||
args = append(args, "--output-list")
|
||||
temp := outputs.Outputs
|
||||
outputs.Outputs = map[string]outputs.Creator{
|
||||
"test": func() telegraf.Output { return nil },
|
||||
}
|
||||
err := runApp(args, buf, NewMockServer(), NewMockConfig(buf), NewMockTelegraf())
|
||||
require.NoError(t, err)
|
||||
expectedOutput := `DEPRECATED: use telegraf plugins outputs
|
||||
Available Output Plugins:
|
||||
test
|
||||
`
|
||||
require.Equal(t, expectedOutput, buf.String())
|
||||
outputs.Outputs = temp
|
||||
}
|
||||
|
||||
func TestDeprecationListFlag(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
args := os.Args[0:1]
|
||||
args = append(args, "--deprecation-list")
|
||||
mS := NewMockServer()
|
||||
mC := NewMockConfig(buf)
|
||||
mC.ExpectedDeprecatedPlugins = make(map[string][]config.PluginDeprecationInfo)
|
||||
mC.ExpectedDeprecatedPlugins["inputs"] = []config.PluginDeprecationInfo{
|
||||
{
|
||||
DeprecationInfo: config.DeprecationInfo{
|
||||
Name: "test",
|
||||
},
|
||||
},
|
||||
}
|
||||
err := runApp(args, buf, mS, mC, NewMockTelegraf())
|
||||
require.NoError(t, err)
|
||||
expectedOutput := `Deprecated Input Plugins:
|
||||
plugin name: test
|
||||
Deprecated Output Plugins:
|
||||
Deprecated Processor Plugins:
|
||||
Deprecated Aggregator Plugins:
|
||||
`
|
||||
|
||||
require.Equal(t, expectedOutput, buf.String())
|
||||
}
|
||||
|
||||
func TestPprofAddressFlag(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
args := os.Args[0:1]
|
||||
address := "localhost:6060"
|
||||
args = append(args, "--pprof-addr", address)
|
||||
m := NewMockServer()
|
||||
err := runApp(args, buf, m, NewMockConfig(buf), NewMockTelegraf())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, address, m.Address)
|
||||
}
|
||||
|
||||
// !!! DEPRECATED !!!
|
||||
// TestPluginDirectoryFlag tests `--plugin-directory`
|
||||
func TestPluginDirectoryFlag(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
args := os.Args[0:1]
|
||||
args = append(args, "--plugin-directory", ".")
|
||||
err := runApp(args, buf, NewMockServer(), NewMockConfig(buf), NewMockTelegraf())
|
||||
require.ErrorContains(t, err, "go plugin support is not enabled")
|
||||
}
|
||||
|
||||
func TestCommandConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
commands []string
|
||||
expectedHeaders []string
|
||||
removedHeaders []string
|
||||
expectedPlugins []string
|
||||
removedPlugins []string
|
||||
}{
|
||||
{
|
||||
name: "deprecated flag --sample-config",
|
||||
commands: []string{"--sample-config"},
|
||||
expectedHeaders: []string{
|
||||
outputHeader,
|
||||
inputHeader,
|
||||
aggregatorHeader,
|
||||
processorHeader,
|
||||
serviceInputHeader,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no filters",
|
||||
commands: []string{"config"},
|
||||
expectedHeaders: []string{
|
||||
outputHeader,
|
||||
inputHeader,
|
||||
aggregatorHeader,
|
||||
processorHeader,
|
||||
serviceInputHeader,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter sections for inputs",
|
||||
commands: []string{"config", "--section-filter", "inputs"},
|
||||
expectedHeaders: []string{
|
||||
inputHeader,
|
||||
},
|
||||
removedHeaders: []string{
|
||||
outputHeader,
|
||||
aggregatorHeader,
|
||||
processorHeader,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter sections for inputs,outputs",
|
||||
commands: []string{"config", "--section-filter", "inputs:outputs"},
|
||||
expectedHeaders: []string{
|
||||
inputHeader,
|
||||
outputHeader,
|
||||
},
|
||||
removedHeaders: []string{
|
||||
aggregatorHeader,
|
||||
processorHeader,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter input plugins",
|
||||
commands: []string{"config", "--input-filter", "cpu:file"},
|
||||
expectedPlugins: []string{
|
||||
"[[inputs.cpu]]",
|
||||
"[[inputs.file]]",
|
||||
},
|
||||
removedPlugins: []string{
|
||||
"[[inputs.disk]]",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter output plugins",
|
||||
commands: []string{"config", "--output-filter", "influxdb:http"},
|
||||
expectedPlugins: []string{
|
||||
"[[outputs.influxdb]]",
|
||||
"[[outputs.http]]",
|
||||
},
|
||||
removedPlugins: []string{
|
||||
"[[outputs.file]]",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter processor plugins",
|
||||
commands: []string{"config", "--processor-filter", "date:enum"},
|
||||
expectedPlugins: []string{
|
||||
"[[processors.date]]",
|
||||
"[[processors.enum]]",
|
||||
},
|
||||
removedPlugins: []string{
|
||||
"[[processors.parser]]",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter aggregator plugins",
|
||||
commands: []string{"config", "--aggregator-filter", "basicstats:starlark"},
|
||||
expectedPlugins: []string{
|
||||
"[[aggregators.basicstats]]",
|
||||
"[[aggregators.starlark]]",
|
||||
},
|
||||
removedPlugins: []string{
|
||||
"[[aggregators.minmax]]",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test filters before config",
|
||||
commands: []string{"--input-filter", "cpu:file", "config"},
|
||||
expectedPlugins: []string{
|
||||
"[[inputs.cpu]]",
|
||||
"[[inputs.file]]",
|
||||
},
|
||||
removedPlugins: []string{
|
||||
"[[inputs.disk]]",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test filters before and after config",
|
||||
commands: []string{"--input-filter", "file", "config", "--input-filter", "cpu"},
|
||||
expectedPlugins: []string{
|
||||
"[[inputs.cpu]]",
|
||||
"[[inputs.file]]",
|
||||
},
|
||||
removedPlugins: []string{
|
||||
"[[inputs.disk]]",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
args := os.Args[0:1]
|
||||
args = append(args, test.commands...)
|
||||
err := runApp(args, buf, NewMockServer(), NewMockConfig(buf), NewMockTelegraf())
|
||||
require.NoError(t, err)
|
||||
output := buf.String()
|
||||
for _, e := range test.expectedHeaders {
|
||||
require.Contains(t, output, e, "expected header not found")
|
||||
}
|
||||
for _, r := range test.removedHeaders {
|
||||
require.NotContains(t, output, r, "removed header found")
|
||||
}
|
||||
for _, e := range test.expectedPlugins {
|
||||
require.Contains(t, output, e, "expected plugin not found")
|
||||
}
|
||||
for _, r := range test.removedPlugins {
|
||||
require.NotContains(t, output, r, "removed plugin found")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
Version string
|
||||
Branch string
|
||||
Commit string
|
||||
ExpectedOutput string
|
||||
}{
|
||||
{
|
||||
Version: "v2.0.0",
|
||||
ExpectedOutput: "Telegraf v2.0.0\n",
|
||||
},
|
||||
{
|
||||
ExpectedOutput: "Telegraf unknown\n",
|
||||
},
|
||||
{
|
||||
Version: "v2.0.0",
|
||||
Branch: "master",
|
||||
ExpectedOutput: "Telegraf v2.0.0 (git: master@unknown)\n",
|
||||
},
|
||||
{
|
||||
Version: "v2.0.0",
|
||||
Branch: "master",
|
||||
Commit: "123",
|
||||
ExpectedOutput: "Telegraf v2.0.0 (git: master@123)\n",
|
||||
},
|
||||
{
|
||||
Version: "v2.0.0",
|
||||
Commit: "123",
|
||||
ExpectedOutput: "Telegraf v2.0.0 (git: unknown@123)\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
buf := new(bytes.Buffer)
|
||||
args := os.Args[0:1]
|
||||
args = append(args, "version")
|
||||
internal.Version = test.Version
|
||||
internal.Branch = test.Branch
|
||||
internal.Commit = test.Commit
|
||||
err := runApp(args, buf, NewMockServer(), NewMockConfig(buf), NewMockTelegraf())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.ExpectedOutput, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Users should use the version subcommand
|
||||
func TestFlagVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
Version string
|
||||
Branch string
|
||||
Commit string
|
||||
ExpectedOutput string
|
||||
}{
|
||||
{
|
||||
Version: "v2.0.0",
|
||||
ExpectedOutput: "Telegraf v2.0.0\n",
|
||||
},
|
||||
{
|
||||
ExpectedOutput: "Telegraf unknown\n",
|
||||
},
|
||||
{
|
||||
Version: "v2.0.0",
|
||||
Branch: "master",
|
||||
ExpectedOutput: "Telegraf v2.0.0 (git: master@unknown)\n",
|
||||
},
|
||||
{
|
||||
Version: "v2.0.0",
|
||||
Branch: "master",
|
||||
Commit: "123",
|
||||
ExpectedOutput: "Telegraf v2.0.0 (git: master@123)\n",
|
||||
},
|
||||
{
|
||||
Version: "v2.0.0",
|
||||
Commit: "123",
|
||||
ExpectedOutput: "Telegraf v2.0.0 (git: unknown@123)\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
buf := new(bytes.Buffer)
|
||||
args := os.Args[0:1]
|
||||
args = append(args, "--version")
|
||||
internal.Version = test.Version
|
||||
internal.Branch = test.Branch
|
||||
internal.Commit = test.Commit
|
||||
err := runApp(args, buf, NewMockServer(), NewMockConfig(buf), NewMockTelegraf())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.ExpectedOutput, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGlobablBoolFlags(t *testing.T) {
|
||||
commands := []string{
|
||||
"--debug",
|
||||
"--test",
|
||||
"--quiet",
|
||||
"--once",
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
args := os.Args[0:1]
|
||||
args = append(args, commands...)
|
||||
m := NewMockTelegraf()
|
||||
err := runApp(args, buf, NewMockServer(), NewMockConfig(buf), m)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, m.debug)
|
||||
require.True(t, m.test)
|
||||
require.True(t, m.once)
|
||||
require.True(t, m.quiet)
|
||||
}
|
||||
|
||||
func TestFlagsAreSet(t *testing.T) {
|
||||
expectedInt := 1
|
||||
expectedString := "test"
|
||||
|
||||
commands := []string{
|
||||
"--config", expectedString,
|
||||
"--config-directory", expectedString,
|
||||
"--debug",
|
||||
"--test",
|
||||
"--quiet",
|
||||
"--once",
|
||||
"--test-wait", strconv.Itoa(expectedInt),
|
||||
"--watch-config", expectedString,
|
||||
"--pidfile", expectedString,
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
args := os.Args[0:1]
|
||||
args = append(args, commands...)
|
||||
m := NewMockTelegraf()
|
||||
err := runApp(args, buf, NewMockServer(), NewMockConfig(buf), m)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, []string{expectedString}, m.config)
|
||||
require.Equal(t, []string{expectedString}, m.configDir)
|
||||
require.True(t, m.debug)
|
||||
require.True(t, m.test)
|
||||
require.True(t, m.once)
|
||||
require.True(t, m.quiet)
|
||||
require.Equal(t, expectedInt, m.testWait)
|
||||
require.Equal(t, expectedString, m.watchConfig)
|
||||
require.Equal(t, expectedString, m.pidFile)
|
||||
}
|
38
cmd/telegraf/main_win_test.go
Normal file
38
cmd/telegraf/main_win_test.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWindowsFlagsAreSet(t *testing.T) {
|
||||
expectedString := "test"
|
||||
|
||||
commands := []string{
|
||||
"--service", expectedString,
|
||||
"--service-name", expectedString,
|
||||
"--service-display-name", expectedString,
|
||||
"--service-restart-delay", expectedString,
|
||||
"--service-auto-restart",
|
||||
"--console",
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
args := os.Args[0:1]
|
||||
args = append(args, commands...)
|
||||
m := NewMockTelegraf()
|
||||
err := runApp(args, buf, NewMockServer(), NewMockConfig(buf), m)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, expectedString, m.service)
|
||||
require.Equal(t, expectedString, m.serviceName)
|
||||
require.Equal(t, expectedString, m.serviceDisplayName)
|
||||
require.Equal(t, expectedString, m.serviceRestartDelay)
|
||||
require.True(t, m.serviceAutoRestart)
|
||||
require.True(t, m.console)
|
||||
}
|
52
cmd/telegraf/pprof.go
Normal file
52
cmd/telegraf/pprof.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
_ "net/http/pprof" //nolint:gosec // Import for pprof, only enabled via CLI flag
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Server interface {
|
||||
Start(string)
|
||||
ErrChan() <-chan error
|
||||
}
|
||||
|
||||
type PprofServer struct {
|
||||
err chan error
|
||||
}
|
||||
|
||||
func NewPprofServer() *PprofServer {
|
||||
return &PprofServer{
|
||||
err: make(chan error),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PprofServer) Start(address string) {
|
||||
go func() {
|
||||
pprofHostPort := address
|
||||
parts := strings.Split(pprofHostPort, ":")
|
||||
if len(parts) == 2 && parts[0] == "" {
|
||||
pprofHostPort = "localhost:" + parts[1]
|
||||
}
|
||||
pprofHostPort = "http://" + pprofHostPort + "/debug/pprof"
|
||||
|
||||
log.Printf("I! Starting pprof HTTP server at: %s", pprofHostPort)
|
||||
|
||||
server := &http.Server{
|
||||
Addr: address,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
if err := server.ListenAndServe(); err != nil {
|
||||
p.err <- err
|
||||
}
|
||||
close(p.err)
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *PprofServer) ErrChan() <-chan error {
|
||||
return p.err
|
||||
}
|
408
cmd/telegraf/printer.go
Normal file
408
cmd/telegraf/printer.go
Normal file
|
@ -0,0 +1,408 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/internal/choice"
|
||||
"github.com/influxdata/telegraf/plugins/aggregators"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
"github.com/influxdata/telegraf/plugins/outputs"
|
||||
"github.com/influxdata/telegraf/plugins/processors"
|
||||
"github.com/influxdata/telegraf/plugins/secretstores"
|
||||
)
|
||||
|
||||
var (
|
||||
// Default sections
|
||||
sectionDefaults = []string{"global_tags", "agent", "secretstores", "outputs", "processors", "aggregators", "inputs"}
|
||||
|
||||
// Default input plugins
|
||||
inputDefaults = []string{"cpu", "mem", "swap", "system", "kernel", "processes", "disk", "diskio"}
|
||||
|
||||
// Default output plugins
|
||||
outputDefaults = make([]string, 0)
|
||||
)
|
||||
|
||||
var header = `# Telegraf Configuration
|
||||
#
|
||||
# Telegraf is entirely plugin driven. All metrics are gathered from the
|
||||
# declared inputs, and sent to the declared outputs.
|
||||
#
|
||||
# Plugins must be declared in here to be active.
|
||||
# To deactivate a plugin, comment out the name and any variables.
|
||||
#
|
||||
# Use 'telegraf -config telegraf.conf -test' to see what metrics a config
|
||||
# file would generate.
|
||||
#
|
||||
# Environment variables can be used anywhere in this config file, simply surround
|
||||
# them with ${}. For strings the variable must be within quotes (ie, "${STR_VAR}"),
|
||||
# for numbers and booleans they should be plain (ie, ${INT_VAR}, ${BOOL_VAR})
|
||||
|
||||
`
|
||||
var globalTagsConfig = `
|
||||
# Global tags can be specified here in key="value" format.
|
||||
[global_tags]
|
||||
# dc = "us-east-1" # will tag all metrics with dc=us-east-1
|
||||
# rack = "1a"
|
||||
## Environment variables can be used as tags, and throughout the config file
|
||||
# user = "$USER"
|
||||
|
||||
`
|
||||
|
||||
// DO NOT REMOVE THE NEXT TWO LINES! This is required to embed the agentConfig data.
|
||||
//
|
||||
//go:embed agent.conf
|
||||
var agentConfig string
|
||||
|
||||
var secretstoreHeader = `
|
||||
###############################################################################
|
||||
# SECRETSTORE PLUGINS #
|
||||
###############################################################################
|
||||
|
||||
`
|
||||
|
||||
var outputHeader = `
|
||||
###############################################################################
|
||||
# OUTPUT PLUGINS #
|
||||
###############################################################################
|
||||
|
||||
`
|
||||
|
||||
var processorHeader = `
|
||||
###############################################################################
|
||||
# PROCESSOR PLUGINS #
|
||||
###############################################################################
|
||||
|
||||
`
|
||||
|
||||
var aggregatorHeader = `
|
||||
###############################################################################
|
||||
# AGGREGATOR PLUGINS #
|
||||
###############################################################################
|
||||
|
||||
`
|
||||
|
||||
var inputHeader = `
|
||||
###############################################################################
|
||||
# INPUT PLUGINS #
|
||||
###############################################################################
|
||||
|
||||
`
|
||||
|
||||
var serviceInputHeader = `
|
||||
###############################################################################
|
||||
# SERVICE INPUT PLUGINS #
|
||||
###############################################################################
|
||||
|
||||
`
|
||||
|
||||
// printSampleConfig prints the sample config
|
||||
func printSampleConfig(outputBuffer io.Writer, filters Filters) {
|
||||
sectionFilters := filters.section
|
||||
inputFilters := filters.input
|
||||
outputFilters := filters.output
|
||||
aggregatorFilters := filters.aggregator
|
||||
processorFilters := filters.processor
|
||||
secretstoreFilters := filters.secretstore
|
||||
|
||||
// print headers
|
||||
outputBuffer.Write([]byte(header))
|
||||
|
||||
if len(sectionFilters) == 0 {
|
||||
sectionFilters = sectionDefaults
|
||||
}
|
||||
printFilteredGlobalSections(sectionFilters, outputBuffer)
|
||||
|
||||
// print secretstore plugins
|
||||
if choice.Contains("secretstores", sectionFilters) {
|
||||
if len(secretstoreFilters) != 0 {
|
||||
if len(secretstoreFilters) >= 3 && secretstoreFilters[1] != "none" {
|
||||
fmt.Print(secretstoreHeader)
|
||||
}
|
||||
printFilteredSecretstores(secretstoreFilters, false, outputBuffer)
|
||||
} else {
|
||||
fmt.Print(secretstoreHeader)
|
||||
snames := make([]string, 0, len(secretstores.SecretStores))
|
||||
for sname := range secretstores.SecretStores {
|
||||
snames = append(snames, sname)
|
||||
}
|
||||
sort.Strings(snames)
|
||||
printFilteredSecretstores(snames, true, outputBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
// print output plugins
|
||||
if choice.Contains("outputs", sectionFilters) {
|
||||
if len(outputFilters) != 0 {
|
||||
if len(outputFilters) >= 3 && outputFilters[1] != "none" {
|
||||
outputBuffer.Write([]byte(outputHeader))
|
||||
}
|
||||
printFilteredOutputs(outputFilters, false, outputBuffer)
|
||||
} else {
|
||||
outputBuffer.Write([]byte(outputHeader))
|
||||
printFilteredOutputs(outputDefaults, false, outputBuffer)
|
||||
// Print non-default outputs, commented
|
||||
var pnames []string
|
||||
for pname := range outputs.Outputs {
|
||||
if !choice.Contains(pname, outputDefaults) {
|
||||
pnames = append(pnames, pname)
|
||||
}
|
||||
}
|
||||
printFilteredOutputs(pnames, true, outputBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
// print processor plugins
|
||||
if choice.Contains("processors", sectionFilters) {
|
||||
if len(processorFilters) != 0 {
|
||||
if len(processorFilters) >= 3 && processorFilters[1] != "none" {
|
||||
outputBuffer.Write([]byte(processorHeader))
|
||||
}
|
||||
printFilteredProcessors(processorFilters, false, outputBuffer)
|
||||
} else {
|
||||
outputBuffer.Write([]byte(processorHeader))
|
||||
pnames := make([]string, 0, len(processors.Processors))
|
||||
for pname := range processors.Processors {
|
||||
pnames = append(pnames, pname)
|
||||
}
|
||||
printFilteredProcessors(pnames, true, outputBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
// print aggregator plugins
|
||||
if choice.Contains("aggregators", sectionFilters) {
|
||||
if len(aggregatorFilters) != 0 {
|
||||
if len(aggregatorFilters) >= 3 && aggregatorFilters[1] != "none" {
|
||||
outputBuffer.Write([]byte(aggregatorHeader))
|
||||
}
|
||||
printFilteredAggregators(aggregatorFilters, false, outputBuffer)
|
||||
} else {
|
||||
outputBuffer.Write([]byte(aggregatorHeader))
|
||||
pnames := make([]string, 0, len(aggregators.Aggregators))
|
||||
for pname := range aggregators.Aggregators {
|
||||
pnames = append(pnames, pname)
|
||||
}
|
||||
printFilteredAggregators(pnames, true, outputBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
// print input plugins
|
||||
if choice.Contains("inputs", sectionFilters) {
|
||||
if len(inputFilters) != 0 {
|
||||
if len(inputFilters) >= 3 && inputFilters[1] != "none" {
|
||||
outputBuffer.Write([]byte(inputHeader))
|
||||
}
|
||||
printFilteredInputs(inputFilters, false, outputBuffer)
|
||||
} else {
|
||||
outputBuffer.Write([]byte(inputHeader))
|
||||
printFilteredInputs(inputDefaults, false, outputBuffer)
|
||||
// Print non-default inputs, commented
|
||||
var pnames []string
|
||||
for pname := range inputs.Inputs {
|
||||
if !choice.Contains(pname, inputDefaults) {
|
||||
pnames = append(pnames, pname)
|
||||
}
|
||||
}
|
||||
printFilteredInputs(pnames, true, outputBuffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printFilteredProcessors(processorFilters []string, commented bool, outputBuffer io.Writer) {
|
||||
// Filter processors
|
||||
var pnames []string
|
||||
for pname := range processors.Processors {
|
||||
if choice.Contains(pname, processorFilters) {
|
||||
pnames = append(pnames, pname)
|
||||
}
|
||||
}
|
||||
sort.Strings(pnames)
|
||||
|
||||
// Print Outputs
|
||||
for _, pname := range pnames {
|
||||
creator := processors.Processors[pname]
|
||||
output := creator()
|
||||
printConfig(pname, output, "processors", commented, processors.Deprecations[pname], outputBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
func printFilteredAggregators(aggregatorFilters []string, commented bool, outputBuffer io.Writer) {
|
||||
// Filter outputs
|
||||
var anames []string
|
||||
for aname := range aggregators.Aggregators {
|
||||
if choice.Contains(aname, aggregatorFilters) {
|
||||
anames = append(anames, aname)
|
||||
}
|
||||
}
|
||||
sort.Strings(anames)
|
||||
|
||||
// Print Outputs
|
||||
for _, aname := range anames {
|
||||
creator := aggregators.Aggregators[aname]
|
||||
output := creator()
|
||||
printConfig(aname, output, "aggregators", commented, aggregators.Deprecations[aname], outputBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
func printFilteredInputs(inputFilters []string, commented bool, outputBuffer io.Writer) {
|
||||
// Filter inputs
|
||||
var pnames []string
|
||||
for pname := range inputs.Inputs {
|
||||
if choice.Contains(pname, inputFilters) {
|
||||
pnames = append(pnames, pname)
|
||||
}
|
||||
}
|
||||
sort.Strings(pnames)
|
||||
|
||||
// cache service inputs to print them at the end
|
||||
servInputs := make(map[string]telegraf.ServiceInput)
|
||||
// for alphabetical looping:
|
||||
servInputNames := make([]string, 0, len(pnames))
|
||||
|
||||
// Print Inputs
|
||||
for _, pname := range pnames {
|
||||
// Skip inputs that are registered twice for backward compatibility
|
||||
switch pname {
|
||||
case "cisco_telemetry_gnmi", "http_listener", "io", "KNXListener":
|
||||
continue
|
||||
}
|
||||
creator := inputs.Inputs[pname]
|
||||
input := creator()
|
||||
|
||||
if p, ok := input.(telegraf.ServiceInput); ok {
|
||||
servInputs[pname] = p
|
||||
servInputNames = append(servInputNames, pname)
|
||||
continue
|
||||
}
|
||||
|
||||
printConfig(pname, input, "inputs", commented, inputs.Deprecations[pname], outputBuffer)
|
||||
}
|
||||
|
||||
// Print Service Inputs
|
||||
if len(servInputs) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Strings(servInputNames)
|
||||
|
||||
outputBuffer.Write([]byte(serviceInputHeader))
|
||||
for _, name := range servInputNames {
|
||||
printConfig(name, servInputs[name], "inputs", commented, inputs.Deprecations[name], outputBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
func printFilteredOutputs(outputFilters []string, commented bool, outputBuffer io.Writer) {
|
||||
// Filter outputs
|
||||
var onames []string
|
||||
var influxdbV2 string
|
||||
|
||||
for oname := range outputs.Outputs {
|
||||
if choice.Contains(oname, outputFilters) {
|
||||
// Make influxdb_v2 the exception and have it be first in the list
|
||||
// Store it and add it later
|
||||
if oname == "influxdb_v2" {
|
||||
influxdbV2 = oname
|
||||
continue
|
||||
}
|
||||
|
||||
onames = append(onames, oname)
|
||||
}
|
||||
}
|
||||
sort.Strings(onames)
|
||||
|
||||
if influxdbV2 != "" {
|
||||
onames = append([]string{influxdbV2}, onames...)
|
||||
}
|
||||
|
||||
// Print Outputs
|
||||
for _, oname := range onames {
|
||||
creator := outputs.Outputs[oname]
|
||||
output := creator()
|
||||
printConfig(oname, output, "outputs", commented, outputs.Deprecations[oname], outputBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
func printFilteredSecretstores(secretstoreFilters []string, commented bool, outputBuffer io.Writer) {
|
||||
// Filter secretstores
|
||||
var snames []string
|
||||
for sname := range secretstores.SecretStores {
|
||||
if choice.Contains(sname, secretstoreFilters) {
|
||||
snames = append(snames, sname)
|
||||
}
|
||||
}
|
||||
sort.Strings(snames)
|
||||
|
||||
// Print SecretStores
|
||||
for _, sname := range snames {
|
||||
creator := secretstores.SecretStores[sname]
|
||||
store := creator("dummy")
|
||||
printConfig(sname, store, "secretstores", commented, secretstores.Deprecations[sname], outputBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
func printFilteredGlobalSections(sectionFilters []string, outputBuffer io.Writer) {
|
||||
if choice.Contains("global_tags", sectionFilters) {
|
||||
outputBuffer.Write([]byte(globalTagsConfig))
|
||||
}
|
||||
|
||||
if choice.Contains("agent", sectionFilters) {
|
||||
outputBuffer.Write([]byte(agentConfig))
|
||||
}
|
||||
}
|
||||
|
||||
func printConfig(name string, p telegraf.PluginDescriber, op string, commented bool, di telegraf.DeprecationInfo, outputBuffer io.Writer) {
|
||||
comment := ""
|
||||
if commented {
|
||||
comment = "# "
|
||||
}
|
||||
|
||||
if di.Since != "" {
|
||||
removalNote := ""
|
||||
if di.RemovalIn != "" {
|
||||
removalNote = " and will be removed in " + di.RemovalIn
|
||||
}
|
||||
fmt.Fprintf(outputBuffer, "\n%s## DEPRECATED: The %q plugin is deprecated in version %s%s, %s.",
|
||||
comment, name, di.Since, removalNote, di.Notice)
|
||||
}
|
||||
|
||||
sample := p.SampleConfig()
|
||||
if sample == "" {
|
||||
fmt.Fprintf(outputBuffer, "\n#[[%s.%s]]", op, name)
|
||||
fmt.Fprintf(outputBuffer, "\n%s # no configuration\n\n", comment)
|
||||
} else {
|
||||
lines := strings.Split(sample, "\n")
|
||||
outputBuffer.Write([]byte("\n"))
|
||||
for i, line := range lines {
|
||||
if i == len(lines)-1 {
|
||||
outputBuffer.Write([]byte("\n"))
|
||||
continue
|
||||
}
|
||||
outputBuffer.Write([]byte(strings.TrimRight(comment+line, " ") + "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintInputConfig prints the config usage of a single input.
|
||||
func PrintInputConfig(name string, outputBuffer io.Writer) error {
|
||||
creator, ok := inputs.Inputs[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("input %s not found", name)
|
||||
}
|
||||
|
||||
printConfig(name, creator(), "inputs", false, inputs.Deprecations[name], outputBuffer)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrintOutputConfig prints the config usage of a single output.
|
||||
func PrintOutputConfig(name string, outputBuffer io.Writer) error {
|
||||
creator, ok := outputs.Outputs[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("output %s not found", name)
|
||||
}
|
||||
|
||||
printConfig(name, creator(), "outputs", false, outputs.Deprecations[name], outputBuffer)
|
||||
return nil
|
||||
}
|
493
cmd/telegraf/telegraf.go
Normal file
493
cmd/telegraf/telegraf.go
Normal file
|
@ -0,0 +1,493 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-systemd/v22/daemon"
|
||||
"github.com/fatih/color"
|
||||
"github.com/influxdata/tail/watch"
|
||||
"gopkg.in/tomb.v1"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/agent"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
"github.com/influxdata/telegraf/logger"
|
||||
"github.com/influxdata/telegraf/plugins/aggregators"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
"github.com/influxdata/telegraf/plugins/outputs"
|
||||
"github.com/influxdata/telegraf/plugins/parsers"
|
||||
"github.com/influxdata/telegraf/plugins/processors"
|
||||
"github.com/influxdata/telegraf/plugins/secretstores"
|
||||
)
|
||||
|
||||
var stop chan struct{}
|
||||
|
||||
type GlobalFlags struct {
|
||||
config []string
|
||||
configDir []string
|
||||
testWait int
|
||||
configURLRetryAttempts int
|
||||
configURLWatchInterval time.Duration
|
||||
watchConfig string
|
||||
watchInterval time.Duration
|
||||
pidFile string
|
||||
plugindDir string
|
||||
password string
|
||||
oldEnvBehavior bool
|
||||
printPluginConfigSource bool
|
||||
test bool
|
||||
debug bool
|
||||
once bool
|
||||
quiet bool
|
||||
unprotected bool
|
||||
}
|
||||
|
||||
type WindowFlags struct {
|
||||
service string
|
||||
serviceName string
|
||||
serviceDisplayName string
|
||||
serviceRestartDelay string
|
||||
serviceAutoRestart bool
|
||||
console bool
|
||||
}
|
||||
|
||||
type App interface {
|
||||
Init(<-chan error, Filters, GlobalFlags, WindowFlags)
|
||||
Run() error
|
||||
|
||||
// Secret store commands
|
||||
ListSecretStores() ([]string, error)
|
||||
GetSecretStore(string) (telegraf.SecretStore, error)
|
||||
}
|
||||
|
||||
type Telegraf struct {
|
||||
pprofErr <-chan error
|
||||
|
||||
inputFilters []string
|
||||
outputFilters []string
|
||||
configFiles []string
|
||||
secretstoreFilters []string
|
||||
|
||||
cfg *config.Config
|
||||
|
||||
GlobalFlags
|
||||
WindowFlags
|
||||
}
|
||||
|
||||
func (t *Telegraf) Init(pprofErr <-chan error, f Filters, g GlobalFlags, w WindowFlags) {
|
||||
t.pprofErr = pprofErr
|
||||
t.inputFilters = f.input
|
||||
t.outputFilters = f.output
|
||||
t.secretstoreFilters = f.secretstore
|
||||
t.GlobalFlags = g
|
||||
t.WindowFlags = w
|
||||
|
||||
// Disable secret protection before performing any other operation
|
||||
if g.unprotected {
|
||||
log.Println("W! Running without secret protection!")
|
||||
config.DisableSecretProtection()
|
||||
}
|
||||
|
||||
// Set global password
|
||||
if g.password != "" {
|
||||
config.Password = config.NewSecret([]byte(g.password))
|
||||
}
|
||||
|
||||
// Set environment replacement behavior
|
||||
config.OldEnvVarReplacement = g.oldEnvBehavior
|
||||
|
||||
config.PrintPluginConfigSource = g.printPluginConfigSource
|
||||
}
|
||||
|
||||
func (t *Telegraf) ListSecretStores() ([]string, error) {
|
||||
c, err := t.loadConfiguration()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ids := make([]string, 0, len(c.SecretStores))
|
||||
for k := range c.SecretStores {
|
||||
ids = append(ids, k)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (t *Telegraf) GetSecretStore(id string) (telegraf.SecretStore, error) {
|
||||
t.quiet = true
|
||||
c, err := t.loadConfiguration()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store, found := c.SecretStores[id]
|
||||
if !found {
|
||||
return nil, errors.New("unknown secret store")
|
||||
}
|
||||
|
||||
return store, nil
|
||||
}
|
||||
|
||||
func (t *Telegraf) reloadLoop() error {
|
||||
reloadConfig := false
|
||||
reload := make(chan bool, 1)
|
||||
reload <- true
|
||||
for <-reload {
|
||||
reload <- false
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
signals := make(chan os.Signal, 1)
|
||||
signal.Notify(signals, os.Interrupt, syscall.SIGHUP,
|
||||
syscall.SIGTERM, syscall.SIGINT)
|
||||
if t.watchConfig != "" {
|
||||
for _, fConfig := range t.configFiles {
|
||||
if isURL(fConfig) {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := os.Stat(fConfig); err != nil {
|
||||
log.Printf("W! Cannot watch config %s: %s", fConfig, err)
|
||||
} else {
|
||||
go t.watchLocalConfig(ctx, signals, fConfig)
|
||||
}
|
||||
}
|
||||
for _, fConfigDirectory := range t.configDir {
|
||||
if _, err := os.Stat(fConfigDirectory); err != nil {
|
||||
log.Printf("W! Cannot watch config directory %s: %s", fConfigDirectory, err)
|
||||
} else {
|
||||
go t.watchLocalConfig(ctx, signals, fConfigDirectory)
|
||||
}
|
||||
}
|
||||
}
|
||||
if t.configURLWatchInterval > 0 {
|
||||
remoteConfigs := make([]string, 0)
|
||||
for _, fConfig := range t.configFiles {
|
||||
if isURL(fConfig) {
|
||||
remoteConfigs = append(remoteConfigs, fConfig)
|
||||
}
|
||||
}
|
||||
if len(remoteConfigs) > 0 {
|
||||
go t.watchRemoteConfigs(ctx, signals, t.configURLWatchInterval, remoteConfigs)
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
select {
|
||||
case sig := <-signals:
|
||||
if sig == syscall.SIGHUP {
|
||||
log.Println("I! Reloading Telegraf config")
|
||||
// May need to update the list of known config files
|
||||
// if a delete or create occured. That way on the reload
|
||||
// we ensure we watch the correct files.
|
||||
if err := t.getConfigFiles(); err != nil {
|
||||
log.Println("E! Error loading config files: ", err)
|
||||
}
|
||||
<-reload
|
||||
reload <- true
|
||||
}
|
||||
cancel()
|
||||
case err := <-t.pprofErr:
|
||||
log.Printf("E! pprof server failed: %v", err)
|
||||
cancel()
|
||||
case <-stop:
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
|
||||
err := t.runAgent(ctx, reloadConfig)
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
return fmt.Errorf("[telegraf] Error running agent: %w", err)
|
||||
}
|
||||
reloadConfig = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Telegraf) watchLocalConfig(ctx context.Context, signals chan os.Signal, fConfig string) {
|
||||
var mytomb tomb.Tomb
|
||||
var watcher watch.FileWatcher
|
||||
if t.watchConfig == "poll" {
|
||||
if t.watchInterval > 0 {
|
||||
watcher = watch.NewPollingFileWatcherWithDuration(fConfig, t.watchInterval)
|
||||
} else {
|
||||
watcher = watch.NewPollingFileWatcher(fConfig)
|
||||
}
|
||||
} else {
|
||||
watcher = watch.NewInotifyFileWatcher(fConfig)
|
||||
}
|
||||
changes, err := watcher.ChangeEvents(&mytomb, 0)
|
||||
if err != nil {
|
||||
log.Printf("E! Error watching config file/directory %q: %s\n", fConfig, err)
|
||||
return
|
||||
}
|
||||
log.Printf("I! Config watcher started for %s\n", fConfig)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
mytomb.Done()
|
||||
return
|
||||
case <-changes.Modified:
|
||||
log.Printf("I! Config file/directory %q modified\n", fConfig)
|
||||
case <-changes.Deleted:
|
||||
// deleted can mean moved. wait a bit a check existence
|
||||
<-time.After(time.Second)
|
||||
if _, err := os.Stat(fConfig); err == nil {
|
||||
log.Printf("I! Config file/directory %q overwritten\n", fConfig)
|
||||
} else {
|
||||
log.Printf("W! Config file/directory %q deleted\n", fConfig)
|
||||
}
|
||||
case <-changes.Truncated:
|
||||
log.Printf("I! Config file/directory %q truncated\n", fConfig)
|
||||
case <-changes.Created:
|
||||
log.Printf("I! Config directory %q has new file(s)\n", fConfig)
|
||||
case <-mytomb.Dying():
|
||||
log.Printf("I! Config watcher %q ended\n", fConfig)
|
||||
return
|
||||
}
|
||||
mytomb.Done()
|
||||
signals <- syscall.SIGHUP
|
||||
}
|
||||
|
||||
func (*Telegraf) watchRemoteConfigs(ctx context.Context, signals chan os.Signal, interval time.Duration, remoteConfigs []string) {
|
||||
configs := strings.Join(remoteConfigs, ", ")
|
||||
log.Printf("I! Remote config watcher started for: %s\n", configs)
|
||||
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
lastModified := make(map[string]string, len(remoteConfigs))
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-signals:
|
||||
return
|
||||
case <-ticker.C:
|
||||
for _, configURL := range remoteConfigs {
|
||||
req, err := http.NewRequest("HEAD", configURL, nil)
|
||||
if err != nil {
|
||||
log.Printf("W! Creating request for fetching config from %q failed: %v\n", configURL, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if v, exists := os.LookupEnv("INFLUX_TOKEN"); exists {
|
||||
req.Header.Add("Authorization", "Token "+v)
|
||||
}
|
||||
req.Header.Set("User-Agent", internal.ProductToken())
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("W! Fetching config from %q failed: %v\n", configURL, err)
|
||||
continue
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
modified := resp.Header.Get("Last-Modified")
|
||||
if modified == "" {
|
||||
log.Printf("E! Last-Modified header not found, stopping the watcher for %s\n", configURL)
|
||||
delete(lastModified, configURL)
|
||||
}
|
||||
|
||||
if lastModified[configURL] == "" {
|
||||
lastModified[configURL] = modified
|
||||
} else if lastModified[configURL] != modified {
|
||||
log.Printf("I! Remote config modified: %s\n", configURL)
|
||||
signals <- syscall.SIGHUP
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Telegraf) loadConfiguration() (*config.Config, error) {
|
||||
// If no other options are specified, load the config file and run.
|
||||
c := config.NewConfig()
|
||||
c.Agent.Quiet = t.quiet
|
||||
c.Agent.ConfigURLRetryAttempts = t.configURLRetryAttempts
|
||||
c.OutputFilters = t.outputFilters
|
||||
c.InputFilters = t.inputFilters
|
||||
c.SecretStoreFilters = t.secretstoreFilters
|
||||
|
||||
if err := t.getConfigFiles(); err != nil {
|
||||
return c, err
|
||||
}
|
||||
if err := c.LoadAll(t.configFiles...); err != nil {
|
||||
return c, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (t *Telegraf) getConfigFiles() error {
|
||||
var configFiles []string
|
||||
|
||||
configFiles = append(configFiles, t.config...)
|
||||
for _, fConfigDirectory := range t.configDir {
|
||||
files, err := config.WalkDirectory(fConfigDirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configFiles = append(configFiles, files...)
|
||||
}
|
||||
|
||||
// load default config paths if none are found
|
||||
if len(configFiles) == 0 {
|
||||
defaultFiles, err := config.GetDefaultConfigPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load default config paths: %w", err)
|
||||
}
|
||||
configFiles = append(configFiles, defaultFiles...)
|
||||
}
|
||||
|
||||
t.configFiles = configFiles
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Telegraf) runAgent(ctx context.Context, reloadConfig bool) error {
|
||||
c := t.cfg
|
||||
var err error
|
||||
if reloadConfig {
|
||||
if c, err = t.loadConfiguration(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !t.test && t.testWait == 0 && len(c.Outputs) == 0 {
|
||||
return errors.New("no outputs found, probably invalid config file provided")
|
||||
}
|
||||
if t.plugindDir == "" && len(c.Inputs) == 0 {
|
||||
return errors.New("no inputs found, probably invalid config file provided")
|
||||
}
|
||||
|
||||
if int64(c.Agent.Interval) <= 0 {
|
||||
return fmt.Errorf("agent interval must be positive, found %v", c.Agent.Interval)
|
||||
}
|
||||
|
||||
if int64(c.Agent.FlushInterval) <= 0 {
|
||||
return fmt.Errorf("agent flush_interval must be positive; found %v", c.Agent.Interval)
|
||||
}
|
||||
|
||||
// Setup logging as configured.
|
||||
logConfig := &logger.Config{
|
||||
Debug: c.Agent.Debug || t.debug,
|
||||
Quiet: c.Agent.Quiet || t.quiet,
|
||||
LogTarget: c.Agent.LogTarget,
|
||||
LogFormat: c.Agent.LogFormat,
|
||||
Logfile: c.Agent.Logfile,
|
||||
StructuredLogMessageKey: c.Agent.StructuredLogMessageKey,
|
||||
RotationInterval: time.Duration(c.Agent.LogfileRotationInterval),
|
||||
RotationMaxSize: int64(c.Agent.LogfileRotationMaxSize),
|
||||
RotationMaxArchives: c.Agent.LogfileRotationMaxArchives,
|
||||
LogWithTimezone: c.Agent.LogWithTimezone,
|
||||
}
|
||||
|
||||
if err := logger.SetupLogging(logConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("I! Starting Telegraf %s%s brought to you by InfluxData the makers of InfluxDB", internal.Version, internal.Customized)
|
||||
log.Printf("I! Available plugins: %d inputs, %d aggregators, %d processors, %d parsers, %d outputs, %d secret-stores",
|
||||
len(inputs.Inputs),
|
||||
len(aggregators.Aggregators),
|
||||
len(processors.Processors),
|
||||
len(parsers.Parsers),
|
||||
len(outputs.Outputs),
|
||||
len(secretstores.SecretStores),
|
||||
)
|
||||
log.Printf("I! Loaded inputs: %s\n%s", strings.Join(c.InputNames(), " "), c.InputNamesWithSources())
|
||||
log.Printf("I! Loaded aggregators: %s\n%s", strings.Join(c.AggregatorNames(), " "), c.AggregatorNamesWithSources())
|
||||
log.Printf("I! Loaded processors: %s\n%s", strings.Join(c.ProcessorNames(), " "), c.ProcessorNamesWithSources())
|
||||
log.Printf("I! Loaded secretstores: %s\n%s", strings.Join(c.SecretstoreNames(), " "), c.SecretstoreNamesWithSources())
|
||||
if !t.once && (t.test || t.testWait != 0) {
|
||||
log.Print("W! " + color.RedString("Outputs are not used in testing mode!"))
|
||||
} else {
|
||||
log.Printf("I! Loaded outputs: %s\n%s", strings.Join(c.OutputNames(), " "), c.OutputNamesWithSources())
|
||||
}
|
||||
log.Printf("I! Tags enabled: %s", c.ListTags())
|
||||
|
||||
if count, found := c.Deprecations["inputs"]; found && (count[0] > 0 || count[1] > 0) {
|
||||
log.Printf("W! Deprecated inputs: %d and %d options", count[0], count[1])
|
||||
}
|
||||
if count, found := c.Deprecations["aggregators"]; found && (count[0] > 0 || count[1] > 0) {
|
||||
log.Printf("W! Deprecated aggregators: %d and %d options", count[0], count[1])
|
||||
}
|
||||
if count, found := c.Deprecations["processors"]; found && (count[0] > 0 || count[1] > 0) {
|
||||
log.Printf("W! Deprecated processors: %d and %d options", count[0], count[1])
|
||||
}
|
||||
if count, found := c.Deprecations["outputs"]; found && (count[0] > 0 || count[1] > 0) {
|
||||
log.Printf("W! Deprecated outputs: %d and %d options", count[0], count[1])
|
||||
}
|
||||
if count, found := c.Deprecations["secretstores"]; found && (count[0] > 0 || count[1] > 0) {
|
||||
log.Printf("W! Deprecated secretstores: %d and %d options", count[0], count[1])
|
||||
}
|
||||
|
||||
// Compute the amount of locked memory needed for the secrets
|
||||
if !t.GlobalFlags.unprotected {
|
||||
required := 3 * c.NumberSecrets * uint64(os.Getpagesize())
|
||||
available := getLockedMemoryLimit()
|
||||
if required > available {
|
||||
required /= 1024
|
||||
available /= 1024
|
||||
log.Printf("I! Found %d secrets...", c.NumberSecrets)
|
||||
msg := fmt.Sprintf("Insufficient lockable memory %dkb when %dkb is required.", available, required)
|
||||
msg += " Please increase the limit for Telegraf in your Operating System!"
|
||||
log.Print("W! " + color.RedString(msg))
|
||||
}
|
||||
}
|
||||
ag := agent.NewAgent(c)
|
||||
|
||||
// Notify systemd that telegraf is ready
|
||||
// SdNotify() only tries to notify if the NOTIFY_SOCKET environment is set, so it's safe to call when systemd isn't present.
|
||||
// Ignore the return values here because they're not valid for platforms that don't use systemd.
|
||||
// For platforms that use systemd, telegraf doesn't log if the notification failed.
|
||||
//nolint:errcheck // see above
|
||||
daemon.SdNotify(false, daemon.SdNotifyReady)
|
||||
|
||||
if t.once {
|
||||
wait := time.Duration(t.testWait) * time.Second
|
||||
return ag.Once(ctx, wait)
|
||||
}
|
||||
|
||||
if t.test || t.testWait != 0 {
|
||||
wait := time.Duration(t.testWait) * time.Second
|
||||
return ag.Test(ctx, wait)
|
||||
}
|
||||
|
||||
if t.pidFile != "" {
|
||||
f, err := os.OpenFile(t.pidFile, os.O_CREATE|os.O_WRONLY, 0640)
|
||||
if err != nil {
|
||||
log.Printf("E! Unable to create pidfile: %s", err)
|
||||
} else {
|
||||
fmt.Fprintf(f, "%d\n", os.Getpid())
|
||||
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := os.Remove(t.pidFile)
|
||||
if err != nil {
|
||||
log.Printf("E! Unable to remove pidfile: %s", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
return ag.Run(ctx)
|
||||
}
|
||||
|
||||
// isURL checks if string is valid url
|
||||
func isURL(str string) bool {
|
||||
u, err := url.Parse(str)
|
||||
return err == nil && u.Scheme != "" && u.Host != ""
|
||||
}
|
42
cmd/telegraf/telegraf_posix.go
Normal file
42
cmd/telegraf/telegraf_posix.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
//go:build !windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runtime"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func (t *Telegraf) Run() error {
|
||||
stop = make(chan struct{})
|
||||
defer close(stop)
|
||||
|
||||
cfg, err := t.loadConfiguration()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.cfg = cfg
|
||||
return t.reloadLoop()
|
||||
}
|
||||
|
||||
func getLockedMemoryLimit() uint64 {
|
||||
var rLimitMemlock int
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "dragonfly", "freebsd", "netbsd", "openbsd":
|
||||
// From https://cgit.freebsd.org/src/tree/sys/sys/resource.h#n107
|
||||
rLimitMemlock = 6
|
||||
default:
|
||||
// From https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/resource.h#L35
|
||||
rLimitMemlock = 8
|
||||
}
|
||||
|
||||
var limit syscall.Rlimit
|
||||
if err := syscall.Getrlimit(rLimitMemlock, &limit); err != nil {
|
||||
log.Printf("E! Cannot get limit for locked memory: %v", err)
|
||||
return 0
|
||||
}
|
||||
//nolint:unconvert // required for e.g. FreeBSD that has the field as int64
|
||||
return uint64(limit.Max)
|
||||
}
|
408
cmd/telegraf/telegraf_windows.go
Normal file
408
cmd/telegraf/telegraf_windows.go
Normal file
|
@ -0,0 +1,408 @@
|
|||
//go:build windows
|
||||
|
||||
//go:generate ../../scripts/windows-gen-syso.sh $GOARCH
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/svc"
|
||||
"golang.org/x/sys/windows/svc/eventlog"
|
||||
"golang.org/x/sys/windows/svc/mgr"
|
||||
)
|
||||
|
||||
func getLockedMemoryLimit() uint64 {
|
||||
handle := windows.CurrentProcess()
|
||||
|
||||
var low, high uintptr
|
||||
var flag uint32
|
||||
windows.GetProcessWorkingSetSizeEx(handle, &low, &high, &flag)
|
||||
|
||||
return uint64(high)
|
||||
}
|
||||
|
||||
func (t *Telegraf) Run() error {
|
||||
// Process the service commands
|
||||
if t.service != "" {
|
||||
fmt.Println("The use of --service is deprecated, please use the 'service' command instead!")
|
||||
switch t.service {
|
||||
case "install":
|
||||
cfg := &serviceConfig{
|
||||
displayName: t.serviceDisplayName,
|
||||
restartDelay: t.serviceRestartDelay,
|
||||
autoRestart: t.serviceAutoRestart,
|
||||
configs: t.config,
|
||||
configDirs: t.configDir,
|
||||
watchConfig: t.watchConfig,
|
||||
}
|
||||
if err := installService(t.serviceName, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Successfully installed service %q\n", t.serviceName)
|
||||
case "uninstall":
|
||||
if err := uninstallService(t.serviceName); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Successfully uninstalled service %q\n", t.serviceName)
|
||||
case "start":
|
||||
if err := startService(t.serviceName); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Successfully started service %q\n", t.serviceName)
|
||||
case "stop":
|
||||
if err := stopService(t.serviceName); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Successfully stopped service %q\n", t.serviceName)
|
||||
case "status":
|
||||
status, err := queryService(t.serviceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Service %q is in %q state\n", t.serviceName, status)
|
||||
default:
|
||||
return fmt.Errorf("invalid service command %q", t.service)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Determine if Telegraf is started as a Windows service.
|
||||
isWinService, err := svc.IsWindowsService()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot determine if run as Windows service: %w", err)
|
||||
}
|
||||
if !t.console && isWinService {
|
||||
return svc.Run(t.serviceName, t)
|
||||
}
|
||||
|
||||
// Load the configuration file(s)
|
||||
cfg, err := t.loadConfiguration()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.cfg = cfg
|
||||
|
||||
stop = make(chan struct{})
|
||||
defer close(stop)
|
||||
return t.reloadLoop()
|
||||
}
|
||||
|
||||
// Handler for the Windows service framework
|
||||
func (t *Telegraf) Execute(_ []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
|
||||
// Mark the status as startup pending until we are fully started
|
||||
const accepted = svc.AcceptStop | svc.AcceptShutdown
|
||||
changes <- svc.Status{State: svc.StartPending}
|
||||
defer func() {
|
||||
changes <- svc.Status{State: svc.Stopped}
|
||||
}()
|
||||
|
||||
// Create a eventlog logger for all service related things
|
||||
svclog, err := eventlog.Open(t.serviceName)
|
||||
if err != nil {
|
||||
log.Printf("E! Initializing the service logger failed: %s", err)
|
||||
return true, 1
|
||||
}
|
||||
defer svclog.Close()
|
||||
|
||||
// Load the configuration file(s)
|
||||
cfg, err := t.loadConfiguration()
|
||||
if err != nil {
|
||||
if lerr := svclog.Error(100, err.Error()); lerr != nil {
|
||||
log.Printf("E! Logging error %q failed: %s", err, lerr)
|
||||
}
|
||||
return true, 2
|
||||
}
|
||||
t.cfg = cfg
|
||||
|
||||
// Actually start the processing loop in the background to be able to
|
||||
// react to service change requests
|
||||
loopErr := make(chan error)
|
||||
stop = make(chan struct{})
|
||||
defer close(loopErr)
|
||||
defer close(stop)
|
||||
go func() {
|
||||
loopErr <- t.reloadLoop()
|
||||
}()
|
||||
changes <- svc.Status{State: svc.Running, Accepts: accepted}
|
||||
|
||||
for {
|
||||
select {
|
||||
case err := <-loopErr:
|
||||
if err != nil {
|
||||
if lerr := svclog.Error(100, err.Error()); lerr != nil {
|
||||
log.Printf("E! Logging error %q failed: %s", err, lerr)
|
||||
}
|
||||
return true, 3
|
||||
}
|
||||
return false, 0
|
||||
case c := <-r:
|
||||
switch c.Cmd {
|
||||
case svc.Interrogate:
|
||||
changes <- c.CurrentStatus
|
||||
// Testing deadlock from https://code.google.com/p/winsvc/issues/detail?id=4
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
changes <- c.CurrentStatus
|
||||
case svc.Stop, svc.Shutdown:
|
||||
changes <- svc.Status{State: svc.StopPending}
|
||||
var empty struct{}
|
||||
stop <- empty // signal reloadLoop to finish (context cancel)
|
||||
default:
|
||||
msg := fmt.Sprintf("Unexpected control request #%d", c)
|
||||
if lerr := svclog.Error(100, msg); lerr != nil {
|
||||
log.Printf("E! Logging error %q failed: %s", msg, lerr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type serviceConfig struct {
|
||||
displayName string
|
||||
restartDelay string
|
||||
autoRestart bool
|
||||
|
||||
// Telegraf parameters
|
||||
configs []string
|
||||
configDirs []string
|
||||
watchConfig string
|
||||
}
|
||||
|
||||
func installService(name string, cfg *serviceConfig) error {
|
||||
// Determine the executable to use in the service
|
||||
executable, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("determining executable failed: %w", err)
|
||||
}
|
||||
|
||||
// Determine the program files directory name
|
||||
programFiles := os.Getenv("ProgramFiles")
|
||||
if programFiles == "" { // Should never happen
|
||||
programFiles = "C:\\Program Files"
|
||||
}
|
||||
|
||||
// Collect the command line arguments
|
||||
args := make([]string, 0, 2*(len(cfg.configs)+len(cfg.configDirs))+2)
|
||||
for _, fn := range cfg.configs {
|
||||
args = append(args, "--config", fn)
|
||||
}
|
||||
for _, dn := range cfg.configDirs {
|
||||
args = append(args, "--config-directory", dn)
|
||||
}
|
||||
if len(args) == 0 {
|
||||
args = append(args, "--config", filepath.Join(programFiles, "Telegraf", "telegraf.conf"))
|
||||
}
|
||||
if cfg.watchConfig != "" {
|
||||
args = append(args, "--watch-config", cfg.watchConfig)
|
||||
}
|
||||
// Pass the service name to the command line, to have a custom name when relaunching as a service
|
||||
args = append(args, "--service-name", name)
|
||||
|
||||
// Create a configuration for the service
|
||||
svccfg := mgr.Config{
|
||||
DisplayName: cfg.displayName,
|
||||
Description: "Collects, processes and publishes data using a series of plugins.",
|
||||
StartType: mgr.StartAutomatic,
|
||||
ServiceType: windows.SERVICE_WIN32_OWN_PROCESS,
|
||||
}
|
||||
|
||||
// Connect to the service manager and try to install the service if it
|
||||
// doesn't exist. Fail on existing service and stop installation.
|
||||
svcmgr, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return fmt.Errorf("connecting to service manager failed: %w", err)
|
||||
}
|
||||
defer svcmgr.Disconnect()
|
||||
|
||||
if service, err := svcmgr.OpenService(name); err == nil {
|
||||
service.Close()
|
||||
return fmt.Errorf("service %q is already installed", name)
|
||||
}
|
||||
|
||||
service, err := svcmgr.CreateService(name, executable, svccfg, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating service failed: %w", err)
|
||||
}
|
||||
defer service.Close()
|
||||
|
||||
// Set the recovery strategy to restart with a fixed period of 10 seconds
|
||||
// and the user specified delay if requested
|
||||
if cfg.autoRestart {
|
||||
delay, err := time.ParseDuration(cfg.restartDelay)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse restart delay %q: %w", cfg.restartDelay, err)
|
||||
}
|
||||
recovery := []mgr.RecoveryAction{{Type: mgr.ServiceRestart, Delay: delay}}
|
||||
if err := service.SetRecoveryActions(recovery, 10); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Register the event as a source of eventlog events
|
||||
events := uint32(eventlog.Error | eventlog.Warning | eventlog.Info)
|
||||
if err := eventlog.InstallAsEventCreate(name, events); err != nil {
|
||||
//nolint:errcheck // Try to remove the service on best effort basis as we cannot handle any error here
|
||||
service.Delete()
|
||||
return fmt.Errorf("setting up eventlog source failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func uninstallService(name string) error {
|
||||
// Connect to the service manager and try to open the service. In case the
|
||||
// service is not installed, return with the corresponding error.
|
||||
svcmgr, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return fmt.Errorf("connecting to service manager failed: %w", err)
|
||||
}
|
||||
defer svcmgr.Disconnect()
|
||||
|
||||
service, err := svcmgr.OpenService(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening service failed: %w", err)
|
||||
}
|
||||
defer service.Close()
|
||||
|
||||
// Uninstall the service and remove the eventlog source
|
||||
if err := service.Delete(); err != nil {
|
||||
return fmt.Errorf("uninstalling service failed: %w", err)
|
||||
}
|
||||
|
||||
if err := eventlog.Remove(name); err != nil {
|
||||
return fmt.Errorf("removing eventlog source failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func startService(name string) error {
|
||||
nameUTF16, err := syscall.UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("conversion of service name %q to UTF16 failed: %w", name, err)
|
||||
}
|
||||
|
||||
// Open the service manager and service with the least privileges required to start the service
|
||||
mgrhandle, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_CONNECT|windows.SC_MANAGER_ENUMERATE_SERVICE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening service manager failed: %w", err)
|
||||
}
|
||||
defer windows.CloseServiceHandle(mgrhandle)
|
||||
|
||||
svchandle, err := windows.OpenService(mgrhandle, nameUTF16, windows.SERVICE_QUERY_STATUS|windows.SERVICE_START)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening service failed: %w", err)
|
||||
}
|
||||
service := &mgr.Service{Handle: svchandle, Name: name}
|
||||
defer service.Close()
|
||||
|
||||
// Check if the service is actually stopped
|
||||
status, err := service.Query()
|
||||
if err != nil {
|
||||
return fmt.Errorf("querying service state failed: %w", err)
|
||||
}
|
||||
if status.State != svc.Stopped {
|
||||
return fmt.Errorf("service is not stopped but in state %q", stateDescription(status.State))
|
||||
}
|
||||
|
||||
return service.Start()
|
||||
}
|
||||
|
||||
func stopService(name string) error {
|
||||
nameUTF16, err := syscall.UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("conversion of service name %q to UTF16 failed: %w", name, err)
|
||||
}
|
||||
|
||||
// Open the service manager and service with the least privileges required to start the service
|
||||
mgrhandle, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_CONNECT|windows.SC_MANAGER_ENUMERATE_SERVICE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening service manager failed: %w", err)
|
||||
}
|
||||
defer windows.CloseServiceHandle(mgrhandle)
|
||||
|
||||
svchandle, err := windows.OpenService(mgrhandle, nameUTF16, windows.SERVICE_QUERY_STATUS|windows.SERVICE_STOP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening service failed: %w", err)
|
||||
}
|
||||
service := &mgr.Service{Handle: svchandle, Name: name}
|
||||
defer service.Close()
|
||||
|
||||
// Stop the service and wait for it to finish
|
||||
status, err := service.Control(svc.Stop)
|
||||
if err != nil {
|
||||
return fmt.Errorf("stopping service failed: %w", err)
|
||||
}
|
||||
for status.State != svc.Stopped {
|
||||
// Wait for the hinted time, but clip it to prevent stalling operation
|
||||
wait := time.Duration(status.WaitHint) * time.Millisecond
|
||||
if wait < 100*time.Millisecond {
|
||||
wait = 100 * time.Millisecond
|
||||
} else if wait > 10*time.Second {
|
||||
wait = 10 * time.Second
|
||||
}
|
||||
time.Sleep(wait)
|
||||
|
||||
status, err = service.Query()
|
||||
if err != nil {
|
||||
return fmt.Errorf("querying service state failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func queryService(name string) (string, error) {
|
||||
nameUTF16, err := syscall.UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("conversion of service name %q to UTF16 failed: %w", name, err)
|
||||
}
|
||||
|
||||
// Open the service manager and service with the least privileges required to start the service
|
||||
mgrhandle, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_CONNECT|windows.SC_MANAGER_ENUMERATE_SERVICE)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("opening service manager failed: %w", err)
|
||||
}
|
||||
defer windows.CloseServiceHandle(mgrhandle)
|
||||
|
||||
svchandle, err := windows.OpenService(mgrhandle, nameUTF16, windows.SERVICE_QUERY_STATUS)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("opening service failed: %w", err)
|
||||
}
|
||||
service := &mgr.Service{Handle: svchandle, Name: name}
|
||||
defer service.Close()
|
||||
|
||||
// Query the service state and report it to the user
|
||||
status, err := service.Query()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("querying service state failed: %w", err)
|
||||
}
|
||||
|
||||
return stateDescription(status.State), nil
|
||||
}
|
||||
|
||||
func stateDescription(state svc.State) string {
|
||||
switch state {
|
||||
case svc.Stopped:
|
||||
return "stopped"
|
||||
case svc.StartPending:
|
||||
return "start pending"
|
||||
case svc.StopPending:
|
||||
return "stop pending"
|
||||
case svc.Running:
|
||||
return "running"
|
||||
case svc.ContinuePending:
|
||||
return "continue pending"
|
||||
case svc.PausePending:
|
||||
return "pause pending"
|
||||
case svc.Paused:
|
||||
return "paused"
|
||||
}
|
||||
return fmt.Sprintf("unknown %v", state)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue