1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

1
cmd/telegraf/README.md Symbolic link
View file

@ -0,0 +1 @@
../../docs/COMMANDS_AND_FLAGS.md

104
cmd/telegraf/agent.conf Normal file
View 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
View 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
View 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
},
},
},
},
}
}

View 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
},
},
},
},
}
}

View 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
},
},
},
},
}
}

View 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
View 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
View 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)
}

View 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
View 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
View 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
View 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 != ""
}

View 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)
}

View 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)
}