opsgenie: Add visible_to

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: foo@example.com

Fixes #3953

Signed-off-by: Carl Henrik Lunde <chlunde@ifi.uio.no>
This commit is contained in:
Carl Henrik Lunde 2024-08-15 13:54:15 +02:00
parent b233fe1816
commit db0f43f6e9
4 changed files with 138 additions and 3 deletions

View File

@ -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"`
@ -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
}
@ -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"`

View File

@ -1075,6 +1075,10 @@ OpsGenie notifications are sent via the [OpsGenie API](https://docs.opsgenie.com
responders:
[ - <responder> ... ]
# List of teams and users the alert will become visible to without sending any notification.
visible_to:
[ - <visible_to> ... ]
# Comma separated list of tags attached to the notifications.
[ tags: <tmpl_string> ]
@ -1114,6 +1118,22 @@ responders:
type: <tmpl_string>
```
#### `<visible_to>`
```yaml
# Exactly one of these fields should be defined.
[ id: <tmpl_string> ]
[ name: <tmpl_string> ]
[ username: <tmpl_string> ]
# One of `team`, `teams` or `user`.
#
# The `teams` responder is configured using the `name` field above.
# This field can contain a comma-separated list of team names.
# If the list is empty, no additional visibility will be configured.
type: <tmpl_string>
```
### `<pagerduty_config>`
PagerDuty notifications are sent via the [PagerDuty API](https://developer.pagerduty.com/documentation/integration/events).

View File

@ -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"`
@ -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"`
}
@ -211,6 +219,36 @@ 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,
@ -218,6 +256,7 @@ func (n *Notifier) createRequests(ctx context.Context, as ...*types.Alert) ([]*h
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),

View File

@ -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"]}
`,
},
{
@ -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"]}
`,
},
{
@ -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"]}
`,
},
} {
@ -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",