Skip to content

Commit

Permalink
support for responders in opsgenie
Browse files Browse the repository at this point in the history
  • Loading branch information
yuri-tceretian committed Oct 25, 2023
1 parent 939f557 commit 72e1494
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 15 deletions.
51 changes: 44 additions & 7 deletions receivers/opsgenie/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"strings"
"text/template"

"github.com/grafana/alerting/receivers"
"github.com/grafana/alerting/templates"
Expand All @@ -18,6 +19,15 @@ const (
DefaultAlertsURL = "https://api.opsgenie.com/v2/alerts"
)

var SupportedResponderTypes = []string{"team", "teams", "user", "escalation", "schedule"}

type MessageResponder struct {
ID string `json:"id,omitempty" yaml:"id,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Username string `json:"username,omitempty" yaml:"username,omitempty"`
Type string `json:"type" yaml:"type"` // team, user, escalation, schedule etc.
}

type Config struct {
APIKey string
APIUrl string
Expand All @@ -26,17 +36,19 @@ type Config struct {
AutoClose bool
OverridePriority bool
SendTagsAs string
Responders []MessageResponder
}

func NewConfig(jsonData json.RawMessage, decryptFn receivers.DecryptFunc) (Config, error) {
type rawSettings struct {
APIKey string `json:"apiKey,omitempty" yaml:"apiKey,omitempty"`
APIUrl string `json:"apiUrl,omitempty" yaml:"apiUrl,omitempty"`
Message string `json:"message,omitempty" yaml:"message,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
AutoClose *bool `json:"autoClose,omitempty" yaml:"autoClose,omitempty"`
OverridePriority *bool `json:"overridePriority,omitempty" yaml:"overridePriority,omitempty"`
SendTagsAs string `json:"sendTagsAs,omitempty" yaml:"sendTagsAs,omitempty"`
APIKey string `json:"apiKey,omitempty" yaml:"apiKey,omitempty"`
APIUrl string `json:"apiUrl,omitempty" yaml:"apiUrl,omitempty"`
Message string `json:"message,omitempty" yaml:"message,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
AutoClose *bool `json:"autoClose,omitempty" yaml:"autoClose,omitempty"`
OverridePriority *bool `json:"overridePriority,omitempty" yaml:"overridePriority,omitempty"`
SendTagsAs string `json:"sendTagsAs,omitempty" yaml:"sendTagsAs,omitempty"`
Responders []MessageResponder `json:"responders,omitempty" yaml:"responders,omitempty"`
}

raw := rawSettings{}
Expand Down Expand Up @@ -74,6 +86,30 @@ func NewConfig(jsonData json.RawMessage, decryptFn receivers.DecryptFunc) (Confi
raw.OverridePriority = &overridePriority
}

for idx, r := range raw.Responders {
if r.ID == "" && r.Username == "" && r.Name == "" {
return Config{}, fmt.Errorf("responder at index [%d] must have at least one of id, username or name specified", idx)
}
if strings.Contains(r.Type, "{{") {
_, err := template.New("").Parse(r.Type)
if err != nil {
return Config{}, fmt.Errorf("responder at index [%d] type is not a valid template: %v", idx, err)
}
} else {
r.Type = strings.ToLower(r.Type)
match := false
for _, t := range SupportedResponderTypes {
if r.Type == t {
match = true
break
}
}
if !match {
return Config{}, fmt.Errorf("responder at index [%d] has unsupported type. Supported only: %s", idx, strings.Join(SupportedResponderTypes, ","))
}
}
}

return Config{
APIKey: raw.APIKey,
APIUrl: raw.APIUrl,
Expand All @@ -82,5 +118,6 @@ func NewConfig(jsonData json.RawMessage, decryptFn receivers.DecryptFunc) (Confi
AutoClose: *raw.AutoClose,
OverridePriority: *raw.OverridePriority,
SendTagsAs: raw.SendTagsAs,
Responders: raw.Responders,
}, nil
}
62 changes: 61 additions & 1 deletion receivers/opsgenie/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,36 @@ func TestNewConfig(t *testing.T) {
},
expectedInitError: `invalid value for sendTagsAs: "test-tags"`,
},
{
name: "Error if responder type is not supported",
settings: `{ "responders" : [
{ "type" : "test-123", "id": "test" }
] }`,
secureSettings: map[string][]byte{
"apiKey": []byte("test-api-key"),
},
expectedInitError: `responder at index [0] has unsupported type. Supported only: team,teams,user,escalation,schedule`,
},
{
name: "Error if responder type is not supported",
settings: `{ "responders" : [
{ "type" : "test-123", "id": "test" }
] }`,
secureSettings: map[string][]byte{
"apiKey": []byte("test-api-key"),
},
expectedInitError: `responder at index [0] has unsupported type. Supported only: team,teams,user,escalation,schedule`,
},
{
name: "Error if responder ID,name,username are empty",
settings: `{ "responders" : [
{ "type" : "user" }
] }`,
secureSettings: map[string][]byte{
"apiKey": []byte("test-api-key"),
},
expectedInitError: `responder at index [0] must have at least one of id, username or name specified`,
},
{
name: "Should use default message if all spaces",
settings: `{ "message" : " " }`,
Expand Down Expand Up @@ -157,7 +187,8 @@ func TestNewConfig(t *testing.T) {
"description": "",
"autoClose": null,
"overridePriority": null,
"sendTagsAs": ""
"sendTagsAs": "",
"responders": null
}`,
expectedConfig: Config{
APIKey: "test-api-key",
Expand All @@ -167,6 +198,7 @@ func TestNewConfig(t *testing.T) {
AutoClose: true,
OverridePriority: true,
SendTagsAs: SendTags,
Responders: nil,
},
},
{
Expand All @@ -181,6 +213,20 @@ func TestNewConfig(t *testing.T) {
AutoClose: false,
OverridePriority: false,
SendTagsAs: "both",
Responders: []MessageResponder{
{
ID: "test-id",
Type: "team",
},
{
Username: "test-user",
Type: "user",
},
{
Name: "test-schedule",
Type: "schedule",
},
},
},
},
{
Expand All @@ -195,6 +241,20 @@ func TestNewConfig(t *testing.T) {
AutoClose: false,
OverridePriority: false,
SendTagsAs: "both",
Responders: []MessageResponder{
{
ID: "test-id",
Type: "team",
},
{
Username: "test-user",
Type: "user",
},
{
Name: "test-schedule",
Type: "schedule",
},
},
},
},
}
Expand Down
34 changes: 34 additions & 0 deletions receivers/opsgenie/opsgenie.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,39 @@ func (on *Notifier) buildOpsgenieMessage(ctx context.Context, alerts model.Alert
}
sort.Strings(tags)

