Skip to content

Commit

Permalink
Merge branch 'prometheus:main' into 4xx-error-for-notification-failure
Browse files Browse the repository at this point in the history
  • Loading branch information
qinxx108 authored Nov 16, 2022
2 parents 0e0697c + 6ef6e68 commit 8b09c90
Show file tree
Hide file tree
Showing 48 changed files with 1,562 additions and 214 deletions.
39 changes: 36 additions & 3 deletions api/v2/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/go-openapi/analysis"
"github.com/go-openapi/loads"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
Expand Down Expand Up @@ -101,9 +102,9 @@ func NewAPI(
}

// Load embedded swagger file.
swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
swaggerSpec, swaggerSpecAnalysis, err := getSwaggerSpec()
if err != nil {
return nil, fmt.Errorf("failed to load embedded swagger file: %v", err.Error())
return nil, err
}

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

openAPI.AlertGetAlertsHandler = alert_ops.GetAlertsHandlerFunc(api.getAlertsHandler)
Expand Down Expand Up @@ -672,3 +675,33 @@ func parseFilter(filter []string) ([]*labels.Matcher, error) {
}
return matchers, nil
}

var (
swaggerSpecCacheMx sync.Mutex
swaggerSpecCache *loads.Document
swaggerSpecAnalysisCache *analysis.Spec
)

// getSwaggerSpec loads and caches the swagger spec. If a cached version already exists,
// it returns the cached one. The reason why we cache it is because some downstream projects
// (e.g. Grafana Mimir) creates many Alertmanager instances in the same process, so they would
// incur in a significant memory penalty if we would reload the swagger spec each time.
func getSwaggerSpec() (*loads.Document, *analysis.Spec, error) {
swaggerSpecCacheMx.Lock()
defer swaggerSpecCacheMx.Unlock()

// Check if a cached version exists.
if swaggerSpecCache != nil {
return swaggerSpecCache, swaggerSpecAnalysisCache, nil
}

// Load embedded swagger file.
swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
if err != nil {
return nil, nil, fmt.Errorf("failed to load embedded swagger file: %w", err)
}

swaggerSpecCache = swaggerSpec
swaggerSpecAnalysisCache = analysis.New(swaggerSpec.Spec())
return swaggerSpec, swaggerSpecAnalysisCache, nil
}
12 changes: 6 additions & 6 deletions asset/assets_vfsdata.go

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions cmd/alertmanager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import (
"github.com/prometheus/alertmanager/inhibit"
"github.com/prometheus/alertmanager/nflog"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/notify/discord"
"github.com/prometheus/alertmanager/notify/email"
"github.com/prometheus/alertmanager/notify/opsgenie"
"github.com/prometheus/alertmanager/notify/pagerduty"
Expand Down Expand Up @@ -173,6 +174,10 @@ func buildReceiverIntegrations(nc *config.Receiver, tmpl *template.Template, log
for i, c := range nc.TelegramConfigs {
add("telegram", i, c, func(l log.Logger) (notify.Notifier, error) { return telegram.New(c, tmpl, l) })
}
for i, c := range nc.DiscordConfigs {
add("discord", i, c, func(l log.Logger) (notify.Notifier, error) { return discord.New(c, tmpl, l) })
}

if errs.Len() > 0 {
return nil, &errs
}
Expand Down
22 changes: 20 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ func resolveFilepaths(baseDir string, cfg *Config) {
for _, cfg := range receiver.TelegramConfigs {
cfg.HTTPConfig.SetDirectory(baseDir)
}
for _, cfg := range receiver.DiscordConfigs {
cfg.HTTPConfig.SetDirectory(baseDir)
}
}
}

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

if c.Global.VictorOpsAPIKey != "" && len(c.Global.VictorOpsAPIKeyFile) > 0 {
return fmt.Errorf("at most one of victorops_api_key & victorops_api_key_file must be configured")
}

if len(c.Global.SMTPAuthPassword) > 0 && len(c.Global.SMTPAuthPasswordFile) > 0 {
return fmt.Errorf("at most one of smtp_auth_password & smtp_auth_password_file must be configured")
}
Expand Down Expand Up @@ -476,11 +483,12 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
if !strings.HasSuffix(voc.APIURL.Path, "/") {
voc.APIURL.Path += "/"
}
if voc.APIKey == "" {
if c.Global.VictorOpsAPIKey == "" {
if voc.APIKey == "" && len(voc.APIKeyFile) == 0 {
if c.Global.VictorOpsAPIKey == "" && len(c.Global.VictorOpsAPIKeyFile) == 0 {
return fmt.Errorf("no global VictorOps API Key set")
}
voc.APIKey = c.Global.VictorOpsAPIKey
voc.APIKeyFile = c.Global.VictorOpsAPIKeyFile
}
}
for _, sns := range rcv.SNSConfigs {
Expand All @@ -497,6 +505,14 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
telegram.APIUrl = c.Global.TelegramAPIUrl
}
}
for _, discord := range rcv.DiscordConfigs {
if discord.HTTPConfig == nil {
discord.HTTPConfig = c.Global.HTTPConfig
}
if discord.WebhookURL == nil {
return fmt.Errorf("no discord webhook URL provided")
}
}

names[rcv.Name] = struct{}{}
}
Expand Down Expand Up @@ -718,6 +734,7 @@ type GlobalConfig struct {
WeChatAPICorpID string `yaml:"wechat_api_corp_id,omitempty" json:"wechat_api_corp_id,omitempty"`
VictorOpsAPIURL *URL `yaml:"victorops_api_url,omitempty" json:"victorops_api_url,omitempty"`
VictorOpsAPIKey Secret `yaml:"victorops_api_key,omitempty" json:"victorops_api_key,omitempty"`
VictorOpsAPIKeyFile string `yaml:"victorops_api_key_file,omitempty" json:"victorops_api_key_file,omitempty"`
TelegramAPIUrl *URL `yaml:"telegram_api_url,omitempty" json:"telegram_api_url,omitempty"`
}

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

