From 7ef5dc854fbbbcd75192a8e968774c57628dc9b2 Mon Sep 17 00:00:00 2001 From: aizerin Date: Tue, 18 Feb 2025 12:52:09 +0100 Subject: [PATCH] add ms_power_automate_webhook_url_from_field --- CHANGELOG.md | 2 +- docs/source/alerts.rst | 2 ++ elastalert/alerters/powerautomate.py | 9 ++++++-- elastalert/schema.yaml | 1 + elastalert/util.py | 8 +++++++ tests/alerters/powerautomate_test.py | 31 ++++++++++++++++++++++++++++ tests/util_test.py | 10 +++++++++ 7 files changed, 60 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f85702e..50857455 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - None ## New features -- None +- [MicrosoftPowerAutomate] Add support for 'ms_power_automate_webhook_url_from_field' option to dynamically select the webhook URL from the match. - [#1623](https://github.com/jertel/elastalert2/pull/1623) - @aizerin ## Other changes - [Docs] Add missing documentation of the `aggregation_alert_time_compared_with_timestamp_field` option. - [#1588](https://github.com/jertel/elastalert2/pull/1588) - @nicolasnovelli diff --git a/docs/source/alerts.rst b/docs/source/alerts.rst index 3158f0ba..6030b9c3 100644 --- a/docs/source/alerts.rst +++ b/docs/source/alerts.rst @@ -1672,6 +1672,8 @@ The alerter requires the following options: ``ms_power_automate_webhook_url``: The webhook URL provided in Power Automate, `doc Microsoft `_. After creating the flow select your Teams channel under "Send each adaptive card". You can use a list of URLs to send to multiple channels. +``ms_power_automate_webhook_url_from_field``: Use a field from the document that triggered the alert as the webhook. If the field cannot be found, the ``ms_power_automate_webhook_url`` value will be used as a default. + Optional: ``ms_power_automate_summary_text_size``: By default, is set to the value ``large``. This field supports the values, default, small, medium and extraLarge. diff --git a/elastalert/alerters/powerautomate.py b/elastalert/alerters/powerautomate.py index b48865b0..20684357 100644 --- a/elastalert/alerters/powerautomate.py +++ b/elastalert/alerters/powerautomate.py @@ -3,7 +3,7 @@ import requests from elastalert.alerts import Alerter, DateTimeEncoder -from elastalert.util import EAException, elastalert_logger, lookup_es_key +from elastalert.util import EAException, elastalert_logger, lookup_es_key, expand_string_into_array from requests.exceptions import RequestException @@ -121,8 +121,13 @@ def alert(self, matches): "url": opensearch_discover_url, "style": self.ms_power_automate_opensearch_discover_color }) + urls = self.ms_power_automate_webhook_url + if 'ms_power_automate_webhook_url_from_field' in self.rule: + webhook = lookup_es_key(matches[0], self.rule['ms_power_automate_webhook_url_from_field']) + if isinstance(webhook, str): + urls = expand_string_into_array(webhook) - for url in self.ms_power_automate_webhook_url: + for url in urls: try: response = requests.post(url, data=json.dumps(payload, cls=DateTimeEncoder), headers=headers, proxies=proxies, verify=verify) diff --git a/elastalert/schema.yaml b/elastalert/schema.yaml index 09d707c2..6fd743de 100644 --- a/elastalert/schema.yaml +++ b/elastalert/schema.yaml @@ -715,6 +715,7 @@ properties: ### Microsoft Power Automate ms_power_automate_webhook_url: *arrayOfString + ms_power_automate_webhook_url_from_field: { type: string } ms_power_automate_alert_summary: {type: string} ms_power_automate_summary_text_size: {type: string, enum: ['default', 'small', 'medium', 'large', 'extraLarge']} ms_power_automate_body_text_size: {type: string, enum: ['default', 'small', 'medium', 'large', 'extraLarge']} diff --git a/elastalert/util.py b/elastalert/util.py index 8589e3a5..08f0054d 100644 --- a/elastalert/util.py +++ b/elastalert/util.py @@ -521,6 +521,14 @@ def expand_string_into_dict(dictionary, string, value, sep='.'): dictionary[field1] = _expand_string_into_dict(new_string, value) return dictionary +def expand_string_into_array(value, sep=','): + """ + Expands a separated string into an array of strings. + """ + if sep not in value: + return [value] + else: + return value.split(sep) def format_string(format_config, target_value): """ diff --git a/tests/alerters/powerautomate_test.py b/tests/alerters/powerautomate_test.py index 51e4e21d..5ae8d39e 100644 --- a/tests/alerters/powerautomate_test.py +++ b/tests/alerters/powerautomate_test.py @@ -1,6 +1,7 @@ import json import logging from unittest import mock +import pytest from elastalert.alerters.powerautomate import MsPowerAutomateAlerter from elastalert.alerts import BasicMatchString @@ -925,3 +926,33 @@ def test_ms_power_automate_body_text_size_medium(): ) actual_data = json.loads(mock_post_request.call_args_list[0][1]['data']) assert expected_data == actual_data + + +@pytest.mark.parametrize('match_data, expected_data', [ + ({'webhook_url': 'webhook.com'}, ['webhook.com']), + ({'webhook_url': 'webhook.com,webhook2.com'}, ['webhook.com', 'webhook2.com']), + ({}, ['default.com']) +]) +def test_ms_power_automate_webhook_url_from_field(match_data, expected_data): + rule = { + 'name': 'Test Rule', + 'type': 'any', + 'ms_power_automate_webhook_url': 'default.com', + 'ms_power_automate_webhook_url_from_field': 'webhook_url', + 'alert': [], + 'alert_subject': 'Cool subject', + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = MsPowerAutomateAlerter(rule) + with mock.patch('requests.post') as mock_post_request: + alert.alert([match_data]) + + for url in expected_data: + mock_post_request.assert_any_call( + url, + data=mock.ANY, + headers={'content-type': 'application/json'}, + proxies=None, + verify=True + ) diff --git a/tests/util_test.py b/tests/util_test.py index 4fd53f64..3460171f 100644 --- a/tests/util_test.py +++ b/tests/util_test.py @@ -40,6 +40,7 @@ from elastalert.util import pretty_ts from elastalert.util import parse_hosts from elastalert.util import get_version_from_cluster_info +from elastalert.util import expand_string_into_array from elasticsearch.client import Elasticsearch @@ -697,3 +698,12 @@ def test_get_version(version, distro, expectedversion): client = Elasticsearch() actualversion = get_version_from_cluster_info(client) assert expectedversion == actualversion + + +@pytest.mark.parametrize('value, expect', [ + ('foo', ['foo']), + ('foo,foo', ['foo', 'foo']), +]) +def test_expand_string_into_array(value, expect): + actual = expand_string_into_array(value) + assert expect == actual