Skip to content

Commit 8b09c90

Browse files
authored
Merge branch 'prometheus:main' into 4xx-error-for-notification-failure
2 parents 0e0697c + 6ef6e68 commit 8b09c90

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1562
-214
lines changed

api/v2/api.go

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323

2424
"github.com/go-kit/log"
2525
"github.com/go-kit/log/level"
26+
"github.com/go-openapi/analysis"
2627
"github.com/go-openapi/loads"
2728
"github.com/go-openapi/runtime/middleware"
2829
"github.com/go-openapi/strfmt"
@@ -101,9 +102,9 @@ func NewAPI(
101102
}
102103

103104
// Load embedded swagger file.
104-
swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
105+
swaggerSpec, swaggerSpecAnalysis, err := getSwaggerSpec()
105106
if err != nil {
106-
return nil, fmt.Errorf("failed to load embedded swagger file: %v", err.Error())
107+
return nil, err
107108
}
108109

109110
// Create new service API.
@@ -113,7 +114,9 @@ func NewAPI(
113114
// the API itself via RoutesHandler. See:
114115
// https://github.com/go-swagger/go-swagger/issues/1779
115116
openAPI.Middleware = func(b middleware.Builder) http.Handler {
116-
return middleware.Spec("", swaggerSpec.Raw(), openAPI.Context().RoutesHandler(b))
117+
// Manually create the context so that we can use the singleton swaggerSpecAnalysis.
118+
swaggerContext := middleware.NewRoutableContextWithAnalyzedSpec(swaggerSpec, swaggerSpecAnalysis, openAPI, nil)
119+
return middleware.Spec("", swaggerSpec.Raw(), swaggerContext.RoutesHandler(b))
117120
}
118121

119122
openAPI.AlertGetAlertsHandler = alert_ops.GetAlertsHandlerFunc(api.getAlertsHandler)
@@ -672,3 +675,33 @@ func parseFilter(filter []string) ([]*labels.Matcher, error) {
672675
}
673676
return matchers, nil
674677
}
678+
679+
var (
680+
swaggerSpecCacheMx sync.Mutex
681+
swaggerSpecCache *loads.Document
682+
swaggerSpecAnalysisCache *analysis.Spec
683+
)
684+
685+
// getSwaggerSpec loads and caches the swagger spec. If a cached version already exists,
686+
// it returns the cached one. The reason why we cache it is because some downstream projects
687+
// (e.g. Grafana Mimir) creates many Alertmanager instances in the same process, so they would
688+
// incur in a significant memory penalty if we would reload the swagger spec each time.
689+
func getSwaggerSpec() (*loads.Document, *analysis.Spec, error) {
690+
swaggerSpecCacheMx.Lock()
691+
defer swaggerSpecCacheMx.Unlock()
692+
693+
// Check if a cached version exists.
694+
if swaggerSpecCache != nil {
695+
return swaggerSpecCache, swaggerSpecAnalysisCache, nil
696+
}
697+
698+
// Load embedded swagger file.
699+
swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
700+
if err != nil {
701+
return nil, nil, fmt.Errorf("failed to load embedded swagger file: %w", err)
702+
}
703+
704+
swaggerSpecCache = swaggerSpec
705+
swaggerSpecAnalysisCache = analysis.New(swaggerSpec.Spec())
706+
return swaggerSpec, swaggerSpecAnalysisCache, nil
707+
}

asset/assets_vfsdata.go

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/alertmanager/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import (
4949
"github.com/prometheus/alertmanager/inhibit"
5050
"github.com/prometheus/alertmanager/nflog"
5151
"github.com/prometheus/alertmanager/notify"
52+
"github.com/prometheus/alertmanager/notify/discord"
5253
"github.com/prometheus/alertmanager/notify/email"
5354
"github.com/prometheus/alertmanager/notify/opsgenie"
5455
"github.com/prometheus/alertmanager/notify/pagerduty"
@@ -173,6 +174,10 @@ func buildReceiverIntegrations(nc *config.Receiver, tmpl *template.Template, log
173174
for i, c := range nc.TelegramConfigs {
174175
add("telegram", i, c, func(l log.Logger) (notify.Notifier, error) { return telegram.New(c, tmpl, l) })
175176
}
177+
for i, c := range nc.DiscordConfigs {
178+
add("discord", i, c, func(l log.Logger) (notify.Notifier, error) { return discord.New(c, tmpl, l) })
179+
}
180+
176181
if errs.Len() > 0 {
177182
return nil, &errs
178183
}

config/config.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,9 @@ func resolveFilepaths(baseDir string, cfg *Config) {
248248
for _, cfg := range receiver.TelegramConfigs {
249249
cfg.HTTPConfig.SetDirectory(baseDir)
250250
}
251+
for _, cfg := range receiver.DiscordConfigs {
252+
cfg.HTTPConfig.SetDirectory(baseDir)
253+
}
251254
}
252255
}
253256

@@ -335,6 +338,10 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
335338
return fmt.Errorf("at most one of opsgenie_api_key & opsgenie_api_key_file must be configured")
336339
}
337340