responders := make([]opsGenieCreateMessageResponder, 0, len(on.settings.Responders))
for idx, r := range on.settings.Responders {
responder := opsGenieCreateMessageResponder{
ID: tmpl(r.ID),
Name: tmpl(r.Name),
Username: tmpl(r.Username),
Type: tmpl(r.Type),
}

if responder == (opsGenieCreateMessageResponder{}) {
on.log.Warn("templates in the responder were expanded to empty responder. Skipping it", "idx", idx)
// Filter out empty responders. This is useful if you want to fill
// responders dynamically from alert's common labels.
continue
}

if responder.Type == "teams" {
teams := strings.Split(responder.Name, ",")
for _, team := range teams {
if team == "" {
continue
}
newResponder := opsGenieCreateMessageResponder{
Name: tmpl(team),
Type: "team",
}
responders = append(responders, newResponder)
}
continue
}
responders = append(responders, responder)
}

result := opsGenieCreateMessage{
Alias: key.Hash(),
Description: description,
Expand All @@ -184,6 +217,7 @@ func (on *Notifier) buildOpsgenieMessage(ctx context.Context, alerts model.Alert
Message: message,
Details: details,
Priority: priority,
Responders: responders,
}

apiURL = tmpl(on.settings.APIUrl)
Expand Down
130 changes: 130 additions & 0 deletions receivers/opsgenie/opsgenie_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,136 @@ func TestNotify(t *testing.T) {
}`,
expMsgError: nil,
},
{
name: "Config with responders",
settings: Config{
APIKey: "abcdefgh0123456789",
APIUrl: DefaultAlertsURL,
Message: templates.DefaultMessageTitleEmbed,
Description: "",
AutoClose: true,
OverridePriority: true,
SendTagsAs: SendBoth,
Responders: []MessageResponder{
{
Name: "Test User",
Type: "user",
},
{
Name: "{{ .CommonAnnotations.user }}",
Type: "{{ .CommonAnnotations.type }}",
},
{
ID: "{{ .CommonAnnotations.user }}",
Type: "user",
},
{
Username: "{{ .CommonAnnotations.user }}",
Type: "team",
},
},
},
alerts: []*types.Alert{
{
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
Annotations: model.LabelSet{"user": "test", "type": "team"},
},
}, {
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val2"},
Annotations: model.LabelSet{"user": "test", "type": "team"},
},
},
},
expMsg: `{
"alias": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
"description": "[FIRING:2] \nhttp://localhost/alerting/list\n\n**Firing**\n\nValue: [no value]\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - type = team\n - user = test\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matcher=alertname%3Dalert1&matcher=lbl1%3Dval1\n\nValue: [no value]\nLabels:\n - alertname = alert1\n - lbl1 = val2\nAnnotations:\n - type = team\n - user = test\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matcher=alertname%3Dalert1&matcher=lbl1%3Dval2\n",
"details": {
"alertname": "alert1",
"url": "http://localhost/alerting/list"
},
"message": "[FIRING:2] ",
"source": "Grafana",
"tags": ["alertname:alert1"],
"responders": [
{
"name": "Test User",
"type": "user"
},
{
"name": "test",
"type": "team"
},
{
"id": "test",
"type": "user"
},
{
"username": "test",
"type": "team"
}
]
}`,
expMsgError: nil,
},
{
name: "Config with teams responders should be exploded",
settings: Config{
APIKey: "abcdefgh0123456789",
APIUrl: DefaultAlertsURL,
Message: templates.DefaultMessageTitleEmbed,
Description: "",
AutoClose: true,
OverridePriority: true,
SendTagsAs: SendBoth,
Responders: []MessageResponder{
{
Name: "team1,team2,{{ .CommonAnnotations.user }}",
Type: "teams",
},
},
},
alerts: []*types.Alert{
{
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
Annotations: model.LabelSet{"user": "test", "type": "team"},
},
}, {
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val2"},
Annotations: model.LabelSet{"user": "test", "type": "team"},
},
},
},
expMsg: `{
"alias": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
"description": "[FIRING:2] \nhttp://localhost/alerting/list\n\n**Firing**\n\nValue: [no value]\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - type = team\n - user = test\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matcher=alertname%3Dalert1&matcher=lbl1%3Dval1\n\nValue: [no value]\nLabels:\n - alertname = alert1\n - lbl1 = val2\nAnnotations:\n - type = team\n - user = test\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matcher=alertname%3Dalert1&matcher=lbl1%3Dval2\n",
"details": {
"alertname": "alert1",
"url": "http://localhost/alerting/list"
},
"message": "[FIRING:2] ",
"source": "Grafana",
"tags": ["alertname:alert1"],
"responders": [
{
"name": "team1",
"type": "team"
},
{
"name": "team2",
"type": "team"
},
{
"name": "test",
"type": "team"
}
]
}`,
expMsgError: nil,
},
{
name: "Resolved is not sent when auto close is false",
settings: Config{
Expand Down
Loading

0 comments on commit 72e1494

Please sign in to comment.