From 77eb6035bd22a19ce082852769e1584f6c8aeec4 Mon Sep 17 00:00:00 2001 From: lepouletsuisse Date: Mon, 1 Nov 2021 09:49:41 +0100 Subject: [PATCH 1/8] Add new alerter httppost2 --- elastalert/alerters/httppost2.py | 75 ++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 elastalert/alerters/httppost2.py diff --git a/elastalert/alerters/httppost2.py b/elastalert/alerters/httppost2.py new file mode 100644 index 00000000..2a34a5fe --- /dev/null +++ b/elastalert/alerters/httppost2.py @@ -0,0 +1,75 @@ +import json + +import requests +from requests import RequestException + +from elastalert.alerts import Alerter, DateTimeEncoder +from elastalert.util import lookup_es_key, EAException, elastalert_logger +from jinja2 import Template + + +class HTTPPost2Alerter(Alerter): + """ Requested elasticsearch indices are sent by HTTP POST. Encoded with JSON. """ + required_options = frozenset(['http_post_url']) + + def __init__(self, rule): + super(HTTPPost2Alerter, self).__init__(rule) + post_url = self.rule.get('http_post_url', None) + if isinstance(post_url, str): + post_url = [post_url] + self.post_url = post_url + self.post_proxy = self.rule.get('http_post_proxy', None) + self.post_payload = self.rule.get('http_post_payload', {}) + self.post_raw_fields = self.rule.get('http_post_raw_fields', {}) + self.post_all_values = self.rule.get('http_post_all_values', not self.post_payload) + self.post_http_headers = self.rule.get('http_post_headers', {}) + self.post_ca_certs = self.rule.get('http_post_ca_certs') + self.post_ignore_ssl_errors = self.rule.get('http_post_ignore_ssl_errors', False) + self.timeout = self.rule.get('http_post_timeout', 10) + + def alert(self, matches): + """ Each match will trigger a POST to the specified endpoint(s). """ + for match in matches: + payload = match if self.post_all_values else {} + for post_key, post_value in list(self.post_payload.items()): + post_key_template = Template(post_key) + post_key_res = post_key_template.render(**match) + post_value_template = Template(post_value) + post_value_res = post_value_template.render(**match) + payload[post_key_res] = post_value_res + + for post_key, es_key in list(self.post_raw_fields.items()): + payload[post_key] = lookup_es_key(match, es_key) + + headers = { + "Content-Type": "application/json", + "Accept": "application/json;charset=utf-8" + } + if self.post_ca_certs: + verify = self.post_ca_certs + else: + verify = not self.post_ignore_ssl_errors + if self.post_ignore_ssl_errors: + requests.packages.urllib3.disable_warnings() + + for header_key, header_value in list(self.post_http_headers.items()): + header_key_template = Template(header_key) + header_key_res = header_key_template.render(**match) + header_value_template = Template(header_value) + header_value_res = header_value_template.render(**match) + headers[header_key_res] = header_value_res + + proxies = {'https': self.post_proxy} if self.post_proxy else None + for url in self.post_url: + try: + response = requests.post(url, data=json.dumps(payload, cls=DateTimeEncoder), + headers=headers, proxies=proxies, timeout=self.timeout, + verify=verify) + response.raise_for_status() + except RequestException as e: + raise EAException("Error posting HTTP Post alert: %s" % e) + elastalert_logger.info("HTTP Post alert sent.") + + def get_info(self): + return {'type': 'http_post', + 'http_post_webhook_url': self.post_url} From 0c1d8013971b3de25cbdc2d3acd45ac48ec31a20 Mon Sep 17 00:00:00 2001 From: lepouletsuisse Date: Mon, 1 Nov 2021 09:49:52 +0100 Subject: [PATCH 2/8] Add new alerter httppost2 tests --- tests/alerters/httppost2_test.py | 737 +++++++++++++++++++++++++++++++ 1 file changed, 737 insertions(+) create mode 100644 tests/alerters/httppost2_test.py diff --git a/tests/alerters/httppost2_test.py b/tests/alerters/httppost2_test.py new file mode 100644 index 00000000..c50f371b --- /dev/null +++ b/tests/alerters/httppost2_test.py @@ -0,0 +1,737 @@ +import json +import logging +import pytest + +from unittest import mock + +from requests import RequestException + +from elastalert.alerters.httppost2 import HTTPPost2Alerter +from elastalert.loaders import FileRulesLoader +from elastalert.util import EAException + + +def test_http_alerter_with_payload(caplog): + caplog.set_level(logging.INFO) + rule = { + 'name': 'Test HTTP Post Alerter With Payload', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_payload': {'posted_name': 'toto'}, + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'somefield': 'foobarbaz' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_data = { + 'posted_name': 'toto', + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, + proxies=None, + timeout=10, + verify=True + ) + assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) + assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + + +def test_http_alerter_with_payload_raw_fields(caplog): + caplog.set_level(logging.INFO) + rule = { + 'name': 'Test HTTP Post Alerter With Payload and raw fields', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_payload': {'posted_name': 'toto'}, + 'http_post_raw_fields': {'posted_raw_field': 'somefield'}, + 'http_post_static_payload': {'name': 'somestaticname'}, + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'somefield': 'foobarbaz' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_data = { + 'posted_name': 'toto', + 'posted_raw_field': 'foobarbaz' + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, + proxies=None, + timeout=10, + verify=True + ) + assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) + assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + + +def test_http_alerter_with_payload_raw_fields_overwrite(caplog): + caplog.set_level(logging.INFO) + rule = { + 'name': 'Test HTTP Post Alerter raw fields overwrite payload', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_payload': {'posted_name': 'toto', 'overwrite_field': 'tata'}, + 'http_post_raw_fields': {'overwrite_field': 'somefield'}, + 'http_post_static_payload': {'name': 'somestaticname'}, + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'somefield': 'foobarbaz' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_data = { + 'posted_name': 'toto', + 'overwrite_field': 'foobarbaz' + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, + proxies=None, + timeout=10, + verify=True + ) + assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) + assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + + +def test_http_alerter_with_payload_no_clash(caplog): + caplog.set_level(logging.INFO) + rule = { + 'name': 'Test HTTP Post Alerter With Payload has no clash with the match fields', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_payload': {'posted_name': 'toto'}, + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'toto': 'foobarbaz' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_data = { + 'posted_name': 'toto', + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, + proxies=None, + timeout=10, + verify=True + ) + assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) + assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + + +def test_http_alerter_with_payload_args_keys(caplog): + caplog.set_level(logging.INFO) + rule = { + 'name': 'Test HTTP Post Alerter With Payload args for the key', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_payload': {'args_{{some_field}}': 'tata'}, + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'some_field': 'toto' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_data = { + 'args_toto': 'tata', + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, + proxies=None, + timeout=10, + verify=True + ) + assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) + assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + + +def test_http_alerter_with_payload_args_key_not_found(caplog): + caplog.set_level(logging.INFO) + rule = { + 'name': 'Test HTTP Post Alerter With Payload args for the key if not found', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_payload': {'args_{{some_field1}}': 'tata'}, + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'some_field': 'toto' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_data = { + 'args_': 'tata', + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, + proxies=None, + timeout=10, + verify=True + ) + assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) + assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + + +def test_http_alerter_with_payload_args_value(caplog): + caplog.set_level(logging.INFO) + rule = { + 'name': 'Test HTTP Post Alerter With Payload args for the value', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_payload': {'posted_name': 'toto', 'args_name': '{{some_field}}'}, + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'some_field': 'foobarbaz' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_data = { + 'posted_name': 'toto', + 'args_name': 'foobarbaz', + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, + proxies=None, + timeout=10, + verify=True + ) + assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) + assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + + +def test_http_alerter_with_payload_args_value_not_found(caplog): + caplog.set_level(logging.INFO) + rule = { + 'name': 'Test HTTP Post Alerter With Payload args for the value if not found', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_payload': {'posted_name': 'toto', 'args_name': '{{some_field1}}'}, + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'some_field': 'foobarbaz' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_data = { + 'posted_name': 'toto', + 'args_name': '', + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, + proxies=None, + timeout=10, + verify=True + ) + assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) + assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + + +def test_http_alerter_with_header_no_clash(caplog): + caplog.set_level(logging.INFO) + rule = { + 'name': 'Test HTTP Post Alerter With Headers has no clash with the match fields', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_headers': {'header_name': 'titi'}, + 'http_post_payload': {'posted_name': 'toto'}, + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'titi': 'foobarbaz' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json;charset=utf-8', + 'header_name': 'titi' + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers=expected_headers, + proxies=None, + timeout=10, + verify=True + ) + assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + + +def test_http_alerter_with_header_args_value(caplog): + caplog.set_level(logging.INFO) + rule = { + 'name': 'Test HTTP Post Alerter With Headers args value', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_headers': {'header_name': '{{titi}}'}, + 'http_post_payload': {'posted_name': 'toto'}, + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'titi': 'foobarbaz' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json;charset=utf-8', + 'header_name': 'foobarbaz' + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers=expected_headers, + proxies=None, + timeout=10, + verify=True + ) + assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + + +def test_http_alerter_with_header_args_value_not_found(caplog): + caplog.set_level(logging.INFO) + rule = { + 'name': 'Test HTTP Post Alerter With Headers args value if not found', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_headers': {'header_name': '{{titi1}}'}, + 'http_post_payload': {'posted_name': 'toto'}, + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'titi': 'foobarbaz' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json;charset=utf-8', + 'header_name': '' + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers=expected_headers, + proxies=None, + timeout=10, + verify=True + ) + assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + + +def test_http_alerter_with_header_args_key(caplog): + caplog.set_level(logging.INFO) + rule = { + 'name': 'Test HTTP Post Alerter With Headers args key', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_headers': {'header_{{titi}}': 'tata'}, + 'http_post_payload': {'posted_name': 'toto'}, + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'titi': 'foobarbaz' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json;charset=utf-8', + 'header_foobarbaz': 'tata' + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers=expected_headers, + proxies=None, + timeout=10, + verify=True + ) + assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + + +def test_http_alerter_with_header_args_key_not_found(caplog): + caplog.set_level(logging.INFO) + rule = { + 'name': 'Test HTTP Post Alerter With Headers args key if not found', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_headers': {'header_{{titi1}}': 'tata'}, + 'http_post_payload': {'posted_name': 'toto'}, + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'titi': 'foobarbaz' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json;charset=utf-8', + 'header_': 'tata' + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers=expected_headers, + proxies=None, + timeout=10, + verify=True + ) + assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + + +def test_http_alerter_with_payload_all_values(): + rule = { + 'name': 'Test HTTP Post Alerter With Payload', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_payload': {'posted_name': 'toto'}, + 'http_post_all_values': True, + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'somefield': 'foobarbaz' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_data = { + 'posted_name': 'toto', + '@timestamp': '2017-01-01T00:00:00', + 'somefield': 'foobarbaz' + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, + proxies=None, + timeout=10, + verify=True + ) + assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) + + +def test_http_alerter_without_payload(): + rule = { + 'name': 'Test HTTP Post Alerter Without Payload', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'somefield': 'foobarbaz' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_data = { + '@timestamp': '2017-01-01T00:00:00', + 'somefield': 'foobarbaz', + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, + proxies=None, + timeout=10, + verify=True + ) + assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) + + +def test_http_alerter_proxy(): + rule = { + 'name': 'Test HTTP Post Alerter Without Payload', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_proxy': 'http://proxy.url', + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'somefield': 'foobarbaz' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_data = { + '@timestamp': '2017-01-01T00:00:00', + 'somefield': 'foobarbaz', + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, + proxies={'https': 'http://proxy.url'}, + timeout=10, + verify=True + ) + assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) + + +def test_http_alerter_timeout(): + rule = { + 'name': 'Test HTTP Post Alerter Without Payload', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_timeout': 20, + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'somefield': 'foobarbaz' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_data = { + '@timestamp': '2017-01-01T00:00:00', + 'somefield': 'foobarbaz', + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, + proxies=None, + timeout=20, + verify=True + ) + assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) + + +def test_http_alerter_headers(): + rule = { + 'name': 'Test HTTP Post Alerter Without Payload', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_headers': {'authorization': 'Basic 123dr3234'}, + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'somefield': 'foobarbaz' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_data = { + '@timestamp': '2017-01-01T00:00:00', + 'somefield': 'foobarbaz', + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8', 'authorization': 'Basic 123dr3234'}, + proxies=None, + timeout=10, + verify=True + ) + assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) + + +@pytest.mark.parametrize('ca_certs, ignore_ssl_errors, excpet_verify', [ + ('', '', True), + ('', True, False), + ('', False, True), + (True, '', True), + (True, True, True), + (True, False, True), + (False, '', True), + (False, True, False), + (False, False, True) +]) +def test_http_alerter_post_ca_certs(ca_certs, ignore_ssl_errors, excpet_verify): + rule = { + 'name': 'Test HTTP Post Alerter Without Payload', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'alert': [] + } + if ca_certs: + rule['http_post_ca_certs'] = ca_certs + + if ignore_ssl_errors: + rule['http_post_ignore_ssl_errors'] = ignore_ssl_errors + + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'somefield': 'foobarbaz' + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_data = { + '@timestamp': '2017-01-01T00:00:00', + 'somefield': 'foobarbaz', + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, + proxies=None, + timeout=10, + verify=excpet_verify + ) + assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) + + +def test_http_alerter_post_ea_exception(): + with pytest.raises(EAException) as ea: + rule = { + 'name': 'Test HTTP Post Alerter Without Payload', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_ca_certs': False, + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'somefield': 'foobarbaz' + } + mock_run = mock.MagicMock(side_effect=RequestException) + with mock.patch('requests.post', mock_run), pytest.raises(RequestException): + alert.alert([match]) + assert 'Error posting HTTP Post alert: ' in str(ea) + + +def test_http_getinfo(): + rule = { + 'name': 'Test HTTP Post Alerter Without Payload', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + + expected_data = { + 'type': 'http_post', + 'http_post_webhook_url': ['http://test.webhook.url'] + } + actual_data = alert.get_info() + assert expected_data == actual_data + + +@pytest.mark.parametrize('http_post_url, expected_data', [ + ('', 'Missing required option(s): http_post_url'), + ('http://test.webhook.url', + { + 'type': 'http_post', + 'http_post_webhook_url': ['http://test.webhook.url'] + }), +]) +def test_http_required_error(http_post_url, expected_data): + try: + rule = { + 'name': 'Test HTTP Post Alerter Without Payload', + 'type': 'any', + 'alert': [] + } + + if http_post_url: + rule['http_post_url'] = http_post_url + + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + + actual_data = alert.get_info() + assert expected_data == actual_data + except Exception as ea: + assert expected_data in str(ea) From 0d6c891dc540c74a7cff6b502375744d13523288 Mon Sep 17 00:00:00 2001 From: lepouletsuisse Date: Mon, 1 Nov 2021 12:56:56 +0100 Subject: [PATCH 3/8] Test nested field works + Documentation for HTTP Post 2 --- docs/source/elastalert.rst | 1 + docs/source/ruletypes.rst | 39 ++++++++++++++++++++++++++++++++ tests/alerters/httppost2_test.py | 33 +++++++++++++++++++++++++++ 3 files changed, 73 insertions(+) diff --git a/docs/source/elastalert.rst b/docs/source/elastalert.rst index d939ecdc..bcec23b4 100755 --- a/docs/source/elastalert.rst +++ b/docs/source/elastalert.rst @@ -42,6 +42,7 @@ Currently, we have support built in for these alert types: - Gitter - GoogleChat - HTTP POST +- HTTP POST 2 - Jira - Line Notify - Mattermost diff --git a/docs/source/ruletypes.rst b/docs/source/ruletypes.rst index 6417f81e..50a22cea 100644 --- a/docs/source/ruletypes.rst +++ b/docs/source/ruletypes.rst @@ -2198,6 +2198,45 @@ Optional: ``http_post_ignore_ssl_errors``: By default ElastAlert 2 will verify SSL certificate. Set this option to ``False`` if you want to ignore SSL errors. +Example usage:: + + alert: post + http_post_url: "http://example.com/api" + http_post_payload: + ip: clientip + http_post_static_payload: + apikey: abc123 + http_post_headers: + authorization: Basic 123dr3234 + +HTTP POST 2 +~~~~~~~~~ + +This alert type will send results to a JSON endpoint using HTTP POST. The key names are configurable so this is compatible with almost any endpoint. By default, the JSON will contain all the items from the match, unless you specify http_post_payload, in which case it will only contain those items. +This alert is a more flexible version of the HTTP Post alerter. + +Required: + +``http_post_url``: The URL to POST. + +Optional: + +``http_post_payload``: List of keys:values to use for the payload of the HTTP Post. You can use {{ field }} (Jinja2 template) in the key and the value to reference any field in the matched events (works for nested fields). If not defined, all the Elasticsearch keys will be sent. Ex: `"description_{{ my_field }}": "Type: {{ type }}\\nSubject: {{ title }}"` + +``http_post_raw_fields``: List of keys:values to use as the content of the POST. Example - ip:clientip will map the value from the clientip field of Elasticsearch to JSON key named ip. This field overwrite the keys with the same name in `http_post_payload`. + +``http_post_headers``: List of keys:values to use for as headers of the HTTP Post. You can use {{ field }} (Jinja2 template) in the key and the value to reference any field in the matched events (works for nested fields). Ex: `"Authorization": "{{ user }}"`. Headers `"Content-Type": "application/json"` and `"Accept": "application/json;charset=utf-8"` are present by default, you can overwrite them if you think this is necessary. + +``http_post_proxy``: URL of proxy, if required. only supports https. + +``http_post_all_values``: Boolean of whether or not to include every key value pair from the match in addition to those in http_post_payload and http_post_static_payload. Defaults to True if http_post_payload is not specified, otherwise False. + +``http_post_timeout``: The timeout value, in seconds, for making the post. The default is 10. If a timeout occurs, the alert will be retried next time elastalert cycles. + +``http_post_ca_certs``: Set this option to ``True`` if you want to validate the SSL certificate. + +``http_post_ignore_ssl_errors``: By default ElastAlert 2 will verify SSL certificate. Set this option to ``False`` if you want to ignore SSL errors. + Example usage:: alert: post diff --git a/tests/alerters/httppost2_test.py b/tests/alerters/httppost2_test.py index c50f371b..24231034 100644 --- a/tests/alerters/httppost2_test.py +++ b/tests/alerters/httppost2_test.py @@ -458,6 +458,39 @@ def test_http_alerter_with_header_args_key_not_found(caplog): assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] +def test_http_alerter_with_payload_nested(caplog): + caplog.set_level(logging.INFO) + rule = { + 'name': 'Test HTTP Post Alerter With Payload', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'http_post_payload': {'posted_name': '{{ toto.tata }}'}, + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPost2Alerter(rule) + match = { + '@timestamp': '2017-01-01T00:00:00', + 'toto': {'tata': 'titi'} + } + with mock.patch('requests.post') as mock_post_request: + alert.alert([match]) + expected_data = { + 'posted_name': 'titi', + } + mock_post_request.assert_called_once_with( + rule['http_post_url'], + data=mock.ANY, + headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, + proxies=None, + timeout=10, + verify=True + ) + assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) + assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + + def test_http_alerter_with_payload_all_values(): rule = { 'name': 'Test HTTP Post Alerter With Payload', From 18affed5d110de9e90c4d91c216ab8e70b95e6be Mon Sep 17 00:00:00 2001 From: lepouletsuisse Date: Mon, 1 Nov 2021 13:05:04 +0100 Subject: [PATCH 4/8] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eaea15b5..8e221b00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - [Alertmanager] Added support for Alertmanager - [#503](https://github.com/jertel/elastalert2/pull/503) - @nsano-rururu - Add summary_table_max_rows optional configuration to limit rows in summary tables - [#508](https://github.com/jertel/elastalert2/pull/508) - @mdavyt92 - Added support for shortening Kibana Discover URLs using Kibana Shorten URL API - [#512](https://github.com/jertel/elastalert2/pull/512) - @JeffAshton +- Added new alerter `HTTP Post 2` which allow more flexibility to build the body/headers of the request. ## Other changes - [Docs] Add exposed metrics documentation - [#498](https://github.com/jertel/elastalert2/pull/498) - @thisisxgp From dc4e752d0ec1371bb7f0aa8ffa15964f87c84b1d Mon Sep 17 00:00:00 2001 From: lepouletsuisse Date: Mon, 1 Nov 2021 13:24:33 +0100 Subject: [PATCH 5/8] Fix example usage for HTTP Post 2 --- docs/source/ruletypes.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/source/ruletypes.rst b/docs/source/ruletypes.rst index 50a22cea..45bb66c7 100644 --- a/docs/source/ruletypes.rst +++ b/docs/source/ruletypes.rst @@ -2210,7 +2210,7 @@ Example usage:: authorization: Basic 123dr3234 HTTP POST 2 -~~~~~~~~~ +~~~~~~~~~~~ This alert type will send results to a JSON endpoint using HTTP POST. The key names are configurable so this is compatible with almost any endpoint. By default, the JSON will contain all the items from the match, unless you specify http_post_payload, in which case it will only contain those items. This alert is a more flexible version of the HTTP Post alerter. @@ -2242,11 +2242,13 @@ Example usage:: alert: post http_post_url: "http://example.com/api" http_post_payload: + description: "An event came from IP {{clientip}}" + username: "{{user.name}}" + http_post_raw_fields: ip: clientip - http_post_static_payload: - apikey: abc123 http_post_headers: authorization: Basic 123dr3234 + X-custom-type: {{type}} Jira ~~~~ From 59d71c6e1d9d52276491c9c1051a3f6183781184 Mon Sep 17 00:00:00 2001 From: lepouletsuisse Date: Mon, 1 Nov 2021 16:30:02 +0100 Subject: [PATCH 6/8] Add post2 to the loader + use http_post2_* instead of http_post_* for HTTP Post 2 --- CHANGELOG.md | 2 +- docs/source/ruletypes.rst | 28 +++--- elastalert/alerters/httppost2.py | 24 ++--- elastalert/loaders.py | 2 + tests/alerters/httppost2_test.py | 164 +++++++++++++++---------------- 5 files changed, 111 insertions(+), 109 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e221b00..7a1260fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ - [Alertmanager] Added support for Alertmanager - [#503](https://github.com/jertel/elastalert2/pull/503) - @nsano-rururu - Add summary_table_max_rows optional configuration to limit rows in summary tables - [#508](https://github.com/jertel/elastalert2/pull/508) - @mdavyt92 - Added support for shortening Kibana Discover URLs using Kibana Shorten URL API - [#512](https://github.com/jertel/elastalert2/pull/512) - @JeffAshton -- Added new alerter `HTTP Post 2` which allow more flexibility to build the body/headers of the request. +- Added new alerter `HTTP Post 2` which allow more flexibility to build the body/headers of the request. - [#512](https://github.com/jertel/elastalert2/pull/530) - @lepouletsuisse ## Other changes - [Docs] Add exposed metrics documentation - [#498](https://github.com/jertel/elastalert2/pull/498) - @thisisxgp diff --git a/docs/source/ruletypes.rst b/docs/source/ruletypes.rst index 45bb66c7..c8e38051 100644 --- a/docs/source/ruletypes.rst +++ b/docs/source/ruletypes.rst @@ -2217,36 +2217,36 @@ This alert is a more flexible version of the HTTP Post alerter. Required: -``http_post_url``: The URL to POST. +``http_post2_url``: The URL to POST. Optional: -``http_post_payload``: List of keys:values to use for the payload of the HTTP Post. You can use {{ field }} (Jinja2 template) in the key and the value to reference any field in the matched events (works for nested fields). If not defined, all the Elasticsearch keys will be sent. Ex: `"description_{{ my_field }}": "Type: {{ type }}\\nSubject: {{ title }}"` +``http_post2_payload``: List of keys:values to use for the payload of the HTTP Post. You can use {{ field }} (Jinja2 template) in the key and the value to reference any field in the matched events (works for nested fields). If not defined, all the Elasticsearch keys will be sent. Ex: `"description_{{ my_field }}": "Type: {{ type }}\\nSubject: {{ title }}"` -``http_post_raw_fields``: List of keys:values to use as the content of the POST. Example - ip:clientip will map the value from the clientip field of Elasticsearch to JSON key named ip. This field overwrite the keys with the same name in `http_post_payload`. +``http_post2_raw_fields``: List of keys:values to use as the content of the POST. Example - ip:clientip will map the value from the clientip field of Elasticsearch to JSON key named ip. This field overwrite the keys with the same name in `http_post2_payload`. -``http_post_headers``: List of keys:values to use for as headers of the HTTP Post. You can use {{ field }} (Jinja2 template) in the key and the value to reference any field in the matched events (works for nested fields). Ex: `"Authorization": "{{ user }}"`. Headers `"Content-Type": "application/json"` and `"Accept": "application/json;charset=utf-8"` are present by default, you can overwrite them if you think this is necessary. +``http_post2_headers``: List of keys:values to use for as headers of the HTTP Post. You can use {{ field }} (Jinja2 template) in the key and the value to reference any field in the matched events (works for nested fields). Ex: `"Authorization": "{{ user }}"`. Headers `"Content-Type": "application/json"` and `"Accept": "application/json;charset=utf-8"` are present by default, you can overwrite them if you think this is necessary. -``http_post_proxy``: URL of proxy, if required. only supports https. +``http_post2_proxy``: URL of proxy, if required. only supports https. -``http_post_all_values``: Boolean of whether or not to include every key value pair from the match in addition to those in http_post_payload and http_post_static_payload. Defaults to True if http_post_payload is not specified, otherwise False. +``http_post2_all_values``: Boolean of whether or not to include every key value pair from the match in addition to those in http_post2_payload and http_post2_static_payload. Defaults to True if http_post2_payload is not specified, otherwise False. -``http_post_timeout``: The timeout value, in seconds, for making the post. The default is 10. If a timeout occurs, the alert will be retried next time elastalert cycles. +``http_post2_timeout``: The timeout value, in seconds, for making the post. The default is 10. If a timeout occurs, the alert will be retried next time elastalert cycles. -``http_post_ca_certs``: Set this option to ``True`` if you want to validate the SSL certificate. +``http_post2_ca_certs``: Set this option to ``True`` if you want to validate the SSL certificate. -``http_post_ignore_ssl_errors``: By default ElastAlert 2 will verify SSL certificate. Set this option to ``False`` if you want to ignore SSL errors. +``http_post2_ignore_ssl_errors``: By default ElastAlert 2 will verify SSL certificate. Set this option to ``False`` if you want to ignore SSL errors. Example usage:: - alert: post - http_post_url: "http://example.com/api" - http_post_payload: + alert: post2 + http_post2_url: "http://example.com/api" + http_post2_payload: description: "An event came from IP {{clientip}}" username: "{{user.name}}" - http_post_raw_fields: + http_post2_raw_fields: ip: clientip - http_post_headers: + http_post2_headers: authorization: Basic 123dr3234 X-custom-type: {{type}} diff --git a/elastalert/alerters/httppost2.py b/elastalert/alerters/httppost2.py index 2a34a5fe..aec32e91 100644 --- a/elastalert/alerters/httppost2.py +++ b/elastalert/alerters/httppost2.py @@ -10,22 +10,22 @@ class HTTPPost2Alerter(Alerter): """ Requested elasticsearch indices are sent by HTTP POST. Encoded with JSON. """ - required_options = frozenset(['http_post_url']) + required_options = frozenset(['http_post2_url']) def __init__(self, rule): super(HTTPPost2Alerter, self).__init__(rule) - post_url = self.rule.get('http_post_url', None) + post_url = self.rule.get('http_post2_url', None) if isinstance(post_url, str): post_url = [post_url] self.post_url = post_url - self.post_proxy = self.rule.get('http_post_proxy', None) - self.post_payload = self.rule.get('http_post_payload', {}) - self.post_raw_fields = self.rule.get('http_post_raw_fields', {}) - self.post_all_values = self.rule.get('http_post_all_values', not self.post_payload) - self.post_http_headers = self.rule.get('http_post_headers', {}) - self.post_ca_certs = self.rule.get('http_post_ca_certs') - self.post_ignore_ssl_errors = self.rule.get('http_post_ignore_ssl_errors', False) - self.timeout = self.rule.get('http_post_timeout', 10) + self.post_proxy = self.rule.get('http_post2_proxy', None) + self.post_payload = self.rule.get('http_post2_payload', {}) + self.post_raw_fields = self.rule.get('http_post2_raw_fields', {}) + self.post_all_values = self.rule.get('http_post2_all_values', not self.post_payload) + self.post_http_headers = self.rule.get('http_post2_headers', {}) + self.post_ca_certs = self.rule.get('http_post2_ca_certs') + self.post_ignore_ssl_errors = self.rule.get('http_post2_ignore_ssl_errors', False) + self.timeout = self.rule.get('http_post2_timeout', 10) def alert(self, matches): """ Each match will trigger a POST to the specified endpoint(s). """ @@ -71,5 +71,5 @@ def alert(self, matches): elastalert_logger.info("HTTP Post alert sent.") def get_info(self): - return {'type': 'http_post', - 'http_post_webhook_url': self.post_url} + return {'type': 'http_post2', + 'http_post2_webhook_url': self.post_url} diff --git a/elastalert/loaders.py b/elastalert/loaders.py index eb20fca0..3ffc1cde 100644 --- a/elastalert/loaders.py +++ b/elastalert/loaders.py @@ -23,6 +23,7 @@ import elastalert.alerters.gitter import elastalert.alerters.googlechat import elastalert.alerters.httppost +import elastalert.alerters.httppost2 import elastalert.alerters.line import elastalert.alerters.pagertree import elastalert.alerters.rocketchat @@ -111,6 +112,7 @@ class RulesLoader(object): 'servicenow': elastalert.alerters.servicenow.ServiceNowAlerter, 'alerta': elastalert.alerters.alerta.AlertaAlerter, 'post': elastalert.alerters.httppost.HTTPPostAlerter, + 'post2': elastalert.alerters.httppost2.HTTPPost2Alerter, 'pagertree': elastalert.alerters.pagertree.PagerTreeAlerter, 'linenotify': elastalert.alerters.line.LineNotifyAlerter, 'hivealerter': elastalert.alerters.thehive.HiveAlerter, diff --git a/tests/alerters/httppost2_test.py b/tests/alerters/httppost2_test.py index 24231034..98c54776 100644 --- a/tests/alerters/httppost2_test.py +++ b/tests/alerters/httppost2_test.py @@ -16,8 +16,8 @@ def test_http_alerter_with_payload(caplog): rule = { 'name': 'Test HTTP Post Alerter With Payload', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_payload': {'posted_name': 'toto'}, + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_payload': {'posted_name': 'toto'}, 'alert': [] } rules_loader = FileRulesLoader({}) @@ -33,7 +33,7 @@ def test_http_alerter_with_payload(caplog): 'posted_name': 'toto', } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, proxies=None, @@ -49,10 +49,10 @@ def test_http_alerter_with_payload_raw_fields(caplog): rule = { 'name': 'Test HTTP Post Alerter With Payload and raw fields', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_payload': {'posted_name': 'toto'}, - 'http_post_raw_fields': {'posted_raw_field': 'somefield'}, - 'http_post_static_payload': {'name': 'somestaticname'}, + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_payload': {'posted_name': 'toto'}, + 'http_post2_raw_fields': {'posted_raw_field': 'somefield'}, + 'http_post2_static_payload': {'name': 'somestaticname'}, 'alert': [] } rules_loader = FileRulesLoader({}) @@ -69,7 +69,7 @@ def test_http_alerter_with_payload_raw_fields(caplog): 'posted_raw_field': 'foobarbaz' } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, proxies=None, @@ -85,10 +85,10 @@ def test_http_alerter_with_payload_raw_fields_overwrite(caplog): rule = { 'name': 'Test HTTP Post Alerter raw fields overwrite payload', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_payload': {'posted_name': 'toto', 'overwrite_field': 'tata'}, - 'http_post_raw_fields': {'overwrite_field': 'somefield'}, - 'http_post_static_payload': {'name': 'somestaticname'}, + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_payload': {'posted_name': 'toto', 'overwrite_field': 'tata'}, + 'http_post2_raw_fields': {'overwrite_field': 'somefield'}, + 'http_post2_static_payload': {'name': 'somestaticname'}, 'alert': [] } rules_loader = FileRulesLoader({}) @@ -105,7 +105,7 @@ def test_http_alerter_with_payload_raw_fields_overwrite(caplog): 'overwrite_field': 'foobarbaz' } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, proxies=None, @@ -121,8 +121,8 @@ def test_http_alerter_with_payload_no_clash(caplog): rule = { 'name': 'Test HTTP Post Alerter With Payload has no clash with the match fields', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_payload': {'posted_name': 'toto'}, + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_payload': {'posted_name': 'toto'}, 'alert': [] } rules_loader = FileRulesLoader({}) @@ -138,7 +138,7 @@ def test_http_alerter_with_payload_no_clash(caplog): 'posted_name': 'toto', } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, proxies=None, @@ -154,8 +154,8 @@ def test_http_alerter_with_payload_args_keys(caplog): rule = { 'name': 'Test HTTP Post Alerter With Payload args for the key', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_payload': {'args_{{some_field}}': 'tata'}, + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_payload': {'args_{{some_field}}': 'tata'}, 'alert': [] } rules_loader = FileRulesLoader({}) @@ -171,7 +171,7 @@ def test_http_alerter_with_payload_args_keys(caplog): 'args_toto': 'tata', } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, proxies=None, @@ -187,8 +187,8 @@ def test_http_alerter_with_payload_args_key_not_found(caplog): rule = { 'name': 'Test HTTP Post Alerter With Payload args for the key if not found', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_payload': {'args_{{some_field1}}': 'tata'}, + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_payload': {'args_{{some_field1}}': 'tata'}, 'alert': [] } rules_loader = FileRulesLoader({}) @@ -204,7 +204,7 @@ def test_http_alerter_with_payload_args_key_not_found(caplog): 'args_': 'tata', } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, proxies=None, @@ -220,8 +220,8 @@ def test_http_alerter_with_payload_args_value(caplog): rule = { 'name': 'Test HTTP Post Alerter With Payload args for the value', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_payload': {'posted_name': 'toto', 'args_name': '{{some_field}}'}, + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_payload': {'posted_name': 'toto', 'args_name': '{{some_field}}'}, 'alert': [] } rules_loader = FileRulesLoader({}) @@ -238,7 +238,7 @@ def test_http_alerter_with_payload_args_value(caplog): 'args_name': 'foobarbaz', } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, proxies=None, @@ -254,8 +254,8 @@ def test_http_alerter_with_payload_args_value_not_found(caplog): rule = { 'name': 'Test HTTP Post Alerter With Payload args for the value if not found', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_payload': {'posted_name': 'toto', 'args_name': '{{some_field1}}'}, + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_payload': {'posted_name': 'toto', 'args_name': '{{some_field1}}'}, 'alert': [] } rules_loader = FileRulesLoader({}) @@ -272,7 +272,7 @@ def test_http_alerter_with_payload_args_value_not_found(caplog): 'args_name': '', } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, proxies=None, @@ -288,9 +288,9 @@ def test_http_alerter_with_header_no_clash(caplog): rule = { 'name': 'Test HTTP Post Alerter With Headers has no clash with the match fields', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_headers': {'header_name': 'titi'}, - 'http_post_payload': {'posted_name': 'toto'}, + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_headers': {'header_name': 'titi'}, + 'http_post2_payload': {'posted_name': 'toto'}, 'alert': [] } rules_loader = FileRulesLoader({}) @@ -308,7 +308,7 @@ def test_http_alerter_with_header_no_clash(caplog): 'header_name': 'titi' } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers=expected_headers, proxies=None, @@ -323,9 +323,9 @@ def test_http_alerter_with_header_args_value(caplog): rule = { 'name': 'Test HTTP Post Alerter With Headers args value', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_headers': {'header_name': '{{titi}}'}, - 'http_post_payload': {'posted_name': 'toto'}, + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_headers': {'header_name': '{{titi}}'}, + 'http_post2_payload': {'posted_name': 'toto'}, 'alert': [] } rules_loader = FileRulesLoader({}) @@ -343,7 +343,7 @@ def test_http_alerter_with_header_args_value(caplog): 'header_name': 'foobarbaz' } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers=expected_headers, proxies=None, @@ -358,9 +358,9 @@ def test_http_alerter_with_header_args_value_not_found(caplog): rule = { 'name': 'Test HTTP Post Alerter With Headers args value if not found', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_headers': {'header_name': '{{titi1}}'}, - 'http_post_payload': {'posted_name': 'toto'}, + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_headers': {'header_name': '{{titi1}}'}, + 'http_post2_payload': {'posted_name': 'toto'}, 'alert': [] } rules_loader = FileRulesLoader({}) @@ -378,7 +378,7 @@ def test_http_alerter_with_header_args_value_not_found(caplog): 'header_name': '' } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers=expected_headers, proxies=None, @@ -393,9 +393,9 @@ def test_http_alerter_with_header_args_key(caplog): rule = { 'name': 'Test HTTP Post Alerter With Headers args key', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_headers': {'header_{{titi}}': 'tata'}, - 'http_post_payload': {'posted_name': 'toto'}, + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_headers': {'header_{{titi}}': 'tata'}, + 'http_post2_payload': {'posted_name': 'toto'}, 'alert': [] } rules_loader = FileRulesLoader({}) @@ -413,7 +413,7 @@ def test_http_alerter_with_header_args_key(caplog): 'header_foobarbaz': 'tata' } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers=expected_headers, proxies=None, @@ -428,9 +428,9 @@ def test_http_alerter_with_header_args_key_not_found(caplog): rule = { 'name': 'Test HTTP Post Alerter With Headers args key if not found', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_headers': {'header_{{titi1}}': 'tata'}, - 'http_post_payload': {'posted_name': 'toto'}, + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_headers': {'header_{{titi1}}': 'tata'}, + 'http_post2_payload': {'posted_name': 'toto'}, 'alert': [] } rules_loader = FileRulesLoader({}) @@ -448,7 +448,7 @@ def test_http_alerter_with_header_args_key_not_found(caplog): 'header_': 'tata' } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers=expected_headers, proxies=None, @@ -463,8 +463,8 @@ def test_http_alerter_with_payload_nested(caplog): rule = { 'name': 'Test HTTP Post Alerter With Payload', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_payload': {'posted_name': '{{ toto.tata }}'}, + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_payload': {'posted_name': '{{ toto.tata }}'}, 'alert': [] } rules_loader = FileRulesLoader({}) @@ -480,7 +480,7 @@ def test_http_alerter_with_payload_nested(caplog): 'posted_name': 'titi', } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, proxies=None, @@ -495,9 +495,9 @@ def test_http_alerter_with_payload_all_values(): rule = { 'name': 'Test HTTP Post Alerter With Payload', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_payload': {'posted_name': 'toto'}, - 'http_post_all_values': True, + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_payload': {'posted_name': 'toto'}, + 'http_post2_all_values': True, 'alert': [] } rules_loader = FileRulesLoader({}) @@ -515,7 +515,7 @@ def test_http_alerter_with_payload_all_values(): 'somefield': 'foobarbaz' } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, proxies=None, @@ -529,7 +529,7 @@ def test_http_alerter_without_payload(): rule = { 'name': 'Test HTTP Post Alerter Without Payload', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', + 'http_post2_url': 'http://test.webhook.url', 'alert': [] } rules_loader = FileRulesLoader({}) @@ -546,7 +546,7 @@ def test_http_alerter_without_payload(): 'somefield': 'foobarbaz', } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, proxies=None, @@ -560,8 +560,8 @@ def test_http_alerter_proxy(): rule = { 'name': 'Test HTTP Post Alerter Without Payload', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_proxy': 'http://proxy.url', + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_proxy': 'http://proxy.url', 'alert': [] } rules_loader = FileRulesLoader({}) @@ -578,7 +578,7 @@ def test_http_alerter_proxy(): 'somefield': 'foobarbaz', } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, proxies={'https': 'http://proxy.url'}, @@ -592,8 +592,8 @@ def test_http_alerter_timeout(): rule = { 'name': 'Test HTTP Post Alerter Without Payload', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_timeout': 20, + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_timeout': 20, 'alert': [] } rules_loader = FileRulesLoader({}) @@ -610,7 +610,7 @@ def test_http_alerter_timeout(): 'somefield': 'foobarbaz', } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, proxies=None, @@ -624,8 +624,8 @@ def test_http_alerter_headers(): rule = { 'name': 'Test HTTP Post Alerter Without Payload', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_headers': {'authorization': 'Basic 123dr3234'}, + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_headers': {'authorization': 'Basic 123dr3234'}, 'alert': [] } rules_loader = FileRulesLoader({}) @@ -642,7 +642,7 @@ def test_http_alerter_headers(): 'somefield': 'foobarbaz', } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8', 'authorization': 'Basic 123dr3234'}, proxies=None, @@ -667,14 +667,14 @@ def test_http_alerter_post_ca_certs(ca_certs, ignore_ssl_errors, excpet_verify): rule = { 'name': 'Test HTTP Post Alerter Without Payload', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', + 'http_post2_url': 'http://test.webhook.url', 'alert': [] } if ca_certs: - rule['http_post_ca_certs'] = ca_certs + rule['http_post2_ca_certs'] = ca_certs if ignore_ssl_errors: - rule['http_post_ignore_ssl_errors'] = ignore_ssl_errors + rule['http_post2_ignore_ssl_errors'] = ignore_ssl_errors rules_loader = FileRulesLoader({}) rules_loader.load_modules(rule) @@ -690,7 +690,7 @@ def test_http_alerter_post_ca_certs(ca_certs, ignore_ssl_errors, excpet_verify): 'somefield': 'foobarbaz', } mock_post_request.assert_called_once_with( - rule['http_post_url'], + rule['http_post2_url'], data=mock.ANY, headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, proxies=None, @@ -705,8 +705,8 @@ def test_http_alerter_post_ea_exception(): rule = { 'name': 'Test HTTP Post Alerter Without Payload', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_ca_certs': False, + 'http_post2_url': 'http://test.webhook.url', + 'http_post2_ca_certs': False, 'alert': [] } rules_loader = FileRulesLoader({}) @@ -726,7 +726,7 @@ def test_http_getinfo(): rule = { 'name': 'Test HTTP Post Alerter Without Payload', 'type': 'any', - 'http_post_url': 'http://test.webhook.url', + 'http_post2_url': 'http://test.webhook.url', 'alert': [] } rules_loader = FileRulesLoader({}) @@ -734,22 +734,22 @@ def test_http_getinfo(): alert = HTTPPost2Alerter(rule) expected_data = { - 'type': 'http_post', - 'http_post_webhook_url': ['http://test.webhook.url'] + 'type': 'http_post2', + 'http_post2_webhook_url': ['http://test.webhook.url'] } actual_data = alert.get_info() assert expected_data == actual_data -@pytest.mark.parametrize('http_post_url, expected_data', [ - ('', 'Missing required option(s): http_post_url'), +@pytest.mark.parametrize('http_post2_url, expected_data', [ + ('', 'Missing required option(s): http_post2_url'), ('http://test.webhook.url', { - 'type': 'http_post', - 'http_post_webhook_url': ['http://test.webhook.url'] + 'type': 'http_post2', + 'http_post2_webhook_url': ['http://test.webhook.url'] }), ]) -def test_http_required_error(http_post_url, expected_data): +def test_http_required_error(http_post2_url, expected_data): try: rule = { 'name': 'Test HTTP Post Alerter Without Payload', @@ -757,8 +757,8 @@ def test_http_required_error(http_post_url, expected_data): 'alert': [] } - if http_post_url: - rule['http_post_url'] = http_post_url + if http_post2_url: + rule['http_post2_url'] = http_post2_url rules_loader = FileRulesLoader({}) rules_loader.load_modules(rule) From eeff3a16a857c4581115c7e3d6bfdc633a9ae4d7 Mon Sep 17 00:00:00 2001 From: lepouletsuisse Date: Mon, 1 Nov 2021 16:54:38 +0100 Subject: [PATCH 7/8] Add HTTP Post 2 to schema.yaml --- elastalert/schema.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/elastalert/schema.yaml b/elastalert/schema.yaml index 04b9eba2..943a0a2b 100644 --- a/elastalert/schema.yaml +++ b/elastalert/schema.yaml @@ -444,6 +444,13 @@ properties: http_post_ignore_ssl_errors: {type: boolean} http_post_timeout: {type: integer} + ### HTTP POST 2 + http_post2_url: *arrayOfString + http_post2_proxy: { type: string } + http_post2_ca_certs: { type: boolean } + http_post2_ignore_ssl_errors: { type: boolean } + http_post2_timeout: { type: integer } + ### Jira jira_server: {type: string} jira_project: {type: string} From 0ca0f281f5d203e7395ef7188f540a92a6a49cd0 Mon Sep 17 00:00:00 2001 From: lepouletsuisse Date: Tue, 2 Nov 2021 08:35:53 +0100 Subject: [PATCH 8/8] Fix typo Changelog + Specify HTTP Post 2 in log message --- CHANGELOG.md | 2 +- elastalert/alerters/httppost2.py | 4 ++-- tests/alerters/httppost2_test.py | 30 +++++++++++++++--------------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a1260fe..07b36eeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ - [Alertmanager] Added support for Alertmanager - [#503](https://github.com/jertel/elastalert2/pull/503) - @nsano-rururu - Add summary_table_max_rows optional configuration to limit rows in summary tables - [#508](https://github.com/jertel/elastalert2/pull/508) - @mdavyt92 - Added support for shortening Kibana Discover URLs using Kibana Shorten URL API - [#512](https://github.com/jertel/elastalert2/pull/512) - @JeffAshton -- Added new alerter `HTTP Post 2` which allow more flexibility to build the body/headers of the request. - [#512](https://github.com/jertel/elastalert2/pull/530) - @lepouletsuisse +- Added new alerter `HTTP Post 2` which allow more flexibility to build the body/headers of the request. - [#530](https://github.com/jertel/elastalert2/pull/530) - @lepouletsuisse ## Other changes - [Docs] Add exposed metrics documentation - [#498](https://github.com/jertel/elastalert2/pull/498) - @thisisxgp diff --git a/elastalert/alerters/httppost2.py b/elastalert/alerters/httppost2.py index aec32e91..3f1073f8 100644 --- a/elastalert/alerters/httppost2.py +++ b/elastalert/alerters/httppost2.py @@ -67,8 +67,8 @@ def alert(self, matches): verify=verify) response.raise_for_status() except RequestException as e: - raise EAException("Error posting HTTP Post alert: %s" % e) - elastalert_logger.info("HTTP Post alert sent.") + raise EAException("Error posting HTTP Post 2 alert: %s" % e) + elastalert_logger.info("HTTP Post 2 alert sent.") def get_info(self): return {'type': 'http_post2', diff --git a/tests/alerters/httppost2_test.py b/tests/alerters/httppost2_test.py index 98c54776..2dce1305 100644 --- a/tests/alerters/httppost2_test.py +++ b/tests/alerters/httppost2_test.py @@ -41,7 +41,7 @@ def test_http_alerter_with_payload(caplog): verify=True ) assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) - assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + assert ('elastalert', logging.INFO, 'HTTP Post 2 alert sent.') == caplog.record_tuples[0] def test_http_alerter_with_payload_raw_fields(caplog): @@ -77,7 +77,7 @@ def test_http_alerter_with_payload_raw_fields(caplog): verify=True ) assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) - assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + assert ('elastalert', logging.INFO, 'HTTP Post 2 alert sent.') == caplog.record_tuples[0] def test_http_alerter_with_payload_raw_fields_overwrite(caplog): @@ -113,7 +113,7 @@ def test_http_alerter_with_payload_raw_fields_overwrite(caplog): verify=True ) assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) - assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + assert ('elastalert', logging.INFO, 'HTTP Post 2 alert sent.') == caplog.record_tuples[0] def test_http_alerter_with_payload_no_clash(caplog): @@ -146,7 +146,7 @@ def test_http_alerter_with_payload_no_clash(caplog): verify=True ) assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) - assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + assert ('elastalert', logging.INFO, 'HTTP Post 2 alert sent.') == caplog.record_tuples[0] def test_http_alerter_with_payload_args_keys(caplog): @@ -179,7 +179,7 @@ def test_http_alerter_with_payload_args_keys(caplog): verify=True ) assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) - assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + assert ('elastalert', logging.INFO, 'HTTP Post 2 alert sent.') == caplog.record_tuples[0] def test_http_alerter_with_payload_args_key_not_found(caplog): @@ -212,7 +212,7 @@ def test_http_alerter_with_payload_args_key_not_found(caplog): verify=True ) assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) - assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + assert ('elastalert', logging.INFO, 'HTTP Post 2 alert sent.') == caplog.record_tuples[0] def test_http_alerter_with_payload_args_value(caplog): @@ -246,7 +246,7 @@ def test_http_alerter_with_payload_args_value(caplog): verify=True ) assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) - assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + assert ('elastalert', logging.INFO, 'HTTP Post 2 alert sent.') == caplog.record_tuples[0] def test_http_alerter_with_payload_args_value_not_found(caplog): @@ -280,7 +280,7 @@ def test_http_alerter_with_payload_args_value_not_found(caplog): verify=True ) assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) - assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + assert ('elastalert', logging.INFO, 'HTTP Post 2 alert sent.') == caplog.record_tuples[0] def test_http_alerter_with_header_no_clash(caplog): @@ -315,7 +315,7 @@ def test_http_alerter_with_header_no_clash(caplog): timeout=10, verify=True ) - assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + assert ('elastalert', logging.INFO, 'HTTP Post 2 alert sent.') == caplog.record_tuples[0] def test_http_alerter_with_header_args_value(caplog): @@ -350,7 +350,7 @@ def test_http_alerter_with_header_args_value(caplog): timeout=10, verify=True ) - assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + assert ('elastalert', logging.INFO, 'HTTP Post 2 alert sent.') == caplog.record_tuples[0] def test_http_alerter_with_header_args_value_not_found(caplog): @@ -385,7 +385,7 @@ def test_http_alerter_with_header_args_value_not_found(caplog): timeout=10, verify=True ) - assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + assert ('elastalert', logging.INFO, 'HTTP Post 2 alert sent.') == caplog.record_tuples[0] def test_http_alerter_with_header_args_key(caplog): @@ -420,7 +420,7 @@ def test_http_alerter_with_header_args_key(caplog): timeout=10, verify=True ) - assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + assert ('elastalert', logging.INFO, 'HTTP Post 2 alert sent.') == caplog.record_tuples[0] def test_http_alerter_with_header_args_key_not_found(caplog): @@ -455,7 +455,7 @@ def test_http_alerter_with_header_args_key_not_found(caplog): timeout=10, verify=True ) - assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + assert ('elastalert', logging.INFO, 'HTTP Post 2 alert sent.') == caplog.record_tuples[0] def test_http_alerter_with_payload_nested(caplog): @@ -488,7 +488,7 @@ def test_http_alerter_with_payload_nested(caplog): verify=True ) assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) - assert ('elastalert', logging.INFO, 'HTTP Post alert sent.') == caplog.record_tuples[0] + assert ('elastalert', logging.INFO, 'HTTP Post 2 alert sent.') == caplog.record_tuples[0] def test_http_alerter_with_payload_all_values(): @@ -719,7 +719,7 @@ def test_http_alerter_post_ea_exception(): mock_run = mock.MagicMock(side_effect=RequestException) with mock.patch('requests.post', mock_run), pytest.raises(RequestException): alert.alert([match]) - assert 'Error posting HTTP Post alert: ' in str(ea) + assert 'Error posting HTTP Post 2 alert: ' in str(ea) def test_http_getinfo():