github.com/influxdata/influxdb/v2@v2.7.6/telegraf.go (about)

     1  package influxdb
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"regexp"
     8  
     9  	"github.com/BurntSushi/toml"
    10  	"github.com/influxdata/influxdb/v2/kit/platform"
    11  	"github.com/influxdata/influxdb/v2/kit/platform/errors"
    12  	"github.com/influxdata/influxdb/v2/telegraf/plugins"
    13  	"github.com/influxdata/influxdb/v2/telegraf/plugins/inputs"
    14  	"github.com/influxdata/influxdb/v2/telegraf/plugins/outputs"
    15  )
    16  
    17  const (
    18  	ErrTelegrafConfigInvalidOrgID  = "invalid org ID"                   // ErrTelegrafConfigInvalidOrgID is the error message for a missing or invalid organization ID.
    19  	ErrTelegrafConfigNotFound      = "telegraf configuration not found" // ErrTelegrafConfigNotFound is the error message for a missing telegraf config.
    20  	ErrTelegrafPluginNameUnmatch   = "the telegraf plugin is name %s doesn't match the config %s"
    21  	ErrNoTelegrafPlugins           = "there is no telegraf plugin in the config"
    22  	ErrUnsupportTelegrafPluginType = "unsupported telegraf plugin type %s"
    23  	ErrUnsupportTelegrafPluginName = "unsupported telegraf plugin %s, type %s"
    24  )
    25  
    26  // ops for buckets error and buckets op logs.
    27  var (
    28  	OpFindTelegrafConfigByID = "FindTelegrafConfigByID"
    29  	OpFindTelegrafConfigs    = "FindTelegrafConfigs"
    30  	OpCreateTelegrafConfig   = "CreateTelegrafConfig"
    31  	OpUpdateTelegrafConfig   = "UpdateTelegrafConfig"
    32  	OpDeleteTelegrafConfig   = "DeleteTelegrafConfig"
    33  )
    34  
    35  // TelegrafConfigStore represents a service for managing telegraf config data.
    36  type TelegrafConfigStore interface {
    37  	// FindTelegrafConfigByID returns a single telegraf config by ID.
    38  	FindTelegrafConfigByID(ctx context.Context, id platform.ID) (*TelegrafConfig, error)
    39  
    40  	// FindTelegrafConfigs returns a list of telegraf configs that match filter and the total count of matching telegraf configs.
    41  	// Additional options provide pagination & sorting.
    42  	FindTelegrafConfigs(ctx context.Context, filter TelegrafConfigFilter, opt ...FindOptions) ([]*TelegrafConfig, int, error)
    43  
    44  	// CreateTelegrafConfig creates a new telegraf config and sets b.ID with the new identifier.
    45  	CreateTelegrafConfig(ctx context.Context, tc *TelegrafConfig, userID platform.ID) error
    46  
    47  	// UpdateTelegrafConfig updates a single telegraf config.
    48  	// Returns the new telegraf config after update.
    49  	UpdateTelegrafConfig(ctx context.Context, id platform.ID, tc *TelegrafConfig, userID platform.ID) (*TelegrafConfig, error)
    50  
    51  	// DeleteTelegrafConfig removes a telegraf config by ID.
    52  	DeleteTelegrafConfig(ctx context.Context, id platform.ID) error
    53  }
    54  
    55  // TelegrafConfigFilter represents a set of filter that restrict the returned telegraf configs.
    56  type TelegrafConfigFilter struct {
    57  	OrgID        *platform.ID
    58  	Organization *string
    59  }
    60  
    61  // TelegrafConfig stores telegraf config for one telegraf instance.
    62  type TelegrafConfig struct {
    63  	ID          platform.ID            `json:"id,omitempty"`          // ID of this config object.
    64  	OrgID       platform.ID            `json:"orgID,omitempty"`       // OrgID is the id of the owning organization.
    65  	Name        string                 `json:"name,omitempty"`        // Name of this config object.
    66  	Description string                 `json:"description,omitempty"` // Decription of this config object.
    67  	Config      string                 `json:"config,omitempty"`      // ConfigTOML contains the raw toml config.
    68  	Metadata    map[string]interface{} `json:"metadata,omitempty"`    // Metadata for the config.
    69  }
    70  
    71  var pluginCount = regexp.MustCompilePOSIX(`\[\[(inputs\..*|outputs\..*|aggregators\..*|processors\..*)\]\]`)
    72  
    73  // CountPlugins returns a map of the number of times each plugin is used.
    74  func (tc *TelegrafConfig) CountPlugins() map[string]float64 {
    75  	plugins := map[string]float64{}
    76  	founds := pluginCount.FindAllStringSubmatch(tc.Config, -1)
    77  
    78  	for _, v := range founds {
    79  		if len(v) < 2 {
    80  			continue
    81  		}
    82  		plugins[v[1]]++
    83  	}
    84  
    85  	return plugins
    86  }
    87  
    88  // UnmarshalJSON implement the json.Unmarshaler interface.
    89  // Gets called when reading from the kv db. mostly legacy so loading old/stored configs still work.
    90  // May not remove for a while. Primarily will get hit when user views/downloads config.
    91  func (tc *TelegrafConfig) UnmarshalJSON(b []byte) error {
    92  	tcd := new(telegrafConfigDecode)
    93  
    94  	if err := json.Unmarshal(b, tcd); err != nil {
    95  		return err
    96  	}
    97  
    98  	orgID := tcd.OrgID
    99  	if orgID == nil || !orgID.Valid() {
   100  		orgID = tcd.OrganizationID
   101  	}
   102  
   103  	if tcd.ID != nil {
   104  		tc.ID = *tcd.ID
   105  	}
   106  
   107  	if orgID != nil {
   108  		tc.OrgID = *orgID
   109  	}
   110  
   111  	tc.Name = tcd.Name
   112  	tc.Description = tcd.Description
   113  
   114  	// Prefer new structure; use full toml config.
   115  	tc.Config = tcd.Config
   116  	tc.Metadata = tcd.Metadata
   117  
   118  	if tcd.Plugins != nil {
   119  		// legacy, remove after some moons. or a migration.
   120  		if len(tcd.Plugins) > 0 {
   121  			bkts, conf, err := decodePluginRaw(tcd)
   122  			if err != nil {
   123  				return err
   124  			}
   125  			tc.Config = plugins.AgentConfig + conf
   126  			tc.Metadata = map[string]interface{}{"buckets": bkts}
   127  		} else if c, ok := plugins.GetPlugin("output", "influxdb_v2"); ok {
   128  			// Handles legacy adding of default plugins (agent and output).
   129  			tc.Config = plugins.AgentConfig + c.Config
   130  			tc.Metadata = map[string]interface{}{
   131  				"buckets": []string{},
   132  			}
   133  		}
   134  	} else if tcd.Metadata == nil || len(tcd.Metadata) == 0 {
   135  		// Get buckets from the config.
   136  		m, err := parseMetadata(tc.Config)
   137  		if err != nil {
   138  			return err
   139  		}
   140  
   141  		tc.Metadata = m
   142  	}
   143  
   144  	return nil
   145  }
   146  
   147  type buckets []string
   148  
   149  func (t *buckets) UnmarshalTOML(data interface{}) error {
   150  	dataOk, ok := data.(map[string]interface{})
   151  	if !ok {
   152  		return &errors.Error{
   153  			Code: errors.EEmptyValue,
   154  			Msg:  "no config to get buckets",
   155  		}
   156  	}
   157  	bkts := []string{}
   158  	for tp, ps := range dataOk {
   159  		if tp != "outputs" {
   160  			continue
   161  		}
   162  		plugins, ok := ps.(map[string]interface{})
   163  		if !ok {
   164  			return &errors.Error{
   165  				Code: errors.EEmptyValue,
   166  				Msg:  "no plugins in config to get buckets",
   167  			}
   168  		}
   169  		for name, configDataArray := range plugins {
   170  			if name != "influxdb_v2" {
   171  				continue
   172  			}
   173  			config, ok := configDataArray.([]map[string]interface{})
   174  			if !ok {
   175  				return &errors.Error{
   176  					Code: errors.EEmptyValue,
   177  					Msg:  "influxdb_v2 output has no config",
   178  				}
   179  			}
   180  			for i := range config {
   181  				if b, ok := config[i]["bucket"]; ok {
   182  					bkts = append(bkts, b.(string))
   183  				}
   184  			}
   185  		}
   186  	}
   187  
   188  	*t = bkts
   189  
   190  	return nil
   191  }
   192  
   193  func parseMetadata(cfg string) (map[string]interface{}, error) {
   194  	bs := []string{}
   195  
   196  	this := &buckets{}
   197  	_, err := toml.Decode(cfg, this)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	for _, i := range *this {
   203  		if i != "" {
   204  			bs = append(bs, i)
   205  		}
   206  	}
   207  
   208  	return map[string]interface{}{"buckets": bs}, nil
   209  }
   210  
   211  // return bucket, config, error
   212  func decodePluginRaw(tcd *telegrafConfigDecode) ([]string, string, error) {
   213  	op := "unmarshal telegraf config raw plugin"
   214  	ps := ""
   215  	bucket := []string{}
   216  
   217  	for _, pr := range tcd.Plugins {
   218  		var tpFn func() plugins.Config
   219  		var ok bool
   220  
   221  		switch pr.Type {
   222  		case "input":
   223  			tpFn, ok = availableInputPlugins[pr.Name]
   224  		case "output":
   225  			tpFn, ok = availableOutputPlugins[pr.Name]
   226  		default:
   227  			return nil, "", &errors.Error{
   228  				Code: errors.EInvalid,
   229  				Op:   op,
   230  				Msg:  fmt.Sprintf(ErrUnsupportTelegrafPluginType, pr.Type),
   231  			}
   232  		}
   233  
   234  		if !ok {
   235  			// This removes the validation (and does not create toml) for new "input" plugins
   236  			// but keeps in place the existing behavior for certain "input" plugins
   237  			if pr.Type == "output" {
   238  				return nil, "", &errors.Error{
   239  					Code: errors.EInvalid,
   240  					Op:   op,
   241  					Msg:  fmt.Sprintf(ErrUnsupportTelegrafPluginName, pr.Name, pr.Type),
   242  				}
   243  			}
   244  			continue
   245  		}
   246  
   247  		config := tpFn()
   248  		// if pr.Config if empty, make it a blank obj,
   249  		// so it will still go to the unmarshalling process to validate.
   250  		if pr.Config == nil || len(string(pr.Config)) == 0 {
   251  			pr.Config = []byte("{}")
   252  		}
   253  
   254  		if err := json.Unmarshal(pr.Config, config); err != nil {
   255  			return nil, "", &errors.Error{
   256  				Code: errors.EInvalid,
   257  				Err:  err,
   258  				Op:   op,
   259  			}
   260  		}
   261  
   262  		if pr.Name == "influxdb_v2" {
   263  			if b := config.(*outputs.InfluxDBV2).Bucket; b != "" {
   264  				bucket = []string{b}
   265  			}
   266  		}
   267  
   268  		ps += config.TOML()
   269  
   270  	}
   271  
   272  	return bucket, ps, nil
   273  }
   274  
   275  // telegrafConfigDecode is the helper struct for json decoding. legacy.
   276  type telegrafConfigDecode struct {
   277  	ID             *platform.ID           `json:"id,omitempty"`
   278  	OrganizationID *platform.ID           `json:"organizationID,omitempty"`
   279  	OrgID          *platform.ID           `json:"orgID,omitempty"`
   280  	Name           string                 `json:"name,omitempty"`
   281  	Description    string                 `json:"description,omitempty"`
   282  	Config         string                 `json:"config,omitempty"`
   283  	Plugins        []telegrafPluginDecode `json:"plugins,omitempty"`
   284  	Metadata       map[string]interface{} `json:"metadata,omitempty"`
   285  }
   286  
   287  // telegrafPluginDecode is the helper struct for json decoding. legacy.
   288  type telegrafPluginDecode struct {
   289  	Type        string          `json:"type,omitempty"`        // Type of the plugin.
   290  	Name        string          `json:"name,omitempty"`        // Name of the plugin.
   291  	Alias       string          `json:"alias,omitempty"`       // Alias of the plugin.
   292  	Description string          `json:"description,omitempty"` // Description of the plugin.
   293  	Config      json.RawMessage `json:"config,omitempty"`      // Config is the currently stored plugin configuration.
   294  }
   295  
   296  var availableInputPlugins = map[string](func() plugins.Config){
   297  	"cpu":          func() plugins.Config { return &inputs.CPUStats{} },
   298  	"disk":         func() plugins.Config { return &inputs.DiskStats{} },
   299  	"diskio":       func() plugins.Config { return &inputs.DiskIO{} },
   300  	"docker":       func() plugins.Config { return &inputs.Docker{} },
   301  	"file":         func() plugins.Config { return &inputs.File{} },
   302  	"kernel":       func() plugins.Config { return &inputs.Kernel{} },
   303  	"kubernetes":   func() plugins.Config { return &inputs.Kubernetes{} },
   304  	"logparser":    func() plugins.Config { return &inputs.LogParserPlugin{} },
   305  	"mem":          func() plugins.Config { return &inputs.MemStats{} },
   306  	"net_response": func() plugins.Config { return &inputs.NetResponse{} },
   307  	"net":          func() plugins.Config { return &inputs.NetIOStats{} },
   308  	"nginx":        func() plugins.Config { return &inputs.Nginx{} },
   309  	"processes":    func() plugins.Config { return &inputs.Processes{} },
   310  	"procstat":     func() plugins.Config { return &inputs.Procstat{} },
   311  	"prometheus":   func() plugins.Config { return &inputs.Prometheus{} },
   312  	"redis":        func() plugins.Config { return &inputs.Redis{} },
   313  	"swap":         func() plugins.Config { return &inputs.SwapStats{} },
   314  	"syslog":       func() plugins.Config { return &inputs.Syslog{} },
   315  	"system":       func() plugins.Config { return &inputs.SystemStats{} },
   316  	"tail":         func() plugins.Config { return &inputs.Tail{} },
   317  }
   318  
   319  var availableOutputPlugins = map[string](func() plugins.Config){
   320  	"file":        func() plugins.Config { return &outputs.File{} },
   321  	"influxdb_v2": func() plugins.Config { return &outputs.InfluxDBV2{} },
   322  }