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

Don't overwrite self.teams upon parsing responders in OpsGenie integration #1539

Merged
merged 8 commits into from
Sep 27, 2024
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

## Other changes
- [Docs] Mention the two available Spike-rule metrics that are add into the match record - [#1542](https://github.com/jertel/elastalert2/pull/1542) - @ulmako
- [OpsGenie] Corrected spelling of the `opsgenie_default_receipients` configuration option to `opsgenie_default_recipients`. Both variations will continue to work and a warning message will notify affected users. [#1539](https://github.com/jertel/elastalert2/pull/1539) - @lstyles
- [OpsGenie] Prevent templated `opsgenie_teams` and `opsgenie_recipients` from being overwritten with evaluated values first time an alert is sent. [#1540](https://github.com/jertel/elastalert2/issues/1540) [#1539](https://github.com/jertel/elastalert2/pull/1539) - @lstyles

# 2.20.0

Expand Down
2 changes: 1 addition & 1 deletion docs/source/alerts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1790,7 +1790,7 @@ Optional:

``opsgenie_recipients_args``: Map of arguments used to format opsgenie_recipients.

``opsgenie_default_receipients``: List of default recipients to notify when the formatting of opsgenie_recipients is unsuccesful.
``opsgenie_default_recipients``: List of default recipients to notify when the formatting of opsgenie_recipients is unsuccesful.

``opsgenie_teams``: A list of OpsGenie teams to notify (useful for schedules with escalation).

Expand Down
61 changes: 35 additions & 26 deletions elastalert/alerters/opsgenie.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ class OpsGenieAlerter(Alerter):

def __init__(self, *args):
super(OpsGenieAlerter, self).__init__(*args)

default_recipients_deprecated = self.rule.get('opsgenie_default_receipients', None)
if default_recipients_deprecated:
elastalert_logger.warning("OpsGenieAlerter: `opsgenie_default_receipients` rule configuration option is deprecated and will be removed in the future. Please use `opsgenie_default_recipients` option instead.")

self.account = self.rule.get('opsgenie_account')
self.api_key = self.rule.get('opsgenie_key', 'key')
self.default_reciepients = self.rule.get('opsgenie_default_receipients', None)
self.default_recipients = self.rule.get('opsgenie_default_recipients', default_recipients_deprecated)
self.recipients = self.rule.get('opsgenie_recipients')
self.recipients_args = self.rule.get('opsgenie_recipients_args')
self.default_teams = self.rule.get('opsgenie_default_teams', None)
Expand All @@ -35,25 +40,29 @@ def __init__(self, *args):
self.source = self.rule.get('opsgenie_source', 'ElastAlert')

def _parse_responders(self, responders, responder_args, matches, default_responders):
if responder_args:
formated_responders = list()
responders_values = dict((k, lookup_es_key(matches[0], v)) for k, v in responder_args.items())
responders_values = dict((k, v) for k, v in responders_values.items() if v)

for responder in responders:
responder = str(responder)
try:
formated_responders.append(responder.format(**responders_values))
except KeyError as error:
elastalert_logger.warning("OpsGenieAlerter: Cannot create responder for OpsGenie Alert. Key not foud: %s. " % (error))
if not formated_responders:
elastalert_logger.warning("OpsGenieAlerter: no responders can be formed. Trying the default responder ")
if not default_responders:
elastalert_logger.warning("OpsGenieAlerter: default responder not set. Falling back")
formated_responders = responders
else:
formated_responders = default_responders
responders = formated_responders
if responders is None:
return None
if responder_args is None:
responder_args = dict()
Comment on lines +43 to +46
Copy link
Contributor Author

@lstyles lstyles Sep 25, 2024

Choose a reason for hiding this comment

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

The top two lines prevent other tests from failing as missing responder_args no longer skips the execution.

The bottom two lines fix a scenario where responders contains a placeholder, but responders_args isn't configured in a rule at all. Previously, a text with placeholder would end up as a responder. Now, a warning will be shown and default_responders will be used instead.


formated_responders = list()
responders_values = dict((k, lookup_es_key(matches[0], v)) for k, v in responder_args.items())
responders_values = dict((k, v) for k, v in responders_values.items() if v)
for responder in responders:
responder = str(responder)
try:
formated_responders.append(responder.format(**responders_values))
except KeyError as error:
elastalert_logger.warning("OpsGenieAlerter: Cannot create responder for OpsGenie Alert. Key not found: %s. " % (error))
if not formated_responders:
elastalert_logger.warning("OpsGenieAlerter: no responders can be formed. Trying the default responder ")
if not default_responders:
elastalert_logger.warning("OpsGenieAlerter: default responder not set. Falling back")
formated_responders = responders
else:
formated_responders = default_responders
responders = formated_responders

return responders

def alert(self, matches):
Expand All @@ -68,16 +77,16 @@ def alert(self, matches):
self.message = self.create_title(matches)
else:
self.message = self.custom_message.format(**matches[0])
self.recipients = self._parse_responders(self.recipients, self.recipients_args, matches, self.default_reciepients)
self.teams = self._parse_responders(self.teams, self.teams_args, matches, self.default_teams)
recipients = self._parse_responders(self.recipients, self.recipients_args, matches, self.default_recipients)
teams = self._parse_responders(self.teams, self.teams_args, matches, self.default_teams)
post = {}
post['message'] = self.message
if self.account:
post['user'] = self.account
if self.recipients:
post['responders'] = [{'username': r, 'type': 'user'} for r in self.recipients]
if self.teams:
post['teams'] = [{'name': r, 'type': 'team'} for r in self.teams]
if recipients:
post['responders'] = [{'username': r, 'type': 'user'} for r in recipients]
if teams:
post['teams'] = [{'name': r, 'type': 'team'} for r in teams]
if self.description:
post['description'] = self.description.format(**matches[0])
else:
Expand Down
105 changes: 88 additions & 17 deletions tests/alerters/opsgenie_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,21 +105,23 @@ def test_opsgenie_alert_routing():
'opsgenie_key': 'ogkey',
'opsgenie_account': 'genies',
'opsgenie_addr': 'https://api.opsgenie.com/v2/alerts',
'opsgenie_recipients': ['{RECEIPIENT_PREFIX}'],
'opsgenie_recipients_args': {'RECEIPIENT_PREFIX': 'recipient'},
'opsgenie_recipients': ['{RECIPIENT_PREFIX}'],
'opsgenie_recipients_args': {'RECIPIENT_PREFIX': 'recipient'},
'type': mock_rule(),
'filter': [{'query': {'query_string': {'query': '*hihi*'}}}],
'alert': 'opsgenie',
'opsgenie_teams': ['{TEAM_PREFIX}-Team'],
'opsgenie_teams_args': {'TEAM_PREFIX': 'team'}
}
with mock.patch('requests.post'):

with mock.patch('requests.post') as mock_post:
alert = OpsGenieAlerter(rule)
alert.alert([{'@timestamp': '2014-10-31T00:00:00', 'team': "Test", 'recipient': "lytics"}])

assert alert.get_info()['teams'] == ['Test-Team']
assert alert.get_info()['recipients'] == ['lytics']
_, kwargs = mock_post.call_args
payload = kwargs['json']

assert payload['teams'][0]['name'] == 'Test-Team'
assert payload['responders'][0]['username'] == 'lytics'


def test_opsgenie_default_alert_routing():
Expand All @@ -128,22 +130,24 @@ def test_opsgenie_default_alert_routing():
'opsgenie_key': 'ogkey',
'opsgenie_account': 'genies',
'opsgenie_addr': 'https://api.opsgenie.com/v2/alerts',
'opsgenie_recipients': ['{RECEIPIENT_PREFIX}'],
'opsgenie_recipients_args': {'RECEIPIENT_PREFIX': 'recipient'},
'opsgenie_recipients': ['{RECIPIENT_PREFIX}'],
'opsgenie_recipients_args': {'RECIPIENT_PREFIX': 'recipient'},
'type': mock_rule(),
'filter': [{'query': {'query_string': {'query': '*hihi*'}}}],
'alert': 'opsgenie',
'opsgenie_teams': ['{TEAM_PREFIX}-Team'],
'opsgenie_default_receipients': ["[email protected]"],
'opsgenie_default_teams': ["Test"]
'opsgenie_default_recipients': ["[email protected]"],
'opsgenie_default_teams': ["Default-Team"]
}
with mock.patch('requests.post'):
with mock.patch('requests.post') as mock_post:

alert = OpsGenieAlerter(rule)
alert.alert([{'@timestamp': '2014-10-31T00:00:00', 'team': "Test"}])

assert alert.get_info()['teams'] == ['{TEAM_PREFIX}-Team']
assert alert.get_info()['recipients'] == ['[email protected]']
_, kwargs = mock_post.call_args
payload = kwargs['json']
assert payload['teams'][0]['name'] == 'Default-Team'
assert payload['responders'][0]['username'] == '[email protected]'


def test_opsgenie_details_with_constant_value():
Expand Down Expand Up @@ -1006,8 +1010,8 @@ def test_opsgenie_parse_responders(caplog):
'opsgenie_key': 'ogkey',
'opsgenie_account': 'genies',
'opsgenie_addr': 'https://api.opsgenie.com/v2/alerts',
'opsgenie_recipients': ['{RECEIPIENT_PREFIX}'],
'opsgenie_recipients_args': {'RECEIPIENT_PREFIX': 'recipient'},
'opsgenie_recipients': ['{RECIPIENT_PREFIX}'],
'opsgenie_recipients_args': {'RECIPIENT_PREFIX': 'recipient'},
'type': mock_rule(),
'filter': [{'query': {'query_string': {'query': '*hihi*'}}}],
'alert': 'opsgenie',
Expand Down Expand Up @@ -1040,7 +1044,7 @@ def test_opsgenie_parse_responders(caplog):
assert expected == actual
user, level, message = caplog.record_tuples[0]
assert logging.WARNING == level
assert "Cannot create responder for OpsGenie Alert. Key not foud: 'RECEIPIENT_PREFIX'." in message
assert "Cannot create responder for OpsGenie Alert. Key not found: 'RECIPIENT_PREFIX'." in message
user, level, message = caplog.record_tuples[1]
assert logging.WARNING == level
assert 'no responders can be formed. Trying the default responder' in message
Expand All @@ -1049,12 +1053,79 @@ def test_opsgenie_parse_responders(caplog):
assert 'default responder not set. Falling back' in message
user, level, message = caplog.record_tuples[3]
assert logging.WARNING == level
assert "Cannot create responder for OpsGenie Alert. Key not foud: 'TEAM_PREFIX'." in message
assert "Cannot create responder for OpsGenie Alert. Key not found: 'TEAM_PREFIX'." in message
user, level, message = caplog.record_tuples[4]
assert logging.WARNING == level
assert 'no responders can be formed. Trying the default responder' in message


def test_opsgenie_parse_opsgenie_teams():
rule = {
'name': 'testOGalert',
'opsgenie_key': 'ogkey',
'opsgenie_account': 'genies',
'opsgenie_addr': 'https://api.opsgenie.com/v2/alerts',
'type': mock_rule(),
'filter': [{'query': {'query_string': {'query': '*hihi*'}}}],
'alert': 'opsgenie',
'opsgenie_teams': ['{TEAM_PREFIX}-Team'],
'opsgenie_teams_args': {'TEAM_PREFIX': 'team'},
'opsgenie_default_teams': ["Test"]
}
match = [
{
'@timestamp': '2014-10-10T00:00:00',
'sender_ip': '1.1.1.1',
'hostname': 'aProbe',
'team': 'Test'
},
{
'@timestamp': '2014-10-10T00:00:00',
'sender_ip': '1.1.1.1',
'hostname2': 'aProbe',
'team': 'Test'
}
]

with mock.patch('requests.post'):
alert = OpsGenieAlerter(rule)
alert.alert(match)
assert alert.teams == rule['opsgenie_teams']


def test_opsgenie_parse_opsgenie_recipients():
rule = {
'name': 'testOGalert',
'opsgenie_key': 'ogkey',
'opsgenie_account': 'genies',
'opsgenie_addr': 'https://api.opsgenie.com/v2/alerts',
'opsgenie_recipients': ['{RECIPIENT_PREFIX}'],
'opsgenie_recipients_args': {'RECIPIENT_PREFIX': 'recipient'},
'type': mock_rule(),
'filter': [{'query': {'query_string': {'query': '*hihi*'}}}],
'alert': 'opsgenie'
}
match = [
{
'@timestamp': '2014-10-10T00:00:00',
'sender_ip': '1.1.1.1',
'hostname': 'aProbe',
'recipient': 'Test'
},
{
'@timestamp': '2014-10-10T00:00:00',
'sender_ip': '1.1.1.1',
'hostname2': 'aProbe',
'recipient': 'Test'
}
]

with mock.patch('requests.post'):
alert = OpsGenieAlerter(rule)
alert.alert(match)
assert alert.recipients == rule['opsgenie_recipients']


def test_opsgenie_create_custom_title():
rule = {
'name': 'Opsgenie Details',
Expand Down