Skip to content
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

[Opsgenie] Added possibility to specify source and entity attrs #315

Merged
merged 4 commits into from
Jul 1, 2021
Merged
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
6 changes: 5 additions & 1 deletion docs/source/ruletypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2340,12 +2340,16 @@ Optional:

``opsgenie_subject_args``: A list of fields to use to format ``opsgenie_subject`` if it contains formaters.

``opsgenie_priority``: Set the OpsGenie priority level. Possible values are P1, P2, P3, P4, P5.
``opsgenie_priority``: Set the OpsGenie priority level. Possible values are P1, P2, P3, P4, P5. Can be formatted with fields from the first match e.g "P{level}"

``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.

``opsgenie_proxy``: By default ElastAlert will not use a network proxy to send notifications to OpsGenie. Set this option using ``hostname:port`` if you need to use a proxy. only supports https.

``opsgenie_source``: Set the OpsGenie source, default is `ElastAlert`. Can be formatted with fields from the first match e.g "{source} {region}"

``opsgenie_entity``: Set the OpsGenie entity. Can be formatted with fields from the first match e.g "{host_name}"

Example usage::

opsgenie_details:
Expand Down
16 changes: 12 additions & 4 deletions elastalert/alerters/opsgenie.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ def __init__(self, *args):
self.opsgenie_proxy = self.rule.get('opsgenie_proxy', None)
self.priority = self.rule.get('opsgenie_priority')
self.opsgenie_details = self.rule.get('opsgenie_details', {})
self.entity = self.rule.get('opsgenie_entity', None)
self.source = self.rule.get('opsgenie_source', 'ElastAlert')

def _parse_responders(self, responders, responder_args, matches, default_responders):
if responder_args:
Expand Down Expand Up @@ -76,17 +78,23 @@ def alert(self, matches):
if self.teams:
post['teams'] = [{'name': r, 'type': 'team'} for r in self.teams]
post['description'] = body
post['source'] = 'ElastAlert'
if self.entity:
post['entity'] = self.entity.format(**matches[0])
if self.source:
post['source'] = self.source.format(**matches[0])

for i, tag in enumerate(self.tags):
self.tags[i] = tag.format(**matches[0])
post['tags'] = self.tags

if self.priority and self.priority not in ('P1', 'P2', 'P3', 'P4', 'P5'):
priority = self.priority
if priority:
priority = priority.format(**matches[0])
if priority and priority not in ('P1', 'P2', 'P3', 'P4', 'P5'):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Could this block be indented as well? The second priority check won't be successful if the first one fails

elastalert_logger.warning("Priority level does not appear to be specified correctly. \
Please make sure to set it to a value between P1 and P5")
Please make sure to set it to a value between P1 and P5")
else:
post['priority'] = self.priority
post['priority'] = priority

if self.alias is not None:
post['alias'] = self.alias.format(**matches[0])
Expand Down
2 changes: 2 additions & 0 deletions elastalert/schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,8 @@ properties:
opsgenie_subject: {type: string}
opsgenie_priority: {type: string}
opsgenie_proxy: {type: string}
opsgenie_source: {type: string}
opsgenie_entity: {type: string}
opsgenie_details:
type: object
minProperties: 1
Expand Down
10 changes: 8 additions & 2 deletions examples/rules/example_opsgenie_frequency.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ opsgenie_key: ogkey
# (Optional)
# OpsGenie recipients with args
# opsgenie_recipients:
# - {recipient}
# - {recipient}
# opsgenie_recipients_args:
# team_prefix:'user.email'

Expand All @@ -36,7 +36,7 @@ opsgenie_key: ogkey
# (Optional)
# OpsGenie teams with args
# opsgenie_teams:
# - {team_prefix}-Team
# - {team_prefix}-Team
# opsgenie_teams_args:
# team_prefix:'team'

Expand All @@ -45,6 +45,12 @@ opsgenie_key: ogkey
opsgenie_tags:
- "Production"

# (Optional) OpsGenie source
# opsgenie_source: ElastAlert_EMEA

# (Optional) OpsGenie entity
# opsgenie_entity: '{hostname}'

# (OptionaL) Connect with SSL to Elasticsearch
#use_ssl: True

Expand Down
38 changes: 34 additions & 4 deletions tests/alerters/opsgenie_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def test_opsgenie_basic(caplog):
assert mock_post.called

assert mcal[0][1]['headers']['Authorization'] == 'GenieKey ogkey'
assert mcal[0][1]['json']['source'] == 'ElastAlert'
# Should be default source 'ElastAlert', because 'opsgenie_source' param isn't set in rule
assert mcal[0][1]['json']['source'] == 'ElastAlert'
user, level, message = caplog.record_tuples[0]
assert "Error response from https://api.opsgenie.com/v2/alerts \n API Response: <MagicMock name='post()' id=" not in message
Expand Down Expand Up @@ -66,7 +66,6 @@ def test_opsgenie_basic_not_status_code_202(caplog):
assert mcal[0][1]['headers']['Authorization'] == 'GenieKey ogkey'
assert mcal[0][1]['json']['source'] == 'ElastAlert'
assert mcal[0][1]['json']['responders'] == [{'username': 'lytics', 'type': 'user'}]
assert mcal[0][1]['json']['source'] == 'ElastAlert'
user, level, message = caplog.record_tuples[0]
assert "Error response from https://api.opsgenie.com/v2/alerts \n API Response: <MagicMock name='post()' id=" in message
assert ('elastalert', logging.INFO, 'Alert sent to OpsGenie') == caplog.record_tuples[1]
Expand Down Expand Up @@ -98,8 +97,6 @@ def test_opsgenie_frequency():
assert mcal[0][1]['headers']['Authorization'] == 'GenieKey ogkey'
assert mcal[0][1]['json']['source'] == 'ElastAlert'
assert mcal[0][1]['json']['responders'] == [{'username': 'lytics', 'type': 'user'}]
assert mcal[0][1]['json']['source'] == 'ElastAlert'
assert mcal[0][1]['json']['source'] == 'ElastAlert'


def test_opsgenie_alert_routing():
Expand Down Expand Up @@ -878,3 +875,36 @@ def test_opsgenie_required_error(opsgenie_key, expected_data):
assert expected_data == actual_data
except Exception as ea:
assert expected_data in str(ea)


@pytest.mark.parametrize('opsgenie_entity, expected_entity, opsgenie_priority, expected_priority', [
('const host', 'const host', 'P1', 'P1'),
('host {hostname}', 'host server_a', 'P{level}', 'P2'),
('Elastalert {source}', 'Elastalert EMEA', '{priority}', 'P3'),
])
def test_opsgenie_substitution(opsgenie_entity, expected_entity, opsgenie_priority, expected_priority):
rule = {
'name': 'Opsgenie Details',
'type': mock_rule(),
'opsgenie_entity': opsgenie_entity,
'opsgenie_priority': opsgenie_priority,
}
matches = [{
'message': 'Testing',
'hostname': 'server_a',
'source': 'EMEA',
'level': '2',
'priority': 'P3',
'@timestamp': '2014-10-31T00:00:00'
}]
alert = OpsGenieAlerter(rule)

with mock.patch('requests.post') as mock_post:
alert = OpsGenieAlerter(rule)
alert.alert(matches)

mcal = mock_post._mock_call_args_list
assert mock_post.called

assert mcal[0][1]['json']['entity'] == expected_entity
assert mcal[0][1]['json']['priority'] == expected_priority