341+
if c.Global.VictorOpsAPIKey != "" && len(c.Global.VictorOpsAPIKeyFile) > 0 {
342+
return fmt.Errorf("at most one of victorops_api_key & victorops_api_key_file must be configured")
343+
}
344+
338345
if len(c.Global.SMTPAuthPassword) > 0 && len(c.Global.SMTPAuthPasswordFile) > 0 {
339346
return fmt.Errorf("at most one of smtp_auth_password & smtp_auth_password_file must be configured")
340347
}
@@ -476,11 +483,12 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
476483
if !strings.HasSuffix(voc.APIURL.Path, "/") {
477484
voc.APIURL.Path += "/"
478485
}
479-
if voc.APIKey == "" {
480-
if c.Global.VictorOpsAPIKey == "" {
486+
if voc.APIKey == "" && len(voc.APIKeyFile) == 0 {
487+
if c.Global.VictorOpsAPIKey == "" && len(c.Global.VictorOpsAPIKeyFile) == 0 {
481488
return fmt.Errorf("no global VictorOps API Key set")
482489
}
483490
voc.APIKey = c.Global.VictorOpsAPIKey
491+
voc.APIKeyFile = c.Global.VictorOpsAPIKeyFile
484492
}
485493
}
486494
for _, sns := range rcv.SNSConfigs {
@@ -497,6 +505,14 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
497505
telegram.APIUrl = c.Global.TelegramAPIUrl
498506
}
499507
}
508+
for _, discord := range rcv.DiscordConfigs {
509+
if discord.HTTPConfig == nil {
510+
discord.HTTPConfig = c.Global.HTTPConfig
511+
}
512+
if discord.WebhookURL == nil {
513+
return fmt.Errorf("no discord webhook URL provided")
514+
}
515+
}
500516

501517
names[rcv.Name] = struct{}{}
502518
}
@@ -718,6 +734,7 @@ type GlobalConfig struct {
718734
WeChatAPICorpID string `yaml:"wechat_api_corp_id,omitempty" json:"wechat_api_corp_id,omitempty"`
719735
VictorOpsAPIURL *URL `yaml:"victorops_api_url,omitempty" json:"victorops_api_url,omitempty"`
720736
VictorOpsAPIKey Secret `yaml:"victorops_api_key,omitempty" json:"victorops_api_key,omitempty"`
737+
VictorOpsAPIKeyFile string `yaml:"victorops_api_key_file,omitempty" json:"victorops_api_key_file,omitempty"`
721738
TelegramAPIUrl *URL `yaml:"telegram_api_url,omitempty" json:"telegram_api_url,omitempty"`
722739
}
723740

