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 }