Allow users to specify outputs as lists

This will provide the ability to specify multiple outputs for a single
type of output.

In essence, allowing this:

[outputs]

[[outputs.influxdb]]
  urls = ["udp://localhost:8089"]
  database = "udp-telegraf"

[[outputs.influxdb]]
  urls = ["http://myhost:8086"]
  database = "telegraf"

[[outputs.kafka]]
  brokers = ["192.168.99.100:9092"]
  topic = "telegraf"

closes #335
This commit is contained in:
Cameron Sparr 2015-11-13 16:14:07 -07:00
parent bf8e0f4cae
commit b10b186cc8
2 changed files with 51 additions and 51 deletions

View File

@ -7,6 +7,7 @@ import (
"math/big" "math/big"
"os" "os"
"sort" "sort"
"strings"
"sync" "sync"
"time" "time"
@ -147,17 +148,13 @@ func (a *Agent) Close() error {
func (a *Agent) LoadOutputs(filters []string, config *Config) ([]string, error) { func (a *Agent) LoadOutputs(filters []string, config *Config) ([]string, error) {
var names []string var names []string
for _, name := range config.OutputsDeclared() { for name, output := range config.OutputsDeclared() {
creator, ok := outputs.Outputs[name] // Trim the ID off the output name for filtering
if !ok { filtername := strings.TrimRight(name, "-0123456789")
return nil, fmt.Errorf("Undefined but requested output: %s", name) if sliceContains(filtername, filters) || len(filters) == 0 {
}
if sliceContains(name, filters) || len(filters) == 0 {
if a.Debug { if a.Debug {
log.Println("Output Enabled: ", name) log.Println("Output Enabled: ", name)
} }
output := creator()
err := config.ApplyOutput(name, output) err := config.ApplyOutput(name, output)
if err != nil { if err != nil {
@ -178,15 +175,8 @@ func (a *Agent) LoadOutputs(filters []string, config *Config) ([]string, error)
func (a *Agent) LoadPlugins(filters []string, config *Config) ([]string, error) { func (a *Agent) LoadPlugins(filters []string, config *Config) ([]string, error) {
var names []string var names []string
for _, name := range config.PluginsDeclared() { for name, plugin := range config.PluginsDeclared() {
creator, ok := plugins.Plugins[name]
if !ok {
return nil, fmt.Errorf("Undefined but requested plugin: %s", name)
}
if sliceContains(name, filters) || len(filters) == 0 { if sliceContains(name, filters) || len(filters) == 0 {
plugin := creator()
config, err := config.ApplyPlugin(name, plugin) config, err := config.ApplyPlugin(name, plugin)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"path/filepath" "path/filepath"
"reflect" "reflect"
"sort" "sort"
@ -20,8 +21,8 @@ import (
// will be logging to, as well as all the plugins that the user has // will be logging to, as well as all the plugins that the user has
// specified // specified
type Config struct { type Config struct {
// This lives outside the agent because mergeStruct doesn't need to handle maps normally. // This lives outside the agent because mergeStruct doesn't need to handle
// We just copy the elements manually in ApplyAgent. // maps normally. We just copy the elements manually in ApplyAgent.
Tags map[string]string Tags map[string]string
agent *Agent agent *Agent
@ -156,23 +157,13 @@ func (c *Config) ApplyPlugin(name string, v interface{}) (*ConfiguredPlugin, err
// Couldn't figure out how to get this to work with the declared function. // Couldn't figure out how to get this to work with the declared function.
// PluginsDeclared returns the name of all plugins declared in the config. // PluginsDeclared returns the name of all plugins declared in the config.
func (c *Config) PluginsDeclared() []string { func (c *Config) PluginsDeclared() map[string]plugins.Plugin {
var names []string return c.plugins
for name := range c.plugins {
names = append(names, name)
}
sort.Strings(names)
return names
} }
// OutputsDeclared returns the name of all outputs declared in the config. // OutputsDeclared returns the name of all outputs declared in the config.
func (c *Config) OutputsDeclared() []string { func (c *Config) OutputsDeclared() map[string]outputs.Output {
var names []string return c.outputs
for name := range c.outputs {
names = append(names, name)
}
sort.Strings(names)
return names
} }
// ListTags returns a string of tags specified in the config, // ListTags returns a string of tags specified in the config,
@ -283,7 +274,7 @@ func PrintSampleConfig(pluginFilters []string, outputFilters []string) {
creator := outputs.Outputs[oname] creator := outputs.Outputs[oname]
output := creator() output := creator()
fmt.Printf("\n# %s\n[outputs.%s]", output.Description(), oname) fmt.Printf("\n# %s\n[[outputs.%s]]", output.Description(), oname)
config := output.SampleConfig() config := output.SampleConfig()
if config == "" { if config == "" {
@ -394,20 +385,24 @@ func findField(fieldName string, value reflect.Value) reflect.Value {
return reflect.Value{} return reflect.Value{}
} }
// A very limited merge. Merges the fields named in the fields parameter, replacing most values, but appending to arrays. // A very limited merge. Merges the fields named in the fields parameter,
// replacing most values, but appending to arrays.
func mergeStruct(base, overlay interface{}, fields []string) error { func mergeStruct(base, overlay interface{}, fields []string) error {
baseValue := reflect.ValueOf(base).Elem() baseValue := reflect.ValueOf(base).Elem()
overlayValue := reflect.ValueOf(overlay).Elem() overlayValue := reflect.ValueOf(overlay).Elem()
if baseValue.Kind() != reflect.Struct { if baseValue.Kind() != reflect.Struct {
return fmt.Errorf("Tried to merge something that wasn't a struct: type %v was %v", baseValue.Type(), baseValue.Kind()) return fmt.Errorf("Tried to merge something that wasn't a struct: type %v was %v",
baseValue.Type(), baseValue.Kind())
} }
if baseValue.Type() != overlayValue.Type() { if baseValue.Type() != overlayValue.Type() {
return fmt.Errorf("Tried to merge two different types: %v and %v", baseValue.Type(), overlayValue.Type()) return fmt.Errorf("Tried to merge two different types: %v and %v",
baseValue.Type(), overlayValue.Type())
} }
for _, field := range fields { for _, field := range fields {
overlayFieldValue := findField(field, overlayValue) overlayFieldValue := findField(field, overlayValue)
if !overlayFieldValue.IsValid() { if !overlayFieldValue.IsValid() {
return fmt.Errorf("could not find field in %v matching %v", overlayValue.Type(), field) return fmt.Errorf("could not find field in %v matching %v",
overlayValue.Type(), field)
} }
baseFieldValue := findField(field, baseValue) baseFieldValue := findField(field, baseValue)
if overlayFieldValue.Kind() == reflect.Slice { if overlayFieldValue.Kind() == reflect.Slice {
@ -519,34 +514,49 @@ func LoadConfig(path string) (*Config, error) {
} }
for name, val := range tbl.Fields { for name, val := range tbl.Fields {
subtbl, ok := val.(*ast.Table) subTable, ok := val.(*ast.Table)
if !ok { if !ok {
return nil, errors.New("invalid configuration") return nil, errors.New("invalid configuration")
} }
switch name { switch name {
case "agent": case "agent":
err := c.parseAgent(subtbl) err := c.parseAgent(subTable)
if err != nil { if err != nil {
log.Printf("Could not parse [agent] config\n")
return nil, err return nil, err
} }
case "tags": case "tags":
if err = toml.UnmarshalTable(subtbl, c.Tags); err != nil { if err = toml.UnmarshalTable(subTable, c.Tags); err != nil {
log.Printf("Could not parse [tags] config\n")
return nil, err return nil, err
} }
case "outputs": case "outputs":
for outputName, outputVal := range subtbl.Fields { for outputName, outputVal := range subTable.Fields {
outputSubtbl, ok := outputVal.(*ast.Table) switch outputSubTable := outputVal.(type) {
if !ok { case *ast.Table:
err = c.parseOutput(outputName, outputSubTable, 0)
if err != nil {
log.Printf("Could not parse config for output: %s\n",
outputName)
return nil, err return nil, err
} }
err = c.parseOutput(outputName, outputSubtbl) case []*ast.Table:
for id, t := range outputSubTable {
err = c.parseOutput(outputName, t, id)
if err != nil { if err != nil {
log.Printf("Could not parse config for output: %s\n",
outputName)
return nil, err return nil, err
} }
} }
default: default:
err = c.parsePlugin(name, subtbl) return nil, fmt.Errorf("Unsupported config format: %s",
outputName)
}
}
default:
err = c.parsePlugin(name, subTable)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -579,7 +589,7 @@ func (c *Config) parseAgent(agentAst *ast.Table) error {
} }
// Parse an output config out of the given *ast.Table. // Parse an output config out of the given *ast.Table.
func (c *Config) parseOutput(name string, outputAst *ast.Table) error { func (c *Config) parseOutput(name string, outputAst *ast.Table, id int) error {
c.outputFieldsSet[name] = extractFieldNames(outputAst) c.outputFieldsSet[name] = extractFieldNames(outputAst)
creator, ok := outputs.Outputs[name] creator, ok := outputs.Outputs[name]
if !ok { if !ok {
@ -590,7 +600,7 @@ func (c *Config) parseOutput(name string, outputAst *ast.Table) error {
if err != nil { if err != nil {
return err return err
} }
c.outputs[name] = output c.outputs[fmt.Sprintf("%s-%d", name, id)] = output
return nil return nil
} }