github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/ruler/base/notifier.go (about)

     1  package base
     2  
     3  import (
     4  	"context"
     5  	"flag"
     6  	"fmt"
     7  	"net/url"
     8  	"regexp"
     9  	"strings"
    10  	"sync"
    11  
    12  	gklog "github.com/go-kit/log"
    13  	"github.com/go-kit/log/level"
    14  	"github.com/grafana/dskit/crypto/tls"
    15  	config_util "github.com/prometheus/common/config"
    16  	"github.com/prometheus/common/model"
    17  	"github.com/prometheus/prometheus/config"
    18  	"github.com/prometheus/prometheus/discovery"
    19  	"github.com/prometheus/prometheus/discovery/dns"
    20  	"github.com/prometheus/prometheus/notifier"
    21  
    22  	"github.com/grafana/loki/pkg/util"
    23  )
    24  
    25  type NotifierConfig struct {
    26  	TLS        tls.ClientConfig `yaml:",inline"`
    27  	BasicAuth  util.BasicAuth   `yaml:",inline"`
    28  	HeaderAuth util.HeaderAuth  `yaml:",inline"`
    29  }
    30  
    31  func (cfg *NotifierConfig) RegisterFlags(f *flag.FlagSet) {
    32  	cfg.TLS.RegisterFlagsWithPrefix("ruler.alertmanager-client", f)
    33  	cfg.BasicAuth.RegisterFlagsWithPrefix("ruler.alertmanager-client.", f)
    34  	cfg.HeaderAuth.RegisterFlagsWithPrefix("ruler.alertmanager-client.", f)
    35  }
    36  
    37  // rulerNotifier bundles a notifier.Manager together with an associated
    38  // Alertmanager service discovery manager and handles the lifecycle
    39  // of both actors.
    40  type rulerNotifier struct {
    41  	notifier  *notifier.Manager
    42  	sdCancel  context.CancelFunc
    43  	sdManager *discovery.Manager
    44  	wg        sync.WaitGroup
    45  	logger    gklog.Logger
    46  }
    47  
    48  func newRulerNotifier(o *notifier.Options, l gklog.Logger) *rulerNotifier {
    49  	sdCtx, sdCancel := context.WithCancel(context.Background())
    50  	return &rulerNotifier{
    51  		notifier:  notifier.NewManager(o, l),
    52  		sdCancel:  sdCancel,
    53  		sdManager: discovery.NewManager(sdCtx, l),
    54  		logger:    l,
    55  	}
    56  }
    57  
    58  // run starts the notifier. This function doesn't block and returns immediately.
    59  func (rn *rulerNotifier) run() {
    60  	rn.wg.Add(2)
    61  	go func() {
    62  		if err := rn.sdManager.Run(); err != nil {
    63  			level.Error(rn.logger).Log("msg", "error starting notifier discovery manager", "err", err)
    64  		}
    65  		rn.wg.Done()
    66  	}()
    67  	go func() {
    68  		rn.notifier.Run(rn.sdManager.SyncCh())
    69  		rn.wg.Done()
    70  	}()
    71  }
    72  
    73  func (rn *rulerNotifier) applyConfig(cfg *config.Config) error {
    74  	if err := rn.notifier.ApplyConfig(cfg); err != nil {
    75  		return err
    76  	}
    77  
    78  	sdCfgs := make(map[string]discovery.Configs)
    79  	for k, v := range cfg.AlertingConfig.AlertmanagerConfigs.ToMap() {
    80  		sdCfgs[k] = v.ServiceDiscoveryConfigs
    81  	}
    82  	return rn.sdManager.ApplyConfig(sdCfgs)
    83  }
    84  
    85  func (rn *rulerNotifier) stop() {
    86  	rn.sdCancel()
    87  	rn.notifier.Stop()
    88  	rn.wg.Wait()
    89  }
    90  
    91  // Builds a Prometheus config.Config from a ruler.Config with just the required
    92  // options to configure notifications to Alertmanager.
    93  func buildNotifierConfig(rulerConfig *Config) (*config.Config, error) {
    94  	amURLs := strings.Split(rulerConfig.AlertmanagerURL, ",")
    95  	validURLs := make([]*url.URL, 0, len(amURLs))
    96  
    97  	srvDNSregexp := regexp.MustCompile(`^_.+._.+`)
    98  	for _, h := range amURLs {
    99  		url, err := url.Parse(h)
   100  		if err != nil {
   101  			return nil, err
   102  		}
   103  
   104  		if url.String() == "" {
   105  			continue
   106  		}
   107  
   108  		// Given we only support SRV lookups as part of service discovery, we need to ensure
   109  		// hosts provided follow this specification: _service._proto.name
   110  		// e.g. _http._tcp.alertmanager.com
   111  		if rulerConfig.AlertmanagerDiscovery && !srvDNSregexp.MatchString(url.Host) {
   112  			return nil, fmt.Errorf("when alertmanager-discovery is on, host name must be of the form _portname._tcp.service.fqdn (is %q)", url.Host)
   113  		}
   114  
   115  		validURLs = append(validURLs, url)
   116  	}
   117  
   118  	if len(validURLs) == 0 {
   119  		return &config.Config{}, nil
   120  	}
   121  
   122  	apiVersion := config.AlertmanagerAPIVersionV1
   123  	if rulerConfig.AlertmanangerEnableV2API {
   124  		apiVersion = config.AlertmanagerAPIVersionV2
   125  	}
   126  
   127  	amConfigs := make([]*config.AlertmanagerConfig, 0, len(validURLs))
   128  	for _, url := range validURLs {
   129  		amConfigs = append(amConfigs, amConfigFromURL(rulerConfig, url, apiVersion))
   130  	}
   131  
   132  	promConfig := &config.Config{
   133  		GlobalConfig: config.GlobalConfig{
   134  			ExternalLabels: rulerConfig.ExternalLabels,
   135  		},
   136  		AlertingConfig: config.AlertingConfig{
   137  			AlertRelabelConfigs: rulerConfig.AlertRelabelConfigs,
   138  			AlertmanagerConfigs: amConfigs,
   139  		},
   140  	}
   141  
   142  	return promConfig, nil
   143  }
   144  
   145  func amConfigFromURL(rulerConfig *Config, url *url.URL, apiVersion config.AlertmanagerAPIVersion) *config.AlertmanagerConfig {
   146  	var sdConfig discovery.Configs
   147  	if rulerConfig.AlertmanagerDiscovery {
   148  		sdConfig = discovery.Configs{
   149  			&dns.SDConfig{
   150  				Names:           []string{url.Host},
   151  				RefreshInterval: model.Duration(rulerConfig.AlertmanagerRefreshInterval),
   152  				Type:            "SRV",
   153  				Port:            0, // Ignored, because of SRV.
   154  			},
   155  		}
   156  
   157  	} else {
   158  		sdConfig = discovery.Configs{
   159  			discovery.StaticConfig{
   160  				{
   161  					Targets: []model.LabelSet{{model.AddressLabel: model.LabelValue(url.Host)}},
   162  				},
   163  			},
   164  		}
   165  	}
   166  
   167  	amConfig := &config.AlertmanagerConfig{
   168  		APIVersion:              apiVersion,
   169  		Scheme:                  url.Scheme,
   170  		PathPrefix:              url.Path,
   171  		Timeout:                 model.Duration(rulerConfig.NotificationTimeout),
   172  		ServiceDiscoveryConfigs: sdConfig,
   173  		HTTPClientConfig: config_util.HTTPClientConfig{
   174  			TLSConfig: config_util.TLSConfig{
   175  				CAFile:             rulerConfig.Notifier.TLS.CAPath,
   176  				CertFile:           rulerConfig.Notifier.TLS.CertPath,
   177  				KeyFile:            rulerConfig.Notifier.TLS.KeyPath,
   178  				InsecureSkipVerify: rulerConfig.Notifier.TLS.InsecureSkipVerify,
   179  				ServerName:         rulerConfig.Notifier.TLS.ServerName,
   180  			},
   181  		},
   182  	}
   183  
   184  	// Check the URL for basic authentication information first
   185  	if url.User != nil {
   186  		amConfig.HTTPClientConfig.BasicAuth = &config_util.BasicAuth{
   187  			Username: url.User.Username(),
   188  		}
   189  
   190  		if password, isSet := url.User.Password(); isSet {
   191  			amConfig.HTTPClientConfig.BasicAuth.Password = config_util.Secret(password)
   192  		}
   193  	}
   194  
   195  	// Override URL basic authentication configs with hard coded config values if present
   196  	if rulerConfig.Notifier.BasicAuth.IsEnabled() {
   197  		amConfig.HTTPClientConfig.BasicAuth = &config_util.BasicAuth{
   198  			Username: rulerConfig.Notifier.BasicAuth.Username,
   199  			Password: config_util.Secret(rulerConfig.Notifier.BasicAuth.Password),
   200  		}
   201  	}
   202  
   203  	if rulerConfig.Notifier.HeaderAuth.IsEnabled() {
   204  		if rulerConfig.Notifier.HeaderAuth.Credentials != "" {
   205  			amConfig.HTTPClientConfig.Authorization = &config_util.Authorization{
   206  				Type:        rulerConfig.Notifier.HeaderAuth.Type,
   207  				Credentials: config_util.Secret(rulerConfig.Notifier.HeaderAuth.Credentials),
   208  			}
   209  		} else if rulerConfig.Notifier.HeaderAuth.CredentialsFile != "" {
   210  			amConfig.HTTPClientConfig.Authorization = &config_util.Authorization{
   211  				Type:            rulerConfig.Notifier.HeaderAuth.Type,
   212  				CredentialsFile: rulerConfig.Notifier.HeaderAuth.CredentialsFile,
   213  			}
   214  
   215  		}
   216  	}
   217  
   218  	return amConfig
   219  }