@@ -850,6 +867,7 @@ type Receiver struct {
850867
// A unique identifier for this receiver.
851868
Name string `yaml:"name" json:"name"`
852869

870+
DiscordConfigs []*DiscordConfig `yaml:"discord_configs,omitempty" json:"discord_configs,omitempty"`
853871
EmailConfigs []*EmailConfig `yaml:"email_configs,omitempty" json:"email_configs,omitempty"`
854872
PagerdutyConfigs []*PagerdutyConfig `yaml:"pagerduty_configs,omitempty" json:"pagerduty_configs,omitempty"`
855873
SlackConfigs []*SlackConfig `yaml:"slack_configs,omitempty" json:"slack_configs,omitempty"`

config/config_test.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,11 +1016,38 @@ func TestVictorOpsDefaultAPIKey(t *testing.T) {
10161016
}
10171017

10181018
defaultKey := conf.Global.VictorOpsAPIKey
1019+
overrideKey := Secret("qwe456")
10191020
if defaultKey != conf.Receivers[0].VictorOpsConfigs[0].APIKey {
10201021
t.Fatalf("Invalid victorops key: %s\nExpected: %s", conf.Receivers[0].VictorOpsConfigs[0].APIKey, defaultKey)
10211022
}
1022-
if defaultKey == conf.Receivers[1].VictorOpsConfigs[0].APIKey {
1023-
t.Errorf("Invalid victorops key: %s\nExpected: %s", conf.Receivers[0].VictorOpsConfigs[0].APIKey, "qwe456")
1023+
if overrideKey != conf.Receivers[1].VictorOpsConfigs[0].APIKey {
1024+
t.Errorf("Invalid victorops key: %s\nExpected: %s", conf.Receivers[0].VictorOpsConfigs[0].APIKey, string(overrideKey))
1025+
}
1026+
}
1027+
1028+
func TestVictorOpsDefaultAPIKeyFile(t *testing.T) {
1029+
conf, err := LoadFile("testdata/conf.victorops-default-apikey-file.yml")
1030+
if err != nil {
1031+
t.Fatalf("Error parsing %s: %s", "testdata/conf.victorops-default-apikey-file.yml", err)
1032+
}
1033+
1034+
defaultKey := conf.Global.VictorOpsAPIKeyFile
1035+
overrideKey := "/override_file"
1036+
if defaultKey != conf.Receivers[0].VictorOpsConfigs[0].APIKeyFile {
1037+
t.Fatalf("Invalid VictorOps key_file: %s\nExpected: %s", conf.Receivers[0].VictorOpsConfigs[0].APIKeyFile, defaultKey)
1038+
}
1039+
if overrideKey != conf.Receivers[1].VictorOpsConfigs[0].APIKeyFile {
1040+
t.Errorf("Invalid VictorOps key_file: %s\nExpected: %s", conf.Receivers[0].VictorOpsConfigs[0].APIKeyFile, overrideKey)
1041+
}
1042+
}
1043+
1044+
func TestVictorOpsBothAPIKeyAndFile(t *testing.T) {
1045+
_, err := LoadFile("testdata/conf.victorops-both-file-and-apikey.yml")
1046+
if err == nil {
1047+
t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.victorops-both-file-and-apikey.yml", err)
1048+
}
1049+
if err.Error() != "at most one of victorops_api_key & victorops_api_key_file must be configured" {
1050+
t.Errorf("Expected: %s\nGot: %s", "at most one of victorops_api_key & victorops_api_key_file must be configured", err.Error())
10241051
}
10251052
}
10261053

config/notifiers.go

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ var (
3333
},
3434
}
3535

