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 }