Skip to content

Commit

Permalink
Merge pull request #2489 from JeffAshton/opsgenie_details_fields
Browse files Browse the repository at this point in the history
Adding ability to specify opsgenie alert details
  • Loading branch information
Qmando authored Sep 27, 2019
2 parents 9c2c898 + 74ef682 commit 325f1df
Show file tree
Hide file tree
Showing 4 changed files with 300 additions and 1 deletion.
9 changes: 9 additions & 0 deletions docs/source/ruletypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1648,6 +1648,15 @@ Optional:

``opsgenie_priority``: Set the OpsGenie priority level. Possible values are P1, P2, P3, P4, P5.

``opsgenie_details``: Map of custom key/value pairs to include in the alert's details. The value can sourced from either fields in the first match, environment variables, or a constant value.

Example usage::

opsgenie_details:
Author: 'Bob Smith' # constant value
Environment: '$VAR' # environment variable
Message: { field: message } # field in the first match

SNS
~~~

Expand Down
23 changes: 22 additions & 1 deletion elastalert/opsgenie.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
import json
import logging

import os.path
import requests

from .alerts import Alerter
Expand Down Expand Up @@ -33,6 +33,7 @@ def __init__(self, *args):
self.alias = self.rule.get('opsgenie_alias')
self.opsgenie_proxy = self.rule.get('opsgenie_proxy', None)
self.priority = self.rule.get('opsgenie_priority')
self.opsgenie_details = self.rule.get('opsgenie_details', {})

def _parse_responders(self, responders, responder_args, matches, default_responders):
if responder_args:
Expand Down Expand Up @@ -97,6 +98,10 @@ def alert(self, matches):
if self.alias is not None:
post['alias'] = self.alias.format(**matches[0])

details = self.get_details(matches)
if details:
post['details'] = details

logging.debug(json.dumps(post))

