Skip to content

Commit

Permalink
opsgenie: Add visible_to
Browse files Browse the repository at this point in the history
Add visible_to with the same support for templating and fields as the
existing responders field.

Opsgenie documentation:

  Teams and users that the alert will become visible to without sending
  any notification. type field is mandatory for each item, where possible
  values are team and user. In addition to the type field, either id or
  name should be given for teams and either id or username should be given
  for users. Please note: that alert will be visible to the teams that are
  specified within responders field by default, so there is no need to
  re-specify them within visibleTo field. You can refer below for example
  values.

The functionality has been tested with unit tests, and with the following
configuration:

  route:
    receiver: og
    group_wait: 30s
    group_interval: 5m
    repeat_interval: 12h
  receivers:
  - name: og
    opsgenie_configs:
    - send_resolved: true
      api_key: ...top secret...
      api_url: https://api.eu.opsgenie.com/
      message: 'Lab message'
      description: 'Static description'
      source: lab
      details:
        foo: bar
      priority: P4
      visible_to:
      - type: user
        username: [email protected]

Fixes #3953
  • Loading branch information
chlunde committed Aug 15, 2024
1 parent b233fe1 commit dc1641f
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 3 deletions.
29 changes: 29 additions & 0 deletions config/notifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,7 @@ type OpsGenieConfig struct {
Details map[string]string `yaml:"details,omitempty" json:"details,omitempty"`
Entity string `yaml:"entity,omitempty" json:"entity,omitempty"`
Responders []OpsGenieConfigResponder `yaml:"responders,omitempty" json:"responders,omitempty"`
VisibleTo []OpsGenieConfigVisibleTo `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"`
Expand Down Expand Up @@ -625,6 +626,24 @@ func (c *OpsGenieConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
}
}

for _, v := range c.VisibleTo {
if v.ID == "" && v.Username == "" && v.Name == "" {
return fmt.Errorf("opsGenieConfig visible_to %v has to have at least one of id, username or name specified", v)
}

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

return nil
}

Expand All @@ -638,6 +657,16 @@ type OpsGenieConfigResponder struct {
Type string `yaml:"type,omitempty" json:"type,omitempty"`
}