DiscordConfigs []*DiscordConfig `yaml:"discord_configs,omitempty" json:"discord_configs,omitempty"`
EmailConfigs []*EmailConfig `yaml:"email_configs,omitempty" json:"email_configs,omitempty"`
PagerdutyConfigs []*PagerdutyConfig `yaml:"pagerduty_configs,omitempty" json:"pagerduty_configs,omitempty"`
SlackConfigs []*SlackConfig `yaml:"slack_configs,omitempty" json:"slack_configs,omitempty"`
Expand Down
31 changes: 29 additions & 2 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1016,11 +1016,38 @@ func TestVictorOpsDefaultAPIKey(t *testing.T) {
}

defaultKey := conf.Global.VictorOpsAPIKey
overrideKey := Secret("qwe456")
if defaultKey != conf.Receivers[0].VictorOpsConfigs[0].APIKey {
t.Fatalf("Invalid victorops key: %s\nExpected: %s", conf.Receivers[0].VictorOpsConfigs[0].APIKey, defaultKey)
}
if defaultKey == conf.Receivers[1].VictorOpsConfigs[0].APIKey {
t.Errorf("Invalid victorops key: %s\nExpected: %s", conf.Receivers[0].VictorOpsConfigs[0].APIKey, "qwe456")
if overrideKey != conf.Receivers[1].VictorOpsConfigs[0].APIKey {
t.Errorf("Invalid victorops key: %s\nExpected: %s", conf.Receivers[0].VictorOpsConfigs[0].APIKey, string(overrideKey))
}
}

func TestVictorOpsDefaultAPIKeyFile(t *testing.T) {
conf, err := LoadFile("testdata/conf.victorops-default-apikey-file.yml")
if err != nil {
t.Fatalf("Error parsing %s: %s", "testdata/conf.victorops-default-apikey-file.yml", err)
}

defaultKey := conf.Global.VictorOpsAPIKeyFile
overrideKey := "/override_file"
if defaultKey != conf.Receivers[0].VictorOpsConfigs[0].APIKeyFile {
t.Fatalf("Invalid VictorOps key_file: %s\nExpected: %s", conf.Receivers[0].VictorOpsConfigs[0].APIKeyFile, defaultKey)
}
if overrideKey != conf.Receivers[1].VictorOpsConfigs[0].APIKeyFile {
t.Errorf("Invalid VictorOps key_file: %s\nExpected: %s", conf.Receivers[0].VictorOpsConfigs[0].APIKeyFile, overrideKey)
}
}

func TestVictorOpsBothAPIKeyAndFile(t *testing.T) {
_, err := LoadFile("testdata/conf.victorops-both-file-and-apikey.yml")
if err == nil {
t.Fatalf("Expected an error parsing %s: %s", "testdata/conf.victorops-both-file-and-apikey.yml", err)
}
if err.Error() != "at most one of victorops_api_key & victorops_api_key_file must be configured" {
t.Errorf("Expected: %s\nGot: %s", "at most one of victorops_api_key & victorops_api_key_file must be configured", err.Error())
}
}

Expand Down
72 changes: 57 additions & 15 deletions config/notifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ var (
},
}

