mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-23 12:31:07 +01:00
3c02937a34
'any' type is supported starting from Go1.18. Let's consistently use it instead of 'interface{}' type across the code base, since `any` is easier to read than 'interface{}'.
203 lines
6.6 KiB
Go
203 lines
6.6 KiB
Go
package notifier
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"gopkg.in/yaml.v2"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/consul"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/dns"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
|
)
|
|
|
|
// Config contains list of supported configuration settings
|
|
// for Notifier
|
|
type Config struct {
|
|
// Scheme defines the HTTP scheme for Notifier address
|
|
Scheme string `yaml:"scheme,omitempty"`
|
|
// PathPrefix is added to URL path before adding alertManagerPath value
|
|
PathPrefix string `yaml:"path_prefix,omitempty"`
|
|
|
|
// ConsulSDConfigs contains list of settings for service discovery via Consul
|
|
// see https://prometheus.io/docs/prometheus/latest/configuration/configuration/#consul_sd_config
|
|
ConsulSDConfigs []consul.SDConfig `yaml:"consul_sd_configs,omitempty"`
|
|
// DNSSDConfigs contains list of settings for service discovery via DNS.
|
|
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#dns_sd_config
|
|
DNSSDConfigs []dns.SDConfig `yaml:"dns_sd_configs,omitempty"`
|
|
|
|
// StaticConfigs contains list of static targets
|
|
StaticConfigs []StaticConfig `yaml:"static_configs,omitempty"`
|
|
|
|
// HTTPClientConfig contains HTTP configuration for Notifier clients
|
|
HTTPClientConfig promauth.HTTPClientConfig `yaml:",inline"`
|
|
// RelabelConfigs contains list of relabeling rules for entities discovered via SD
|
|
RelabelConfigs []promrelabel.RelabelConfig `yaml:"relabel_configs,omitempty"`
|
|
// AlertRelabelConfigs contains list of relabeling rules alert labels
|
|
AlertRelabelConfigs []promrelabel.RelabelConfig `yaml:"alert_relabel_configs,omitempty"`
|
|
// The timeout used when sending alerts.
|
|
Timeout *promutils.Duration `yaml:"timeout,omitempty"`
|
|
|
|
// Checksum stores the hash of yaml definition for the config.
|
|
// May be used to detect any changes to the config file.
|
|
Checksum string
|
|
|
|
// Catches all undefined fields and must be empty after parsing.
|
|
XXX map[string]any `yaml:",inline"`
|
|
|
|
// This is set to the directory from where the config has been loaded.
|
|
baseDir string
|
|
|
|
// stores already parsed RelabelConfigs object
|
|
parsedRelabelConfigs *promrelabel.ParsedConfigs
|
|
// stores already parsed AlertRelabelConfigs object
|
|
parsedAlertRelabelConfigs *promrelabel.ParsedConfigs
|
|
}
|
|
|
|
// StaticConfig contains list of static targets in the following form:
|
|
//
|
|
// targets:
|
|
// [ - '<host>' ]
|
|
type StaticConfig struct {
|
|
Targets []string `yaml:"targets"`
|
|
// HTTPClientConfig contains HTTP configuration for the Targets
|
|
HTTPClientConfig promauth.HTTPClientConfig `yaml:",inline"`
|
|
}
|
|
|
|
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
|
func (cfg *Config) UnmarshalYAML(unmarshal func(any) error) error {
|
|
type config Config
|
|
if err := unmarshal((*config)(cfg)); err != nil {
|
|
return err
|
|
}
|
|
if cfg.Scheme == "" {
|
|
cfg.Scheme = "http"
|
|
}
|
|
if cfg.Timeout.Duration() == 0 {
|
|
cfg.Timeout = promutils.NewDuration(time.Second * 10)
|
|
}
|
|
rCfg, err := promrelabel.ParseRelabelConfigs(cfg.RelabelConfigs)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse relabeling config: %w", err)
|
|
}
|
|
cfg.parsedRelabelConfigs = rCfg
|
|
arCfg, err := promrelabel.ParseRelabelConfigs(cfg.AlertRelabelConfigs)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse alert relabeling config: %w", err)
|
|
}
|
|
cfg.parsedAlertRelabelConfigs = arCfg
|
|
|
|
b, err := yaml.Marshal(cfg)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal configuration for checksum: %w", err)
|
|
}
|
|
h := md5.New()
|
|
h.Write(b)
|
|
cfg.Checksum = fmt.Sprintf("%x", h.Sum(nil))
|
|
return nil
|
|
}
|
|
|
|
func parseConfig(path string) (*Config, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error reading config file: %w", err)
|
|
}
|
|
var cfg *Config
|
|
err = yaml.Unmarshal(data, &cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(cfg.XXX) > 0 {
|
|
var keys []string
|
|
for k := range cfg.XXX {
|
|
keys = append(keys, k)
|
|
}
|
|
return nil, fmt.Errorf("unknown fields in %s", strings.Join(keys, ", "))
|
|
}
|
|
absPath, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot obtain abs path for %q: %w", path, err)
|
|
}
|
|
cfg.baseDir = filepath.Dir(absPath)
|
|
return cfg, nil
|
|
}
|
|
|
|
func parseLabels(target string, metaLabels *promutils.Labels, cfg *Config) (string, *promutils.Labels, error) {
|
|
labels := mergeLabels(target, metaLabels, cfg)
|
|
labels.Labels = cfg.parsedRelabelConfigs.Apply(labels.Labels, 0)
|
|
labels.RemoveMetaLabels()
|
|
labels.Sort()
|
|
// Remove references to already deleted labels, so GC could clean strings for label name and label value past len(labels).
|
|
// This should reduce memory usage when relabeling creates big number of temporary labels with long names and/or values.
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/825 for details.
|
|
labels = labels.Clone()
|
|
|
|
if labels.Len() == 0 {
|
|
return "", nil, nil
|
|
}
|
|
scheme := labels.Get("__scheme__")
|
|
if len(scheme) == 0 {
|
|
scheme = "http"
|
|
}
|
|
alertsPath := labels.Get("__alerts_path__")
|
|
if !strings.HasPrefix(alertsPath, "/") {
|
|
alertsPath = "/" + alertsPath
|
|
}
|
|
address := labels.Get("__address__")
|
|
if len(address) == 0 {
|
|
return "", nil, nil
|
|
}
|
|
address = addMissingPort(scheme, address)
|
|
u := fmt.Sprintf("%s://%s%s", scheme, address, alertsPath)
|
|
if _, err := url.Parse(u); err != nil {
|
|
return "", nil, fmt.Errorf("invalid url %q for scheme=%q (%q), target=%q, metrics_path=%q (%q): %w",
|
|
u, cfg.Scheme, scheme, target, address, alertsPath, err)
|
|
}
|
|
return u, labels, nil
|
|
}
|
|
|
|
func addMissingPort(scheme, target string) string {
|
|
if strings.Contains(target, ":") {
|
|
return target
|
|
}
|
|
if scheme == "https" {
|
|
target += ":443"
|
|
} else {
|
|
target += ":80"
|
|
}
|
|
return target
|
|
}
|
|
|
|
func mergeLabels(target string, metaLabels *promutils.Labels, cfg *Config) *promutils.Labels {
|
|
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config
|
|
m := promutils.NewLabels(3 + metaLabels.Len())
|
|
address := target
|
|
scheme := cfg.Scheme
|
|
alertsPath := path.Join("/", cfg.PathPrefix, alertManagerPath)
|
|
// try to extract optional scheme and alertsPath from __address__.
|
|
if strings.HasPrefix(address, "http://") {
|
|
scheme = "http"
|
|
address = address[len("http://"):]
|
|
} else if strings.HasPrefix(address, "https://") {
|
|
scheme = "https"
|
|
address = address[len("https://"):]
|
|
}
|
|
if n := strings.IndexByte(address, '/'); n >= 0 {
|
|
alertsPath = address[n:]
|
|
address = address[:n]
|
|
}
|
|
m.Add("__address__", address)
|
|
m.Add("__scheme__", scheme)
|
|
m.Add("__alerts_path__", alertsPath)
|
|
m.AddFrom(metaLabels)
|
|
return m
|
|
}
|