type OpsGenieConfigVisibleTo struct {
// One of those 3 should be filled.
ID string `yaml:"id,omitempty" json:"id,omitempty"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Username string `yaml:"username,omitempty" json:"username,omitempty"`

// team, user
Type string `yaml:"type,omitempty" json:"type,omitempty"`
}

// VictorOpsConfig configures notifications via VictorOps.
type VictorOpsConfig struct {
NotifierConfig `yaml:",inline" json:",inline"`
Expand Down
39 changes: 39 additions & 0 deletions notify/opsgenie/opsgenie.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ type opsGenieCreateMessage struct {
Details map[string]string `json:"details"`
Source string `json:"source"`
Responders []opsGenieCreateMessageResponder `json:"responders,omitempty"`
VisibleTo []opsGenieCreateMessageVisibleTo `json:"visibleTo,omitempty"`
Tags []string `json:"tags,omitempty"`
Note string `json:"note,omitempty"`
Priority string `json:"priority,omitempty"`
Expand All @@ -81,6 +82,13 @@ type opsGenieCreateMessageResponder struct {
Type string `json:"type"` // team, user, escalation, schedule etc.
}

type opsGenieCreateMessageVisibleTo struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Username string `json:"username,omitempty"`
Type string `json:"type"` // team, user
}

type opsGenieCloseMessage struct {
Source string `json:"source"`
}
Expand Down Expand Up @@ -211,13 +219,44 @@ func (n *Notifier) createRequests(ctx context.Context, as ...*types.Alert) ([]*h
responders = append(responders, responder)
}

var visibleTos []opsGenieCreateMessageVisibleTo
for _, v := range n.conf.VisibleTo {
visibleTo := opsGenieCreateMessageVisibleTo{
ID: tmpl(v.ID),
Name: tmpl(v.Name),
Username: tmpl(v.Username),
Type: tmpl(v.Type),
}

if visibleTo == (opsGenieCreateMessageVisibleTo{}) {
// Filter out empty responders. This is useful if you want to fill
// responders dynamically from alert's common labels.
continue
}

if visibleTo.Type == "teams" {
teams := safeSplit(visibleTo.Name, ",")
for _, team := range teams {
newVisibleTo := opsGenieCreateMessageVisibleTo{
Name: tmpl(team),
Type: tmpl("team"),
}
visibleTos = append(visibleTos, newVisibleTo)
}
continue
}

visibleTos = append(visibleTos, visibleTo)
}

msg := &opsGenieCreateMessage{
Alias: alias,
Message: message,
Description: tmpl(n.conf.Description),
Details: details,
Source: tmpl(n.conf.Source),
Responders: responders,
VisibleTo: visibleTos,
Tags: safeSplit(tmpl(n.conf.Tags), ","),
Note: tmpl(n.conf.Note),
Priority: tmpl(n.conf.Priority),
Expand Down
53 changes: 50 additions & 3 deletions notify/opsgenie/opsgenie_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func TestOpsGenie(t *testing.T) {
},
expectedEmptyAlertBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"","details":{},"source":""}
`,
expectedBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"message","description":"description","details":{"Actions":"doThis,doThat","Description":"description","Entity":"test-domain","Message":"message","Note":"this is a note","Priority":"P1","ResponderName1":"TeamA","ResponderName2":"EscalationA","ResponderName3":"TeamA,TeamB","ResponderType1":"team","ResponderType2":"escalation","ResponderType3":"teams","Source":"http://prometheus","Tags":"tag1,tag2"},"source":"http://prometheus","responders":[{"name":"TeamA","type":"team"},{"name":"EscalationA","type":"escalation"}],"tags":["tag1","tag2"],"note":"this is a note","priority":"P1","entity":"test-domain","actions":["doThis","doThat"]}
expectedBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"message","description":"description","details":{"Actions":"doThis,doThat","Description":"description","Entity":"test-domain","Message":"message","Note":"this is a note","Priority":"P1","ResponderName1":"TeamA","ResponderName2":"EscalationA","ResponderName3":"TeamA,TeamB","ResponderType1":"team","ResponderType2":"escalation","ResponderType3":"teams","Source":"http://prometheus","Tags":"tag1,tag2","VisibleToName1":"TeamA","VisibleToName2":"user1","VisibleToType1":"team","VisibleToType2":"user"},"source":"http://prometheus","responders":[{"name":"TeamA","type":"team"},{"name":"EscalationA","type":"escalation"}],"tags":["tag1","tag2"],"note":"this is a note","priority":"P1","entity":"test-domain","actions":["doThis","doThat"]}
`,
},
{
Expand Down Expand Up @@ -176,7 +176,7 @@ func TestOpsGenie(t *testing.T) {
},
expectedEmptyAlertBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"","details":{"Description":"adjusted "},"source":""}
`,
expectedBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"message","description":"description","details":{"Actions":"doThis,doThat","Description":"adjusted description","Entity":"test-domain","Message":"message","Note":"this is a note","Priority":"P1","ResponderName1":"TeamA","ResponderName2":"EscalationA","ResponderName3":"TeamA,TeamB","ResponderType1":"team","ResponderType2":"escalation","ResponderType3":"teams","Source":"http://prometheus","Tags":"tag1,tag2"},"source":"http://prometheus","responders":[{"name":"TeamA","type":"team"},{"name":"EscalationA","type":"escalation"}],"tags":["tag1","tag2"],"note":"this is a note","priority":"P1","entity":"test-domain","actions":["doThis","doThat"]}
expectedBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"message","description":"description","details":{"Actions":"doThis,doThat","Description":"adjusted description","Entity":"test-domain","Message":"message","Note":"this is a note","Priority":"P1","ResponderName1":"TeamA","ResponderName2":"EscalationA","ResponderName3":"TeamA,TeamB","ResponderType1":"team","ResponderType2":"escalation","ResponderType3":"teams","Source":"http://prometheus","Tags":"tag1,tag2","VisibleToName1":"TeamA","VisibleToName2":"user1","VisibleToType1":"team","VisibleToType2":"user"},"source":"http://prometheus","responders":[{"name":"TeamA","type":"team"},{"name":"EscalationA","type":"escalation"}],"tags":["tag1","tag2"],"note":"this is a note","priority":"P1","entity":"test-domain","actions":["doThis","doThat"]}
`,
},
{
Expand Down Expand Up @@ -206,7 +206,50 @@ func TestOpsGenie(t *testing.T) {
},
expectedEmptyAlertBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"","details":{"Description":"adjusted "},"source":""}
`,
expectedBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"message","description":"description","details":{"Actions":"doThis,doThat","Description":"adjusted description","Entity":"test-domain","Message":"message","Note":"this is a note","Priority":"P1","ResponderName1":"TeamA","ResponderName2":"EscalationA","ResponderName3":"TeamA,TeamB","ResponderType1":"team","ResponderType2":"escalation","ResponderType3":"teams","Source":"http://prometheus","Tags":"tag1,tag2"},"source":"http://prometheus","responders":[{"name":"TeamA","type":"team"},{"name":"TeamB","type":"team"}],"tags":["tag1","tag2"],"note":"this is a note","priority":"P1"}
expectedBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"message","description":"description","details":{"Actions":"doThis,doThat","Description":"adjusted description","Entity":"test-domain","Message":"message","Note":"this is a note","Priority":"P1","ResponderName1":"TeamA","ResponderName2":"EscalationA","ResponderName3":"TeamA,TeamB","ResponderType1":"team","ResponderType2":"escalation","ResponderType3":"teams","Source":"http://prometheus","Tags":"tag1,tag2","VisibleToName1":"TeamA","VisibleToName2":"user1","VisibleToType1":"team","VisibleToType2":"user"},"source":"http://prometheus","responders":[{"name":"TeamA","type":"team"},{"name":"TeamB","type":"team"}],"tags":["tag1","tag2"],"note":"this is a note","priority":"P1"}
`,
},
{
title: "config with visible_to",
cfg: &config.OpsGenieConfig{
NotifierConfig: config.NotifierConfig{
VSendResolved: true,
},
Message: `{{ .CommonLabels.Message }}`,
Description: `{{ .CommonLabels.Description }}`,
Source: `{{ .CommonLabels.Source }}`,
Responders: []config.OpsGenieConfigResponder{
{
Name: `{{ .CommonLabels.ResponderName1 }}`,
Type: `{{ .CommonLabels.ResponderType1 }}`,
},
{
Name: `{{ .CommonLabels.ResponderName2 }}`,
Type: `{{ .CommonLabels.ResponderType2 }}`,
},
},
VisibleTo: []config.OpsGenieConfigVisibleTo{
{
Name: `{{ .CommonLabels.VisibleToName1 }}`,
Type: `{{ .CommonLabels.VisibleToType1 }}`,
},
{
Name: `{{ .CommonLabels.VisibleToName2 }}`,
Type: `{{ .CommonLabels.VisibleToType2 }}`,
},
},
Tags: `{{ .CommonLabels.Tags }}`,
Note: `{{ .CommonLabels.Note }}`,
Priority: `{{ .CommonLabels.Priority }}`,
Entity: `{{ .CommonLabels.Entity }}`,
Actions: `{{ .CommonLabels.Actions }}`,
APIKey: `{{ .ExternalURL }}`,
APIURL: &config.URL{URL: u},
HTTPConfig: &commoncfg.HTTPClientConfig{},
},
expectedEmptyAlertBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"","details":{},"source":""}
`,
expectedBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"message","description":"description","details":{"Actions":"doThis,doThat","Description":"description","Entity":"test-domain","Message":"message","Note":"this is a note","Priority":"P1","ResponderName1":"TeamA","ResponderName2":"EscalationA","ResponderName3":"TeamA,TeamB","ResponderType1":"team","ResponderType2":"escalation","ResponderType3":"teams","Source":"http://prometheus","Tags":"tag1,tag2","VisibleToName1":"TeamA","VisibleToName2":"user1","VisibleToType1":"team","VisibleToType2":"user"},"source":"http://prometheus","responders":[{"name":"TeamA","type":"team"},{"name":"EscalationA","type":"escalation"}],"visibleTo":[{"name":"TeamA","type":"team"},{"name":"user1","type":"user"}],"tags":["tag1","tag2"],"note":"this is a note","priority":"P1","entity":"test-domain","actions":["doThis","doThat"]}
`,
},
} {
Expand Down Expand Up @@ -248,6 +291,10 @@ func TestOpsGenie(t *testing.T) {
"ResponderType2": "escalation",
"ResponderName3": "TeamA,TeamB",
"ResponderType3": "teams",
"VisibleToName1": "TeamA",
"VisibleToType1": "team",
"VisibleToName2": "user1",
"VisibleToType2": "user",
"Tags": "tag1,tag2",
"Note": "this is a note",
"Priority": "P1",
Expand Down

0 comments on commit dc1641f

Please sign in to comment.