categraf/inputs/prometheus/consul.go

178 lines
4.9 KiB
Go

package prometheus
import (
"bytes"
"fmt"
"log"
"net/url"
"strings"
"text/template"
"flashcat.cloud/categraf/config"
"github.com/hashicorp/consul/api"
)
type ConsulConfig struct {
// Address of the Consul agent. The address must contain a hostname or an IP address
// and optionally a port (format: "host:port").
Enabled bool `toml:"enabled"`
Agent string `toml:"agent"`
Queries []*ConsulQuery `toml:"query"`
Catalog *api.Catalog `toml:"-"`
}
// One Consul service discovery query
type ConsulQuery struct {
// A name of the searched services (not ID)
ServiceName string `toml:"name"`
// A tag of the searched services
ServiceTag string `toml:"tag"`
// A DC of the searched services
ServiceDc string `toml:"dc"`
// A template URL of the Prometheus gathering interface. The hostname part
// of the URL will be replaced by discovered address and port.
ServiceURL string `toml:"url"`
// Extra tags to add to metrics found in Consul
ServiceExtraTags map[string]string `toml:"tags"`
serviceURLTemplate *template.Template
serviceExtraTagsTemplate map[string]*template.Template
}
func (ins *Instance) InitConsulClient() error {
consulAPIConfig := api.DefaultConfig()
if ins.ConsulConfig.Agent != "" {
consulAPIConfig.Address = ins.ConsulConfig.Agent
}
// Parse the template for metrics URL, drop queries with template parse errors
for i := range ins.ConsulConfig.Queries {
serviceURLTemplate, err := template.New("URL").Parse(ins.ConsulConfig.Queries[i].ServiceURL)
if err != nil {
return fmt.Errorf("failed to parse the Consul query URL template (%s): %s", ins.ConsulConfig.Queries[i].ServiceURL, err)
}
ins.ConsulConfig.Queries[i].serviceURLTemplate = serviceURLTemplate
// Allow to use join function in tags
templateFunctions := template.FuncMap{"join": strings.Join}
// Parse the tag value templates
ins.ConsulConfig.Queries[i].serviceExtraTagsTemplate = make(map[string]*template.Template)
for tagName, tagTemplateString := range ins.ConsulConfig.Queries[i].ServiceExtraTags {
tagTemplate, err := template.New(tagName).Funcs(templateFunctions).Parse(tagTemplateString)
if err != nil {
return fmt.Errorf("failed to parse the Consul query Extra Tag template (%s): %s", tagTemplateString, err)
}
ins.ConsulConfig.Queries[i].serviceExtraTagsTemplate[tagName] = tagTemplate
}
}
// Prevent memory leak by erasing truncated values
// for j := i; j < len(ins.ConsulConfig.Queries); j++ {
// ins.ConsulConfig.Queries[j] = nil
// }
// ins.ConsulConfig.Queries = ins.ConsulConfig.Queries[:i]
consul, err := api.NewClient(consulAPIConfig)
if err != nil {
return fmt.Errorf("failed to connect the Consul agent(%s): %v", consulAPIConfig.Address, err)
}
ins.ConsulConfig.Catalog = consul.Catalog()
return nil
}
func (ins *Instance) UrlsFromConsul() ([]ScrapeUrl, error) {
if !ins.ConsulConfig.Enabled {
return []ScrapeUrl{}, nil
}
if config.Config.DebugMode {
log.Println("D! get urls from consul:", ins.ConsulConfig.Agent)
}
urlset := map[string]struct{}{}
var returls []ScrapeUrl
for _, q := range ins.ConsulConfig.Queries {
queryOptions := api.QueryOptions{}
if q.ServiceDc != "" {
queryOptions.Datacenter = q.ServiceDc
}
// Request services from Consul
consulServices, _, err := ins.ConsulConfig.Catalog.Service(q.ServiceName, q.ServiceTag, &queryOptions)
if err != nil {
return nil, err
}
if len(consulServices) == 0 {
if config.Config.DebugMode {
log.Println("D! query consul did not find any instances, service:", q.ServiceName, " tag:", q.ServiceTag)
}
continue
}
if config.Config.DebugMode {
log.Println("D! query consul found", len(consulServices), "instances, service:", q.ServiceName, " tag:", q.ServiceTag)
}
for _, consulService := range consulServices {
su, err := ins.getConsulServiceURL(q, consulService)
if err != nil {
return nil, fmt.Errorf("unable to get scrape URLs from Consul for Service (%s, %s): %s", q.ServiceName, q.ServiceTag, err)
}
if _, has := urlset[su.URL.String()]; has {
continue
}
urlset[su.URL.String()] = struct{}{}
returls = append(returls, *su)
}
}
return returls, nil
}
func (ins *Instance) getConsulServiceURL(q *ConsulQuery, s *api.CatalogService) (*ScrapeUrl, error) {
var buffer bytes.Buffer
buffer.Reset()
err := q.serviceURLTemplate.Execute(&buffer, s)
if err != nil {
return nil, err
}
serviceURL, err := url.Parse(buffer.String())
if err != nil {
return nil, err
}
extraTags := make(map[string]string)
for tagName, tagTemplate := range q.serviceExtraTagsTemplate {
buffer.Reset()
err = tagTemplate.Execute(&buffer, s)
if err != nil {
return nil, err
}
extraTags[tagName] = buffer.String()
}
if config.Config.DebugMode {
log.Println("D! found consul service:", serviceURL.String())
}
return &ScrapeUrl{
URL: serviceURL,
Tags: extraTags,
}, nil
}
type ScrapeUrl struct {
URL *url.URL
Tags map[string]string
}