diff --git a/CHANGELOG.md b/CHANGELOG.md index cb0261f79..2eebba92c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ to "stdout". - [#1172](https://github.com/influxdata/telegraf/pull/1172): Ceph storage stats. Thanks @robinpercy! - [#1233](https://github.com/influxdata/telegraf/pull/1233): Updated golint gopsutil dependency. - [#479](https://github.com/influxdata/telegraf/issues/479): per-plugin execution time added to debug output. +- [#1127](https://github.com/influxdata/telegraf/pull/1142): Support for glob patterns in exec plugin commands configuration. ### Bugfixes diff --git a/plugins/inputs/exec/README.md b/plugins/inputs/exec/README.md index a75ae7856..14acf0957 100644 --- a/plugins/inputs/exec/README.md +++ b/plugins/inputs/exec/README.md @@ -6,14 +6,17 @@ Please also see: [Telegraf Input Data Formats](https://github.com/influxdata/tel #### Configuration -In this example a script called ```/tmp/test.sh``` and a script called ```/tmp/test2.sh``` -are configured for ```[[inputs.exec]]``` in JSON format. +In this example a script called ```/tmp/test.sh```, a script called ```/tmp/test2.sh```, and +all scripts matching glob pattern ```/tmp/collect_*.sh``` are configured for ```[[inputs.exec]]``` +in JSON format. Glob patterns are matched on every run, so adding new scripts that match the pattern +will cause them to be picked up immediately. ``` # Read flattened metrics from one or more commands that output JSON to stdout [[inputs.exec]] # Shell/commands array - commands = ["/tmp/test.sh", "/tmp/test2.sh"] + # Full command line to executable with parameters, or a glob pattern to run all matching files. + commands = ["/tmp/test.sh", "/tmp/test2.sh", "/tmp/collect_*.sh"] # Data format to consume. # NOTE json only reads numerical measurements, strings and booleans are ignored. @@ -180,4 +183,3 @@ sensu.metric.net.server0.eth0.rx_dropped 0 1444234982 The templates configuration will be used to parse the graphite metrics to support influxdb/opentsdb tagging store engines. More detail information about templates, please refer to [The graphite Input](https://github.com/influxdata/influxdb/blob/master/services/graphite/README.md) - diff --git a/plugins/inputs/exec/exec.go b/plugins/inputs/exec/exec.go index c1b2092e8..1f5f12203 100644 --- a/plugins/inputs/exec/exec.go +++ b/plugins/inputs/exec/exec.go @@ -4,6 +4,8 @@ import ( "bytes" "fmt" "os/exec" + "path/filepath" + "strings" "sync" "syscall" "time" @@ -19,7 +21,11 @@ import ( const sampleConfig = ` ## Commands array - commands = ["/tmp/test.sh", "/usr/bin/mycollector --foo=bar"] + commands = [ + "/tmp/test.sh", + "/usr/bin/mycollector --foo=bar", + "/tmp/collect_*.sh" + ] ## Timeout for each command to complete. timeout = "5s" @@ -150,10 +156,36 @@ func (e *Exec) Gather(acc telegraf.Accumulator) error { e.Command = "" } - e.errChan = make(chan error, len(e.Commands)) + commands := make([]string, 0, len(e.Commands)) + for _, pattern := range e.Commands { + cmdAndArgs := strings.SplitN(pattern, " ", 2) + if len(cmdAndArgs) == 0 { + continue + } - e.wg.Add(len(e.Commands)) - for _, command := range e.Commands { + matches, err := filepath.Glob(cmdAndArgs[0]) + if err != nil { + return err + } + + if len(matches) == 0 { + // There were no matches with the glob pattern, so let's assume + // that the command is in PATH and just run it as it is + commands = append(commands, pattern) + } else { + // There were matches, so we'll append each match together with + // the arguments to the commands slice + for _, match := range matches { + commands = append( + commands, strings.Join([]string{match, cmdAndArgs[1]}, " ")) + } + } + } + + e.errChan = make(chan error, len(commands)) + + e.wg.Add(len(commands)) + for _, command := range commands { go e.ProcessCommand(command, acc) } e.wg.Wait() diff --git a/plugins/inputs/exec/exec_test.go b/plugins/inputs/exec/exec_test.go index 9c75857cf..cd9c9eaef 100644 --- a/plugins/inputs/exec/exec_test.go +++ b/plugins/inputs/exec/exec_test.go @@ -169,3 +169,51 @@ func TestLineProtocolParseMultiple(t *testing.T) { acc.AssertContainsTaggedFields(t, "cpu", fields, tags) } } + +func TestExecCommandWithGlob(t *testing.T) { + parser, _ := parsers.NewValueParser("metric", "string", nil) + e := NewExec() + e.Commands = []string{"/bin/ech* metric_value"} + e.SetParser(parser) + + var acc testutil.Accumulator + err := e.Gather(&acc) + require.NoError(t, err) + + fields := map[string]interface{}{ + "value": "metric_value", + } + acc.AssertContainsFields(t, "metric", fields) +} + +func TestExecCommandWithoutGlob(t *testing.T) { + parser, _ := parsers.NewValueParser("metric", "string", nil) + e := NewExec() + e.Commands = []string{"/bin/echo metric_value"} + e.SetParser(parser) + + var acc testutil.Accumulator + err := e.Gather(&acc) + require.NoError(t, err) + + fields := map[string]interface{}{ + "value": "metric_value", + } + acc.AssertContainsFields(t, "metric", fields) +} + +func TestExecCommandWithoutGlobAndPath(t *testing.T) { + parser, _ := parsers.NewValueParser("metric", "string", nil) + e := NewExec() + e.Commands = []string{"echo metric_value"} + e.SetParser(parser) + + var acc testutil.Accumulator + err := e.Gather(&acc) + require.NoError(t, err) + + fields := map[string]interface{}{ + "value": "metric_value", + } + acc.AssertContainsFields(t, "metric", fields) +}