// DefaultDiscordConfig defines default values for Discord configurations.
DefaultDiscordConfig = DiscordConfig{
NotifierConfig: NotifierConfig{
VSendResolved: true,
},
Title: `{{ template "discord.default.title" . }}`,
Message: `{{ template "discord.default.message" . }}`,
}

// DefaultEmailConfig defines default values for Email configurations.
DefaultEmailConfig = EmailConfig{
NotifierConfig: NotifierConfig{
Expand Down Expand Up @@ -157,6 +166,24 @@ func (nc *NotifierConfig) SendResolved() bool {
return nc.VSendResolved
}

// DiscordConfig configures notifications via Discord.
type DiscordConfig struct {
NotifierConfig `yaml:",inline" json:",inline"`

HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`
WebhookURL *SecretURL `yaml:"webhook_url,omitempty" json:"webhook_url,omitempty"`

Title string `yaml:"title,omitempty" json:"title,omitempty"`
Message string `yaml:"message,omitempty" json:"message,omitempty"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *DiscordConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultDiscordConfig
type plain DiscordConfig
return unmarshal((*plain)(c))
}

// EmailConfig configures notifications via mail.
type EmailConfig struct {
NotifierConfig `yaml:",inline" json:",inline"`
Expand Down Expand Up @@ -208,19 +235,22 @@ type PagerdutyConfig struct {

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

ServiceKey Secret `yaml:"service_key,omitempty" json:"service_key,omitempty"`
RoutingKey Secret `yaml:"routing_key,omitempty" json:"routing_key,omitempty"`
URL *URL `yaml:"url,omitempty" json:"url,omitempty"`
Client string `yaml:"client,omitempty" json:"client,omitempty"`
ClientURL string `yaml:"client_url,omitempty" json:"client_url,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Details map[string]string `yaml:"details,omitempty" json:"details,omitempty"`
Images []PagerdutyImage `yaml:"images,omitempty" json:"images,omitempty"`
Links []PagerdutyLink `yaml:"links,omitempty" json:"links,omitempty"`
Severity string `yaml:"severity,omitempty" json:"severity,omitempty"`
Class string `yaml:"class,omitempty" json:"class,omitempty"`
Component string `yaml:"component,omitempty" json:"component,omitempty"`
Group string `yaml:"group,omitempty" json:"group,omitempty"`
ServiceKey Secret `yaml:"service_key,omitempty" json:"service_key,omitempty"`
ServiceKeyFile string `yaml:"service_key_file,omitempty" json:"service_key_file,omitempty"`
RoutingKey Secret `yaml:"routing_key,omitempty" json:"routing_key,omitempty"`
RoutingKeyFile string `yaml:"routing_key_file,omitempty" json:"routing_key_file,omitempty"`
URL *URL `yaml:"url,omitempty" json:"url,omitempty"`
Client string `yaml:"client,omitempty" json:"client,omitempty"`
ClientURL string `yaml:"client_url,omitempty" json:"client_url,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Details map[string]string `yaml:"details,omitempty" json:"details,omitempty"`
Images []PagerdutyImage `yaml:"images,omitempty" json:"images,omitempty"`
Links []PagerdutyLink `yaml:"links,omitempty" json:"links,omitempty"`
Source string `yaml:"source,omitempty" json:"source,omitempty"`
Severity string `yaml:"severity,omitempty" json:"severity,omitempty"`
Class string `yaml:"class,omitempty" json:"class,omitempty"`
Component string `yaml:"component,omitempty" json:"component,omitempty"`
Group string `yaml:"group,omitempty" json:"group,omitempty"`
}

// PagerdutyLink is a link
Expand All @@ -243,12 +273,21 @@ func (c *PagerdutyConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.RoutingKey == "" && c.ServiceKey == "" {
if c.RoutingKey == "" && c.ServiceKey == "" && c.RoutingKeyFile == "" && c.ServiceKeyFile == "" {
return fmt.Errorf("missing service or routing key in PagerDuty config")
}
if len(c.RoutingKey) > 0 && len(c.RoutingKeyFile) > 0 {
return fmt.Errorf("at most one of routing_key & routing_key_file must be configured")
}
if len(c.ServiceKey) > 0 && len(c.ServiceKeyFile) > 0 {
return fmt.Errorf("at most one of service_key & service_key_file must be configured")
}
if c.Details == nil {
c.Details = make(map[string]string)
}
if c.Source == "" {
c.Source = c.Client
}
for k, v := range DefaultPagerdutyDetails {
if _, ok := c.Details[k]; !ok {
c.Details[k] = v
Expand Down Expand Up @@ -528,7 +567,7 @@ type VictorOpsConfig struct {
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`

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

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

Expand Down
Loading

0 comments on commit 8b09c90

Please sign in to comment.