Skip to content

Add Compass notifier #4320

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions asset/assets_vfsdata.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,7 @@ type Receiver struct {
SlackConfigs []*SlackConfig `yaml:"slack_configs,omitempty" json:"slack_configs,omitempty"`
WebhookConfigs []*WebhookConfig `yaml:"webhook_configs,omitempty" json:"webhook_configs,omitempty"`
OpsGenieConfigs []*OpsGenieConfig `yaml:"opsgenie_configs,omitempty" json:"opsgenie_configs,omitempty"`
CompassConfig []*CompassConfig `yaml:"compass_configs,omitempty" json:"compass_configs,omitempty"`
WechatConfigs []*WechatConfig `yaml:"wechat_configs,omitempty" json:"wechat_configs,omitempty"`
PushoverConfigs []*PushoverConfig `yaml:"pushover_configs,omitempty" json:"pushover_configs,omitempty"`
VictorOpsConfigs []*VictorOpsConfig `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"`
Expand Down
108 changes: 108 additions & 0 deletions config/notifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,17 @@ var (
// TODO: Add a details field with all the alerts.
}

// DefaultCompassConfig defines default values for Compass configurations.
DefaultCompassConfig = CompassConfig{
NotifierConfig: NotifierConfig{
VSendResolved: true,
},
Message: `{{ template "compass.default.message" . }}`,
Description: `{{ template "compass.default.description" . }}`,
Source: `{{ template "compass.default.source" . }}`,
// TODO: Add a details field with all the alerts.
}

// DefaultWechatConfig defines default values for wechat configurations.
DefaultWechatConfig = WechatConfig{
NotifierConfig: NotifierConfig{
Expand Down Expand Up @@ -666,6 +677,103 @@ type OpsGenieConfigResponder struct {
Type string `yaml:"type,omitempty" json:"type,omitempty"`
}

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

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

APIUser string `yaml:"api_user,omitempty" json:"api_user,omitempty"`
APIKey Secret `yaml:"api_key,omitempty" json:"api_key,omitempty"`
APIKeyFile string `yaml:"api_key_file,omitempty" json:"api_key_file,omitempty"`
APIURL *URL `yaml:"api_url,omitempty" json:"api_url,omitempty"`
Message string `yaml:"message,omitempty" json:"message,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Source string `yaml:"source,omitempty" json:"source,omitempty"`
Entity string `yaml:"entity,omitempty" json:"entity,omitempty"`
Responders []CompassConfigConfigResponder `yaml:"responders,omitempty" json:"responders,omitempty"`
VisibleTo []CompassConfigConfigVisibleTo `yaml:"visible_to,omitempty" json:"visible_to,omitempty"`
Actions string `yaml:"actions,omitempty" json:"actions,omitempty"`
Tags string `yaml:"tags,omitempty" json:"tags,omitempty"`
Note string `yaml:"note,omitempty" json:"note,omitempty"`
Priority string `yaml:"priority,omitempty" json:"priority,omitempty"`
ExtraProperties map[string]string `yaml:"extra_properties,omitempty" json:"extra_properties,omitempty"`
UpdateAlerts bool `yaml:"update_alerts,omitempty" json:"update_alerts,omitempty"`
}

const compassValidResponderTypesRe = `^(team|user|escalation|schedule)$`
const compassValidVisibleToTypesRe = `^(team|user)$`

var compassResponderTypeMatcher = regexp.MustCompile(compassValidResponderTypesRe)
var compassVisibleToTypeMatcher = regexp.MustCompile(compassValidVisibleToTypesRe)

// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *CompassConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultCompassConfig
type plain CompassConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}

if c.APIUser == "" {
return errors.New("api_user must be configured")
}

if c.APIKey != "" && len(c.APIKeyFile) > 0 {
return errors.New("at most one of api_key & api_key_file must be configured")
}

for _, responder := range c.Responders {
if responder.ID == "" {
return fmt.Errorf("compassConfig responder id must be configured")
}

if strings.Contains(responder.Type, "{{") {
_, err := template.New("").Parse(responder.Type)
if err != nil {
return fmt.Errorf("compassConfig responder %v type is not a valid template: %w", responder, err)
}
} else {
responder.Type = strings.ToLower(responder.Type)
if !compassResponderTypeMatcher.MatchString(responder.Type) {
return fmt.Errorf("compassConfig responder %v type does not match valid options %s", responder, compassValidResponderTypesRe)
}
}
}

for _, visibleTo := range c.VisibleTo {
if visibleTo.ID == "" {
return fmt.Errorf("compassConfig visibleTo id must be configured")
}

if strings.Contains(visibleTo.Type, "{{") {
_, err := template.New("").Parse(visibleTo.Type)
if err != nil {
return fmt.Errorf("compassConfig visibleTo %v type is not a valid template: %w", visibleTo, err)
}
} else {
visibleTo.Type = strings.ToLower(visibleTo.Type)
if !compassVisibleToTypeMatcher.MatchString(visibleTo.Type) {
return fmt.Errorf("compassConfig visibleTo %v type does not match valid options %s", visibleTo, compassValidResponderTypesRe)
}
}
}

return nil
}

type CompassConfigConfigResponder struct {
ID string `yaml:"id,omitempty" json:"id,omitempty"`
// team, user, escalation, schedule etc.
Type string `yaml:"type,omitempty" json:"type,omitempty"`
}

type CompassConfigConfigVisibleTo struct {
ID string `yaml:"id,omitempty" json:"id,omitempty"`
// team, user, escalation, schedule etc.
Type string `yaml:"type,omitempty" json:"type,omitempty"`
}

// VictorOpsConfig configures notifications via VictorOps.
type VictorOpsConfig struct {
NotifierConfig `yaml:",inline" json:",inline"`
Expand Down
94 changes: 94 additions & 0 deletions config/notifiers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,100 @@ api_url: http://example.com
}
}

func TestCompassConfiguration(t *testing.T) {
for _, tc := range []struct {
name string
in string

err bool
}{
{
name: "valid configuration",
in: `api_key: xyz
api_user: abc
responders:
- id: foo
type: schedule
api_url: http://example.com
`,
},
{
name: "api_user is missing",
in: `api_key: xyz
api_url: http://example.com
`,
err: true,
},
{
name: "api_key and api_key_file both defined",
in: `api_key: xyz
api_key_file: xyz
api_user: abc
api_url: http://example.com
`,
err: true,
},
{
name: "invalid responder type",
in: `api_key: xyz
api_user: abc
responders:
- id: foo
type: wrong
api_url: http://example.com
`,
err: true,
},
{
name: "missing responder field",
in: `api_key: xyz
api_user: abc
responders:
- type: schedule
api_url: http://example.com
`,
err: true,
},
{
name: "valid responder type template",
in: `api_key: xyz
api_user: abc
responders:
- id: foo
type: "{{/* valid comment */}}team"
api_url: http://example.com
`,
},
{
name: "invalid responder type template",
in: `api_key: xyz
api_user: abc
responders:
- id: foo
type: "{{/* invalid comment }}team"
api_url: http://example.com
`,
err: true,
},
} {
t.Run(tc.name, func(t *testing.T) {
var cfg CompassConfig

err := yaml.UnmarshalStrict([]byte(tc.in), &cfg)
if tc.err {
if err == nil {
t.Fatalf("expected error but got none")
}
return
}

if err != nil {
t.Errorf("expected no error, got %v", err)
}
})
}
}

func TestSNS(t *testing.T) {
for _, tc := range []struct {
in string
Expand Down
4 changes: 4 additions & 0 deletions config/receiver/receiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/notify/compass"
"github.com/prometheus/alertmanager/notify/discord"
"github.com/prometheus/alertmanager/notify/email"
"github.com/prometheus/alertmanager/notify/jira"
Expand Down Expand Up @@ -73,6 +74,9 @@ func BuildReceiverIntegrations(nc config.Receiver, tmpl *template.Template, logg
for i, c := range nc.OpsGenieConfigs {
add("opsgenie", i, c, func(l *slog.Logger) (notify.Notifier, error) { return opsgenie.New(c, tmpl, l, httpOpts...) })
}
for i, c := range nc.CompassConfig {
add("compass", i, c, func(l *slog.Logger) (notify.Notifier, error) { return compass.New(c, tmpl, l, httpOpts...) })
}
for i, c := range nc.WechatConfigs {
add("wechat", i, c, func(l *slog.Logger) (notify.Notifier, error) { return wechat.New(c, tmpl, l, httpOpts...) })
}
Expand Down
2 changes: 2 additions & 0 deletions notify/compass/api_key_file
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
my_secret_api_key

Loading