36+
// DefaultDiscordConfig defines default values for Discord configurations.
37+
DefaultDiscordConfig = DiscordConfig{
38+
NotifierConfig: NotifierConfig{
39+
VSendResolved: true,
40+
},
41+
Title: `{{ template "discord.default.title" . }}`,
42+
Message: `{{ template "discord.default.message" . }}`,
43+
}
44+
3645
// DefaultEmailConfig defines default values for Email configurations.
3746
DefaultEmailConfig = EmailConfig{
3847
NotifierConfig: NotifierConfig{
@@ -157,6 +166,24 @@ func (nc *NotifierConfig) SendResolved() bool {
157166
return nc.VSendResolved
158167
}
159168

169+
// DiscordConfig configures notifications via Discord.
170+
type DiscordConfig struct {
171+
NotifierConfig `yaml:",inline" json:",inline"`
172+
173+
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`
174+
WebhookURL *SecretURL `yaml:"webhook_url,omitempty" json:"webhook_url,omitempty"`
175+
176+
Title string `yaml:"title,omitempty" json:"title,omitempty"`
177+
Message string `yaml:"message,omitempty" json:"message,omitempty"`
178+
}
179+
180+
// UnmarshalYAML implements the yaml.Unmarshaler interface.
181+
func (c *DiscordConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
182+
*c = DefaultDiscordConfig
183+
type plain DiscordConfig
184+
return unmarshal((*plain)(c))
185+
}
186+
160187
// EmailConfig configures notifications via mail.
161188
type EmailConfig struct {
162189
NotifierConfig `yaml:",inline" json:",inline"`
@@ -208,19 +235,22 @@ type PagerdutyConfig struct {
208235

209236
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`
210237

211-
ServiceKey Secret `yaml:"service_key,omitempty" json:"service_key,omitempty"`
212-
RoutingKey Secret `yaml:"routing_key,omitempty" json:"routing_key,omitempty"`
213-
URL *URL `yaml:"url,omitempty" json:"url,omitempty"`
214-
Client string `yaml:"client,omitempty" json:"client,omitempty"`
215-
ClientURL string `yaml:"client_url,omitempty" json:"client_url,omitempty"`
216-
Description string `yaml:"description,omitempty" json:"description,omitempty"`
217-
Details map[string]string `yaml:"details,omitempty" json:"details,omitempty"`
218-
Images []PagerdutyImage `yaml:"images,omitempty" json:"images,omitempty"`
219-
Links []PagerdutyLink `yaml:"links,omitempty" json:"links,omitempty"`
220-
Severity string `yaml:"severity,omitempty" json:"severity,omitempty"`
221-
Class string `yaml:"class,omitempty" json:"class,omitempty"`
222-
Component string `yaml:"component,omitempty" json:"component,omitempty"`
223-
Group string `yaml:"group,omitempty" json:"group,omitempty"`
238+
ServiceKey Secret `yaml:"service_key,omitempty" json:"service_key,omitempty"`
239+
ServiceKeyFile string `yaml:"service_key_file,omitempty" json:"service_key_file,omitempty"`
240+
RoutingKey Secret `yaml:"routing_key,omitempty" json:"routing_key,omitempty"`
241+
RoutingKeyFile string `yaml:"routing_key_file,omitempty" json:"routing_key_file,omitempty"`
242+
URL *URL `yaml:"url,omitempty" json:"url,omitempty"`
243+
Client string `yaml:"client,omitempty" json:"client,omitempty"`
244+
ClientURL string `yaml:"client_url,omitempty" json:"client_url,omitempty"`
245+
Description string `yaml:"description,omitempty" json:"description,omitempty"`
246+
Details map[string]string `yaml:"details,omitempty" json:"details,omitempty"`
247+
Images []PagerdutyImage `yaml:"images,omitempty" json:"images,omitempty"`
248+
Links []PagerdutyLink `yaml:"links,omitempty" json:"links,omitempty"`
249+
Source string `yaml:"source,omitempty" json:"source,omitempty"`
250+
Severity string `yaml:"severity,omitempty" json:"severity,omitempty"`
251+
Class string `yaml:"class,omitempty" json:"class,omitempty"`
252+
Component string `yaml:"component,omitempty" json:"component,omitempty"`
253+
Group string `yaml:"group,omitempty" json:"group,omitempty"`
224254
}
225255

226256
// PagerdutyLink is a link
@@ -243,12 +273,21 @@ func (c *PagerdutyConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
243273
if err := unmarshal((*plain)(c)); err != nil {
244274
return err
245275
}
246-
if c.RoutingKey == "" && c.ServiceKey == "" {
276+
if c.RoutingKey == "" && c.ServiceKey == "" && c.RoutingKeyFile == "" && c.ServiceKeyFile == "" {
247277
return fmt.Errorf("missing service or routing key in PagerDuty config")
248278
}
279+
if len(c.RoutingKey) > 0 && len(c.RoutingKeyFile) > 0 {
280+
return fmt.Errorf("at most one of routing_key & routing_key_file must be configured")
281+
}
282+
if len(c.ServiceKey) > 0 && len(c.ServiceKeyFile) > 0 {
283+
return fmt.Errorf("at most one of service_key & service_key_file must be configured")
284+
}
249285
if c.Details == nil {
250286
c.Details = make(map[string]string)
251287
}
288+
if c.Source == "" {
289+
c.Source = c.Client
290+
}
252291
for k, v := range DefaultPagerdutyDetails {
253292
if _, ok := c.Details[k]; !ok {
254293
c.Details[k] = v
@@ -528,7 +567,7 @@ type VictorOpsConfig struct {
528567
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`
529568

530569
APIKey Secret `yaml:"api_key,omitempty" json:"api_key,omitempty"`
531-
APIKeyFile Secret `yaml:"api_key_file,omitempty" json:"api_key_file,omitempty"`
570+
APIKeyFile string `yaml:"api_key_file,omitempty" json:"api_key_file,omitempty"`
532571
APIURL *URL `yaml:"api_url" json:"api_url"`
533572
RoutingKey string `yaml:"routing_key" json:"routing_key"`
534573
MessageType string `yaml:"message_type" json:"message_type"`
@@ -548,6 +587,9 @@ func (c *VictorOpsConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
548587
if c.RoutingKey == "" {
549588
return fmt.Errorf("missing Routing key in VictorOps config")
550589
}
590+
if c.APIKey != "" && len(c.APIKeyFile) > 0 {
591+
return fmt.Errorf("at most one of api_key & api_key_file must be configured")
592+
}
551593

552594
reservedFields := []string{"routing_key", "message_type", "state_message", "entity_display_name", "monitoring_tool", "entity_id", "entity_state"}
553595

0 commit comments

Comments
 (0)