headers = {
Expand Down Expand Up @@ -162,3 +167,19 @@ def get_info(self):
if self.teams:
ret['teams'] = self.teams
return ret

def get_details(self, matches):
details = {}

for key, value in self.opsgenie_details.items():

if type(value) is dict:
if 'field' in value:
field_value = lookup_es_key(matches[0], value['field'])
if field_value is not None:
details[key] = str(field_value)

elif type(value) is str:
details[key] = os.path.expandvars(value)

return details
14 changes: 14 additions & 0 deletions elastalert/schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,20 @@ properties:
mattermost_msg_pretext: {type: string}
mattermost_msg_fields: *mattermostField

## Opsgenie
opsgenie_details:
type: object
minProperties: 1
patternProperties:
"^.+$":
oneOf:
- type: string
- type: object
additionalProperties: false
required: [field]
properties:
field: {type: string, minLength: 1}

### PagerDuty
pagerduty_service_key: {type: string}
pagerduty_client_name: {type: string}
Expand Down
255 changes: 255 additions & 0 deletions tests/alerts_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,261 @@ def test_opsgenie_default_alert_routing():
assert alert.get_info()['recipients'] == ['[email protected]']


def test_opsgenie_details_with_constant_value():
rule = {
'name': 'Opsgenie Details',
'type': mock_rule(),
'opsgenie_account': 'genies',
'opsgenie_key': 'ogkey',
'opsgenie_details': {'Foo': 'Bar'}
}
match = {
'@timestamp': '2014-10-31T00:00:00'
}
alert = OpsGenieAlerter(rule)

with mock.patch('requests.post') as mock_post_request:
alert.alert([match])

mock_post_request.assert_called_once_with(
'https://api.opsgenie.com/v2/alerts',
headers={
'Content-Type': 'application/json',
'Authorization': 'GenieKey ogkey'
},
json=mock.ANY,
proxies=None
)

expected_json = {
'description': BasicMatchString(rule, match).__str__(),
'details': {'Foo': 'Bar'},
'message': 'ElastAlert: Opsgenie Details',
'priority': None,
'source': 'ElastAlert',
'tags': ['ElastAlert', 'Opsgenie Details'],
'user': 'genies'
}
actual_json = mock_post_request.call_args_list[0][1]['json']
assert expected_json == actual_json


def test_opsgenie_details_with_field():
rule = {
'name': 'Opsgenie Details',
'type': mock_rule(),
'opsgenie_account': 'genies',
'opsgenie_key': 'ogkey',
'opsgenie_details': {'Foo': {'field': 'message'}}
}
match = {
'message': 'Bar',
'@timestamp': '2014-10-31T00:00:00'
}
alert = OpsGenieAlerter(rule)

with mock.patch('requests.post') as mock_post_request:
alert.alert([match])

mock_post_request.assert_called_once_with(
'https://api.opsgenie.com/v2/alerts',
headers={
'Content-Type': 'application/json',
'Authorization': 'GenieKey ogkey'
},
json=mock.ANY,
proxies=None
)

expected_json = {
'description': BasicMatchString(rule, match).__str__(),
'details': {'Foo': 'Bar'},
'message': 'ElastAlert: Opsgenie Details',
'priority': None,
'source': 'ElastAlert',
'tags': ['ElastAlert', 'Opsgenie Details'],
'user': 'genies'
}
actual_json = mock_post_request.call_args_list[0][1]['json']
assert expected_json == actual_json


def test_opsgenie_details_with_nested_field():
rule = {
'name': 'Opsgenie Details',
'type': mock_rule(),
'opsgenie_account': 'genies',
'opsgenie_key': 'ogkey',
'opsgenie_details': {'Foo': {'field': 'nested.field'}}
}
match = {
'nested': {
'field': 'Bar'
},
'@timestamp': '2014-10-31T00:00:00'
}
alert = OpsGenieAlerter(rule)

with mock.patch('requests.post') as mock_post_request:
alert.alert([match])

mock_post_request.assert_called_once_with(
'https://api.opsgenie.com/v2/alerts',
headers={
'Content-Type': 'application/json',
'Authorization': 'GenieKey ogkey'
},
json=mock.ANY,
proxies=None
)

expected_json = {
'description': BasicMatchString(rule, match).__str__(),
'details': {'Foo': 'Bar'},
'message': 'ElastAlert: Opsgenie Details',
'priority': None,
'source': 'ElastAlert',
'tags': ['ElastAlert', 'Opsgenie Details'],
'user': 'genies'
}
actual_json = mock_post_request.call_args_list[0][1]['json']
assert expected_json == actual_json


def test_opsgenie_details_with_non_string_field():
rule = {
'name': 'Opsgenie Details',
'type': mock_rule(),
'opsgenie_account': 'genies',
'opsgenie_key': 'ogkey',
'opsgenie_details': {
'Age': {'field': 'age'},
'Message': {'field': 'message'}
}
}
match = {
'age': 10,
'message': {
'format': 'The cow goes %s!',
'arg0': 'moo'
}
}
alert = OpsGenieAlerter(rule)

with mock.patch('requests.post') as mock_post_request:
alert.alert([match])

mock_post_request.assert_called_once_with(
'https://api.opsgenie.com/v2/alerts',
headers={
'Content-Type': 'application/json',
'Authorization': 'GenieKey ogkey'
},
json=mock.ANY,
proxies=None
)

expected_json = {
'description': BasicMatchString(rule, match).__str__(),
'details': {
'Age': '10',
'Message': "{'format': 'The cow goes %s!', 'arg0': 'moo'}"
},
'message': 'ElastAlert: Opsgenie Details',
'priority': None,
'source': 'ElastAlert',
'tags': ['ElastAlert', 'Opsgenie Details'],
'user': 'genies'
}
actual_json = mock_post_request.call_args_list[0][1]['json']
assert expected_json == actual_json


def test_opsgenie_details_with_missing_field():
rule = {
'name': 'Opsgenie Details',
'type': mock_rule(),
'opsgenie_account': 'genies',
'opsgenie_key': 'ogkey',
'opsgenie_details': {
'Message': {'field': 'message'},
'Missing': {'field': 'missing'}
}
}
match = {
'message': 'Testing',
'@timestamp': '2014-10-31T00:00:00'
}
alert = OpsGenieAlerter(rule)

with mock.patch('requests.post') as mock_post_request:
alert.alert([match])

mock_post_request.assert_called_once_with(
'https://api.opsgenie.com/v2/alerts',
headers={
'Content-Type': 'application/json',
'Authorization': 'GenieKey ogkey'
},
json=mock.ANY,
proxies=None
)

expected_json = {
'description': BasicMatchString(rule, match).__str__(),
'details': {'Message': 'Testing'},
'message': 'ElastAlert: Opsgenie Details',
'priority': None,
'source': 'ElastAlert',
'tags': ['ElastAlert', 'Opsgenie Details'],
'user': 'genies'
}
actual_json = mock_post_request.call_args_list[0][1]['json']
assert expected_json == actual_json


def test_opsgenie_details_with_environment_variable_replacement(environ):
environ.update({
'TEST_VAR': 'Bar'
})
rule = {
'name': 'Opsgenie Details',
'type': mock_rule(),
'opsgenie_account': 'genies',
'opsgenie_key': 'ogkey',
'opsgenie_details': {'Foo': '$TEST_VAR'}
}
match = {
'@timestamp': '2014-10-31T00:00:00'
}
alert = OpsGenieAlerter(rule)

with mock.patch('requests.post') as mock_post_request:
alert.alert([match])

mock_post_request.assert_called_once_with(
'https://api.opsgenie.com/v2/alerts',
headers={
'Content-Type': 'application/json',
'Authorization': 'GenieKey ogkey'
},
json=mock.ANY,
proxies=None
)

expected_json = {
'description': BasicMatchString(rule, match).__str__(),
'details': {'Foo': 'Bar'},
'message': 'ElastAlert: Opsgenie Details',
'priority': None,
'source': 'ElastAlert',
'tags': ['ElastAlert', 'Opsgenie Details'],
'user': 'genies'
}
actual_json = mock_post_request.call_args_list[0][1]['json']
assert expected_json == actual_json


def test_jira():
description_txt = "Description stuff goes here like a runbook link."
rule = {
Expand Down

0 comments on commit 325f1df

Please sign in to comment.