elastalert2 2.28.0__tar.gz → 2.30.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. {elastalert2-2.28.0/elastalert2.egg-info → elastalert2-2.30.0}/PKG-INFO +14 -13
  2. {elastalert2-2.28.0 → elastalert2-2.30.0}/README.md +1 -1
  3. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/datadog.py +2 -4
  4. elastalert2-2.30.0/elastalert/alerters/sqs.py +83 -0
  5. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerts.py +24 -9
  6. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/elastalert.py +2 -2
  7. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/loaders.py +2 -2
  8. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/schema.yaml +19 -28
  9. {elastalert2-2.28.0 → elastalert2-2.30.0/elastalert2.egg-info}/PKG-INFO +14 -13
  10. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert2.egg-info/SOURCES.txt +2 -2
  11. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert2.egg-info/requires.txt +12 -11
  12. {elastalert2-2.28.0 → elastalert2-2.30.0}/setup.py +13 -12
  13. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/datadog_test.py +67 -0
  14. elastalert2-2.30.0/tests/alerters/sqs_test.py +224 -0
  15. elastalert2-2.28.0/elastalert/alerters/teams.py +0 -120
  16. elastalert2-2.28.0/tests/alerters/teams_test.py +0 -650
  17. {elastalert2-2.28.0 → elastalert2-2.30.0}/LICENSE +0 -0
  18. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/__init__.py +0 -0
  19. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/__init__.py +0 -0
  20. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/alerta.py +0 -0
  21. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/alertmanager.py +0 -0
  22. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/chatwork.py +0 -0
  23. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/command.py +0 -0
  24. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/debug.py +0 -0
  25. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/dingtalk.py +0 -0
  26. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/discord.py +0 -0
  27. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/email.py +0 -0
  28. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/exotel.py +0 -0
  29. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/flashduty.py +0 -0
  30. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/gelf.py +0 -0
  31. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/gitter.py +0 -0
  32. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/googlechat.py +0 -0
  33. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/httppost.py +0 -0
  34. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/httppost2.py +0 -0
  35. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/indexer.py +0 -0
  36. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/iris.py +0 -0
  37. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/jira.py +0 -0
  38. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/lark.py +0 -0
  39. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/line.py +0 -0
  40. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/matrixhookshot.py +0 -0
  41. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/mattermost.py +0 -0
  42. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/opsgenie.py +0 -0
  43. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/pagerduty.py +0 -0
  44. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/pagertree.py +0 -0
  45. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/powerautomate.py +0 -0
  46. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/rocketchat.py +0 -0
  47. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/servicenow.py +0 -0
  48. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/ses.py +0 -0
  49. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/slack.py +0 -0
  50. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/smseagle.py +0 -0
  51. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/sns.py +0 -0
  52. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/stomp.py +0 -0
  53. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/telegram.py +0 -0
  54. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/tencentsms.py +0 -0
  55. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/thehive.py +0 -0
  56. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/twilio.py +0 -0
  57. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/victorops.py +0 -0
  58. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/webex_webhook.py +0 -0
  59. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/workwechat.py +0 -0
  60. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/yzj.py +0 -0
  61. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/alerters/zabbix.py +0 -0
  62. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/auth.py +0 -0
  63. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/config.py +0 -0
  64. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/create_index.py +0 -0
  65. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/enhancements.py +0 -0
  66. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/eql.py +0 -0
  67. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/es_mappings/7/elastalert.json +0 -0
  68. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/es_mappings/7/elastalert_error.json +0 -0
  69. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/es_mappings/7/elastalert_status.json +0 -0
  70. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/es_mappings/7/past_elastalert.json +0 -0
  71. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/es_mappings/7/silence.json +0 -0
  72. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/es_mappings/8/elastalert.json +0 -0
  73. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/es_mappings/8/elastalert_error.json +0 -0
  74. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/es_mappings/8/elastalert_status.json +0 -0
  75. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/es_mappings/8/past_elastalert.json +0 -0
  76. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/es_mappings/8/silence.json +0 -0
  77. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/kibana_discover.py +0 -0
  78. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/kibana_external_url_formatter.py +0 -0
  79. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/opensearch_discover.py +0 -0
  80. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/opensearch_external_url_formatter.py +0 -0
  81. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/prometheus_wrapper.py +0 -0
  82. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/ruletypes.py +0 -0
  83. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/test_rule.py +0 -0
  84. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/util.py +0 -0
  85. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert/yaml.py +0 -0
  86. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert2.egg-info/dependency_links.txt +0 -0
  87. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert2.egg-info/entry_points.txt +0 -0
  88. {elastalert2-2.28.0 → elastalert2-2.30.0}/elastalert2.egg-info/top_level.txt +0 -0
  89. {elastalert2-2.28.0 → elastalert2-2.30.0}/setup.cfg +0 -0
  90. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/__init__.py +0 -0
  91. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/alerta_test.py +0 -0
  92. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/alertmanager_test.py +0 -0
  93. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/chatwork_test.py +0 -0
  94. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/command_test.py +0 -0
  95. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/debug_test.py +0 -0
  96. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/dingtalk_test.py +0 -0
  97. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/discord_test.py +0 -0
  98. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/email_test.py +0 -0
  99. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/exotel_test.py +0 -0
  100. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/flashduty_test.py +0 -0
  101. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/gelf_test.py +0 -0
  102. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/gitter_test.py +0 -0
  103. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/googlechat_test.py +0 -0
  104. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/httppost2_test.py +0 -0
  105. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/httppost_test.py +0 -0
  106. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/indexer_test.py +0 -0
  107. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/iris_test.py +0 -0
  108. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/jira_test.py +0 -0
  109. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/lark_test.py +0 -0
  110. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/line_test.py +0 -0
  111. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/matrixhookshot_test.py +0 -0
  112. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/mattermost_test.py +0 -0
  113. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/opsgenie_test.py +0 -0
  114. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/pagerduty_test.py +0 -0
  115. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/pagertree_test.py +0 -0
  116. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/powerautomate_test.py +0 -0
  117. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/rocketchat_test.py +0 -0
  118. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/servicenow_test.py +0 -0
  119. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/ses_test.py +0 -0
  120. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/slack_test.py +0 -0
  121. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/smseagle_test.py +0 -0
  122. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/sns_test.py +0 -0
  123. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/stomp_test.py +0 -0
  124. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/telegram_test.py +0 -0
  125. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/tencentsms_test.py +0 -0
  126. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/thehive_test.py +0 -0
  127. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/twilio_test.py +0 -0
  128. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/victorops_test.py +0 -0
  129. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/webex_webhook_test.py +0 -0
  130. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/workwechat_test.py +0 -0
  131. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/yzj_test.py +0 -0
  132. {elastalert2-2.28.0 → elastalert2-2.30.0}/tests/alerters/zabbix_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: elastalert2
3
- Version: 2.28.0
3
+ Version: 2.30.0
4
4
  Summary: Automated rule-based alerting for Elasticsearch
5
5
  Home-page: https://github.com/jertel/elastalert2
6
6
  License: Apache 2.0
@@ -15,31 +15,32 @@ Classifier: Operating System :: OS Independent
15
15
  Requires-Python: >=3.12
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
- Requires-Dist: apscheduler<4.0,>=3.11.0
18
+ Requires-Dist: apscheduler<4.0,>=3.11.2
19
19
  Requires-Dist: aws-requests-auth>=0.4.3
20
- Requires-Dist: boto3>=1.40.59
20
+ Requires-Dist: boto3>=1.42.89
21
21
  Requires-Dist: cffi>=2.0.0
22
- Requires-Dist: croniter>=6.0.0
22
+ Requires-Dist: croniter>=6.2.2
23
23
  Requires-Dist: elasticsearch==7.10.1
24
24
  Requires-Dist: envparse>=0.2.0
25
25
  Requires-Dist: exotel==0.1.5
26
26
  Requires-Dist: Jinja2>=3.1.6
27
27
  Requires-Dist: jira>=3.10.5
28
- Requires-Dist: jsonpointer>=3.0.0
29
- Requires-Dist: jsonschema>=4.25.1
30
- Requires-Dist: prettytable>=3.16.0
28
+ Requires-Dist: jsonpointer>=3.1.1
29
+ Requires-Dist: jsonschema>=4.26.0
30
+ Requires-Dist: prettytable>=3.17.0
31
31
  Requires-Dist: prison>=0.2.1
32
- Requires-Dist: prometheus_client>=0.23.1
32
+ Requires-Dist: prometheus-client>=0.25.0
33
33
  Requires-Dist: python-dateutil>=2.9.0.post0
34
+ Requires-Dist: pytz>=2026.1.post1
34
35
  Requires-Dist: PyYAML>=6.0.3
35
- Requires-Dist: zabbix-utils>=2.0.3
36
- Requires-Dist: requests>=2.31.0
36
+ Requires-Dist: zabbix-utils>=2.0.4
37
+ Requires-Dist: requests>=2.33.1
37
38
  Requires-Dist: sortedcontainers>=2.4.0
38
39
  Requires-Dist: statsd-tags==3.2.1.post1
39
40
  Requires-Dist: stomp.py>=8.2.0
40
- Requires-Dist: tencentcloud-sdk-python>=3.0.1479
41
+ Requires-Dist: tencentcloud-sdk-python>=3.1.79
41
42
  Requires-Dist: texttable>=1.7.0
42
- Requires-Dist: twilio>=9.8.4
43
+ Requires-Dist: twilio>=9.10.5
43
44
  Dynamic: classifier
44
45
  Dynamic: description
45
46
  Dynamic: description-content-type
@@ -85,7 +86,7 @@ See our [security policy][13] for reporting urgent vulnerabilities.
85
86
 
86
87
  ElastAlert 2 is licensed under the [Apache License, Version 2.0][5].
87
88
 
88
- [0]: https://github.com/yelp/elastalert
89
+ [0]: https://github.com/YelpArchive/elastalert
89
90
  [1]: https://github.com/jertel/elastalert2/blob/master/examples/config.yaml.example
90
91
  [2]: https://hub.docker.com/r/jertel/elastalert2
91
92
  [3]: https://elastalert2.readthedocs.io/
@@ -32,7 +32,7 @@ See our [security policy][13] for reporting urgent vulnerabilities.
32
32
 
33
33
  ElastAlert 2 is licensed under the [Apache License, Version 2.0][5].
34
34
 
35
- [0]: https://github.com/yelp/elastalert
35
+ [0]: https://github.com/YelpArchive/elastalert
36
36
  [1]: https://github.com/jertel/elastalert2/blob/master/examples/config.yaml.example
37
37
  [2]: https://hub.docker.com/r/jertel/elastalert2
38
38
  [3]: https://elastalert2.readthedocs.io/
@@ -1,8 +1,6 @@
1
1
  import json
2
-
3
2
  import requests
4
3
  from requests import RequestException
5
-
6
4
  from elastalert.alerts import Alerter, DateTimeEncoder
7
5
  from elastalert.util import EAException, elastalert_logger
8
6
 
@@ -15,9 +13,9 @@ class DatadogAlerter(Alerter):
15
13
  super(DatadogAlerter, self).__init__(rule)
16
14
  self.dd_api_key = self.rule.get('datadog_api_key', None)
17
15
  self.dd_app_key = self.rule.get('datadog_app_key', None)
16
+ self.dd_api_url = self.rule.get('datadog_api_url', 'https://api.datadoghq.com/api/v1/events')
18
17
 
19
18
  def alert(self, matches):
20
- url = 'https://api.datadoghq.com/api/v1/events'
21
19
  headers = {
22
20
  'Content-Type': 'application/json',
23
21
  'DD-API-KEY': self.dd_api_key,
@@ -28,7 +26,7 @@ class DatadogAlerter(Alerter):
28
26
  'text': self.create_alert_body(matches)
29
27
  }
30
28
  try:
31
- response = requests.post(url, data=json.dumps(payload, cls=DateTimeEncoder), headers=headers)
29
+ response = requests.post(self.dd_api_url, data=json.dumps(payload, cls=DateTimeEncoder), headers=headers)
32
30
  response.raise_for_status()
33
31
  except RequestException as e:
34
32
  raise EAException('Error posting event to Datadog: %s' % e)
@@ -0,0 +1,83 @@
1
+ import boto3
2
+ import json
3
+ from urllib.parse import urlparse
4
+
5
+ from elastalert.alerts import Alerter
6
+ from elastalert.util import elastalert_logger, EAException
7
+
8
+
9
+ def _get_region_from_sqs_url(queue_url, default_region="us-east-1"):
10
+ """Infer the AWS region from an SQS queue URL like
11
+ https://sqs.us-east-1.amazonaws.com/123456789012/my-queue.
12
+ Falls back to default_region if it cannot be determined.
13
+ """
14
+ host = urlparse(queue_url).hostname or ""
15
+ parts = host.split(".")
16
+ if len(parts) >= 3 and parts[0] == "sqs":
17
+ return parts[1]
18
+ return default_region
19
+
20
+
21
+ class SqsAlerter(Alerter):
22
+ """Send alert using AWS SQS service"""
23
+
24
+ required_options = frozenset(["sqs_queue_url"])
25
+
26
+ def __init__(self, *args):
27
+ super(SqsAlerter, self).__init__(*args)
28
+ self.sqs_queue_url = self.rule.get("sqs_queue_url", None)
29
+ self.sqs_aws_access_key_id = self.rule.get("sqs_aws_access_key_id")
30
+ self.sqs_aws_secret_access_key = self.rule.get("sqs_aws_secret_access_key")
31
+ explicit_region = self.rule.get("sqs_aws_region")
32
+ if explicit_region:
33
+ self.sqs_aws_region = explicit_region
34
+ else:
35
+ # If no region is configured explicitly, derive it from the queue URL.
36
+ self.sqs_aws_region = _get_region_from_sqs_url(self.sqs_queue_url or "")
37
+ self.profile = self.rule.get("sqs_aws_profile", None)
38
+
39
+ def alert(self, matches):
40
+ # Create the alert as a JSON object
41
+ alert_data = {
42
+ "rule_name": self.rule["name"],
43
+ "matches": matches,
44
+ }
45
+ alert_text = self.create_alert_body(matches)
46
+ # SQS message body limit is 1 MB; crop text at ~800KB to be safe
47
+ if len(alert_text) > 800000:
48
+ alert_text = alert_text[:800000]
49
+ alert_text += "\n*message was cropped according to SQS limits!*"
50
+ alert_data["text"] = alert_text
51
+ body = json.dumps(alert_data, default=str)
52
+
53
+ # If the body is still too long, remove the text field
54
+ if len(body) > 1048576:
55
+ alert_data["text"] = "Text message omitted due to SQS size limit."
56
+ body = json.dumps(alert_data, default=str)
57
+ try:
58
+ # Always create the session in the configured region. SQS does not
59
+ # infer the region from the queue URL; the client region must match.
60
+ if self.profile is None:
61
+ session = boto3.Session(
62
+ aws_access_key_id=self.sqs_aws_access_key_id,
63
+ aws_secret_access_key=self.sqs_aws_secret_access_key,
64
+ region_name=self.sqs_aws_region,
65
+ )
66
+ else:
67
+ session = boto3.Session(
68
+ profile_name=self.profile,
69
+ region_name=self.sqs_aws_region,
70
+ )
71
+
72
+ sqs_client = session.client("sqs")
73
+
74
+ response = sqs_client.send_message(
75
+ QueueUrl=self.sqs_queue_url,
76
+ MessageBody=body,
77
+ )
78
+ except Exception as e:
79
+ raise EAException("Error sending Amazon SQS: %s" % e)
80
+ elastalert_logger.info("Sent Amazon SQS message to %s, MessageId: %s" % (self.sqs_queue_url, response.get("MessageId")))
81
+
82
+ def get_info(self):
83
+ return {"type": "sqs"}
@@ -256,10 +256,25 @@ class Alerter(object):
256
256
  if not isinstance(summary_table_fields, list):
257
257
  summary_table_fields = [summary_table_fields]
258
258
 
259
+ # Each entry is either a string field path, or a dict with required 'path' and optional 'header'.
260
+ # Split into parallel lists so paths drive lookups/logging while headers drive rendered column names.
261
+ summary_table_paths = []
262
+ summary_table_headers = []
263
+ for entry in summary_table_fields:
264
+ if isinstance(entry, dict):
265
+ path = entry['path']
266
+ header = entry.get('header', path)
267
+ else:
268
+ path = entry
269
+ header = entry
270
+ summary_table_paths.append(path)
271
+ summary_table_headers.append(header)
272
+
259
273
  # Include a count aggregation so that we can see at a glance how many of each aggregation_key were encountered
260
- summary_table_fields_with_count = summary_table_fields + ['count']
274
+ summary_table_paths_with_count = summary_table_paths + ['count']
275
+ summary_table_headers_with_count = summary_table_headers + ['count']
261
276
  text += "Aggregation resulted in the following data for summary_table_fields ==> {0}:\n\n".format(
262
- summary_table_fields_with_count
277
+ summary_table_paths_with_count
263
278
  )
264
279
 
265
280
  # Prepare match_aggregation used in both table types
@@ -267,7 +282,7 @@ class Alerter(object):
267
282
 
268
283
  # Maintain an aggregate count for each unique key encountered in the aggregation period
269
284
  for match in matches:
270
- key_tuple = tuple([str(lookup_es_key(match, key)) for key in summary_table_fields])
285
+ key_tuple = tuple([str(lookup_es_key(match, key)) for key in summary_table_paths])
271
286
  if key_tuple not in match_aggregation:
272
287
  match_aggregation[key_tuple] = 1
273
288
  else:
@@ -281,9 +296,9 @@ class Alerter(object):
281
296
  # Type dependent table style
282
297
  if summary_table_type == 'ascii':
283
298
  text_table = Texttable(max_width=self.get_aggregation_summary_text__maximum_width())
284
- text_table.header(summary_table_fields_with_count)
299
+ text_table.header(summary_table_headers_with_count)
285
300
  # Format all fields as 'text' to avoid long numbers being shown as scientific notation
286
- text_table.set_cols_dtype(['t' for i in summary_table_fields_with_count])
301
+ text_table.set_cols_dtype(['t' for i in summary_table_headers_with_count])
287
302
 
288
303
  for keys, count in match_aggregation.items():
289
304
  text_table.add_row([key for key in keys] + [count])
@@ -292,9 +307,9 @@ class Alerter(object):
292
307
  elif summary_table_type == 'markdown':
293
308
  # Adapted from https://github.com/codazoda/tomark/blob/master/tomark/tomark.py
294
309
  # Create table header
295
- text += '| ' + ' | '.join(map(str, summary_table_fields_with_count)) + ' |\n'
310
+ text += '| ' + ' | '.join(map(str, summary_table_headers_with_count)) + ' |\n'
296
311
  # Create header separator
297
- text += '|-----' * len(summary_table_fields_with_count) + '|\n'
312
+ text += '|-----' * len(summary_table_headers_with_count) + '|\n'
298
313
  # Create table row
299
314
  for keys, count in match_aggregation.items():
300
315
  markdown_row = ""
@@ -302,12 +317,12 @@ class Alerter(object):
302
317
  markdown_row += '| ' + str(key) + ' '
303
318
  text += markdown_row + '| ' + str(count) + ' |\n'
304
319
  text += '\n'
305
-
320
+
306
321
  elif summary_table_type == 'html':
307
322
  # Portions of the following block of HTML formatting code was taken from
308
323
  # an abandoned PR (https://github.com/jertel/elastalert2/pull/1227).
309
324
  text_table = PrettyTable()
310
- text_table.field_names = summary_table_fields_with_count
325
+ text_table.field_names = summary_table_headers_with_count
311
326
  text_table.set_style(TableStyle.MSWORD_FRIENDLY)
312
327
  text_table.border = True
313
328
  text_table.header = True
@@ -1606,7 +1606,7 @@ class ElastAlerter(object):
1606
1606
 
1607
1607
  # Delete it from the index
1608
1608
  try:
1609
- self.writeback_es.delete(index=self.writeback_index, id=_id)
1609
+ self.writeback_es.delete_by_query(index=self.writeback_index, body={'query': {'term': {'_id': _id}}})
1610
1610
  except ElasticsearchException: # TODO: Give this a more relevant exception, try:except: is evil.
1611
1611
  self.handle_error("Failed to delete alert %s at %s" % (_id, alert_time))
1612
1612
 
@@ -1640,7 +1640,7 @@ class ElastAlerter(object):
1640
1640
  size=self.max_aggregation)
1641
1641
  for match in res['hits']['hits']:
1642
1642
  matches.append(match['_source'])
1643
- self.writeback_es.delete(index=self.writeback_index, id=match['_id'])
1643
+ self.writeback_es.delete_by_query(index=self.writeback_index, body={'query': {'term': {'_id': match['_id']}}})
1644
1644
  except (KeyError, ElasticsearchException) as e:
1645
1645
  self.handle_error("Error fetching aggregated matches: %s" % (e), {'id': _id})
1646
1646
  return matches
@@ -52,7 +52,7 @@ from elastalert.alerters.pagerduty import PagerDutyAlerter
52
52
  from elastalert.alerters.slack import SlackAlerter
53
53
  from elastalert.alerters.smseagle import SMSEagleAlerter
54
54
  from elastalert.alerters.sns import SnsAlerter
55
- from elastalert.alerters.teams import MsTeamsAlerter
55
+ from elastalert.alerters.sqs import SqsAlerter
56
56
  from elastalert.alerters.powerautomate import MsPowerAutomateAlerter
57
57
  from elastalert.alerters.yzj import YzjAlerter
58
58
  from elastalert.alerters.zabbix import ZabbixAlerter
@@ -115,7 +115,7 @@ class RulesLoader(object):
115
115
  'debug': elastalert.alerters.debug.DebugAlerter,
116
116
  'command': elastalert.alerters.command.CommandAlerter,
117
117
  'sns': SnsAlerter,
118
- 'ms_teams': MsTeamsAlerter,
118
+ 'sqs': SqsAlerter,
119
119
  'ms_power_automate': MsPowerAutomateAlerter,
120
120
  'slack': SlackAlerter,
121
121
  'mattermost': MattermostAlerter,
@@ -51,13 +51,6 @@ definitions:
51
51
  type: array
52
52
  items: *slackField
53
53
 
54
- msTeamsFact: &msTeamsFact
55
- type: object
56
- additionalProperties: false
57
- properties:
58
- name: {type: string}
59
- value: {type: string}
60
-
61
54
  msPowerAutomateFact: &msPowerAutomateFact
62
55
  type: object
63
56
  additionalProperties: false
@@ -65,10 +58,6 @@ definitions:
65
58
  name: {type: string}
66
59
  value: {type: string}
67
60
 
68
- arrayOfMsTeamsFacts: &arrayOfMsTeamsFacts
69
- type: array
70
- items: *msTeamsFact
71
-
72
61
  arrayOfMsPowerAutomateFacts: &arrayOfMsPowerAutomateFacts
73
62
  type: array
74
63
  items: *msPowerAutomateFact
@@ -319,7 +308,17 @@ properties:
319
308
  notify_all_errors: {type: boolean}
320
309
 
321
310
  ### summary table
322
- summary_table_fields: {type: array, items: {type: string}}
311
+ summary_table_fields:
312
+ type: array
313
+ items:
314
+ oneOf:
315
+ - type: string
316
+ - type: object
317
+ additionalProperties: false
318
+ required: [path]
319
+ properties:
320
+ path: {type: string, minLength: 1}
321
+ header: {type: string}
323
322
  summary_table_type: {type: string, enum: ['ascii', 'html', 'markdown']}
324
323
  summary_table_max_rows: {type: integer, minimum: 0}
325
324
  summary_prefix: {type: string}
@@ -445,6 +444,13 @@ properties:
445
444
  sns_aws_region: {type: string}
446
445
  sns_aws_profile: {type: string}
447
446
 
447
+ ### AWS SQS
448
+ sqs_queue_url: {type: string}
449
+ sqs_aws_access_key_id: {type: string}
450
+ sqs_aws_secret_access_key: {type: string}
451
+ sqs_aws_region: {type: string}
452
+ sqs_aws_profile: {type: string}
453
+
448
454
  ### Chatwork
449
455
  chatwork_apikey: {type: string}
450
456
  chatwork_room_id: {type: string}
@@ -461,6 +467,7 @@ properties:
461
467
  ### Datadog
462
468
  datadog_api_key: {type: string}
463
469
  datadog_app_key: {type: string}
470
+ datadog_api_url: {type: string}
464
471
 
465
472
  ### Dingtalk
466
473
  dingtalk_access_token: {type: string}
@@ -703,22 +710,6 @@ properties:
703
710
  mattermost_opensearch_discover_color: {type: string}
704
711
  mattermost_opensearch_discover_title: {type: string}
705
712
 
706
-
707
- ### Microsoft Teams
708
- ms_teams_webhook_url: *arrayOfString
709
- ms_teams_alert_summary: {type: string}
710
- ms_teams_theme_color: {type: string}
711
- ms_teams_proxy: {type: string}
712
- ms_teams_alert_fixed_width: {type: boolean}
713
- ms_teams_alert_facts: *arrayOfMsTeamsFacts
714
- ms_teams_attach_kibana_discover_url: {type: boolean}
715
- ms_teams_kibana_discover_title: {type: string}
716
- ms_teams_ca_certs: {type: [boolean, string]}
717
- ms_teams_ignore_ssl_errors: {type: boolean}
718
- ms_teams_attach_opensearch_discover_url: {type: boolean}
719
- ms_teams_opensearch_discover_title: {type: string}
720
-
721
-
722
713
  ### Microsoft Power Automate
723
714
  ms_power_automate_webhook_url: *arrayOfString
724
715
  ms_power_automate_webhook_url_from_field: { type: string }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: elastalert2
3
- Version: 2.28.0
3
+ Version: 2.30.0
4
4
  Summary: Automated rule-based alerting for Elasticsearch
5
5
  Home-page: https://github.com/jertel/elastalert2
6
6
  License: Apache 2.0
@@ -15,31 +15,32 @@ Classifier: Operating System :: OS Independent
15
15
  Requires-Python: >=3.12
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
- Requires-Dist: apscheduler<4.0,>=3.11.0
18
+ Requires-Dist: apscheduler<4.0,>=3.11.2
19
19
  Requires-Dist: aws-requests-auth>=0.4.3
20
- Requires-Dist: boto3>=1.40.59
20
+ Requires-Dist: boto3>=1.42.89
21
21
  Requires-Dist: cffi>=2.0.0
22
- Requires-Dist: croniter>=6.0.0
22
+ Requires-Dist: croniter>=6.2.2
23
23
  Requires-Dist: elasticsearch==7.10.1
24
24
  Requires-Dist: envparse>=0.2.0
25
25
  Requires-Dist: exotel==0.1.5
26
26
  Requires-Dist: Jinja2>=3.1.6
27
27
  Requires-Dist: jira>=3.10.5
28
- Requires-Dist: jsonpointer>=3.0.0
29
- Requires-Dist: jsonschema>=4.25.1
30
- Requires-Dist: prettytable>=3.16.0
28
+ Requires-Dist: jsonpointer>=3.1.1
29
+ Requires-Dist: jsonschema>=4.26.0
30
+ Requires-Dist: prettytable>=3.17.0
31
31
  Requires-Dist: prison>=0.2.1
32
- Requires-Dist: prometheus_client>=0.23.1
32
+ Requires-Dist: prometheus-client>=0.25.0
33
33
  Requires-Dist: python-dateutil>=2.9.0.post0
34
+ Requires-Dist: pytz>=2026.1.post1
34
35
  Requires-Dist: PyYAML>=6.0.3
35
- Requires-Dist: zabbix-utils>=2.0.3
36
- Requires-Dist: requests>=2.31.0
36
+ Requires-Dist: zabbix-utils>=2.0.4
37
+ Requires-Dist: requests>=2.33.1
37
38
  Requires-Dist: sortedcontainers>=2.4.0
38
39
  Requires-Dist: statsd-tags==3.2.1.post1
39
40
  Requires-Dist: stomp.py>=8.2.0
40
- Requires-Dist: tencentcloud-sdk-python>=3.0.1479
41
+ Requires-Dist: tencentcloud-sdk-python>=3.1.79
41
42
  Requires-Dist: texttable>=1.7.0
42
- Requires-Dist: twilio>=9.8.4
43
+ Requires-Dist: twilio>=9.10.5
43
44
  Dynamic: classifier
44
45
  Dynamic: description
45
46
  Dynamic: description-content-type
@@ -85,7 +86,7 @@ See our [security policy][13] for reporting urgent vulnerabilities.
85
86
 
86
87
  ElastAlert 2 is licensed under the [Apache License, Version 2.0][5].
87
88
 
88
- [0]: https://github.com/yelp/elastalert
89
+ [0]: https://github.com/YelpArchive/elastalert
89
90
  [1]: https://github.com/jertel/elastalert2/blob/master/examples/config.yaml.example
90
91
  [2]: https://hub.docker.com/r/jertel/elastalert2
91
92
  [3]: https://elastalert2.readthedocs.io/
@@ -55,8 +55,8 @@ elastalert/alerters/ses.py
55
55
  elastalert/alerters/slack.py
56
56
  elastalert/alerters/smseagle.py
57
57
  elastalert/alerters/sns.py
58
+ elastalert/alerters/sqs.py
58
59
  elastalert/alerters/stomp.py
59
- elastalert/alerters/teams.py
60
60
  elastalert/alerters/telegram.py
61
61
  elastalert/alerters/tencentsms.py
62
62
  elastalert/alerters/thehive.py
@@ -116,8 +116,8 @@ tests/alerters/ses_test.py
116
116
  tests/alerters/slack_test.py
117
117
  tests/alerters/smseagle_test.py
118
118
  tests/alerters/sns_test.py
119
+ tests/alerters/sqs_test.py
119
120
  tests/alerters/stomp_test.py
120
- tests/alerters/teams_test.py
121
121
  tests/alerters/telegram_test.py
122
122
  tests/alerters/tencentsms_test.py
123
123
  tests/alerters/thehive_test.py
@@ -1,25 +1,26 @@
1
- apscheduler<4.0,>=3.11.0
1
+ apscheduler<4.0,>=3.11.2
2
2
  aws-requests-auth>=0.4.3
3
- boto3>=1.40.59
3
+ boto3>=1.42.89
4
4
  cffi>=2.0.0
5
- croniter>=6.0.0
5
+ croniter>=6.2.2
6
6
  elasticsearch==7.10.1
7
7
  envparse>=0.2.0
8
8
  exotel==0.1.5
9
9
  Jinja2>=3.1.6
10
10
  jira>=3.10.5
11
- jsonpointer>=3.0.0
12
- jsonschema>=4.25.1
13
- prettytable>=3.16.0
11
+ jsonpointer>=3.1.1
12
+ jsonschema>=4.26.0
13
+ prettytable>=3.17.0
14
14
  prison>=0.2.1
15
- prometheus_client>=0.23.1
15
+ prometheus-client>=0.25.0
16
16
  python-dateutil>=2.9.0.post0
17
+ pytz>=2026.1.post1
17
18
  PyYAML>=6.0.3
18
- zabbix-utils>=2.0.3
19
- requests>=2.31.0
19
+ zabbix-utils>=2.0.4
20
+ requests>=2.33.1
20
21
  sortedcontainers>=2.4.0
21
22
  statsd-tags==3.2.1.post1
22
23
  stomp.py>=8.2.0
23
- tencentcloud-sdk-python>=3.0.1479
24
+ tencentcloud-sdk-python>=3.1.79
24
25
  texttable>=1.7.0
25
- twilio>=9.8.4
26
+ twilio>=9.10.5
@@ -8,7 +8,7 @@ from setuptools import setup
8
8
  base_dir = os.path.dirname(__file__)
9
9
  setup(
10
10
  name='elastalert2',
11
- version='2.28.0',
11
+ version='2.30.0',
12
12
  description='Automated rule-based alerting for Elasticsearch',
13
13
  long_description=open('README.md').read(),
14
14
  long_description_content_type="text/markdown",
@@ -35,30 +35,31 @@ setup(
35
35
  package_data={'elastalert': ['schema.yaml', 'es_mappings/**/*.json']},
36
36
  python_requires='>=3.12',
37
37
  install_requires=[
38
- 'apscheduler>=3.11.0,<4.0',
38
+ 'apscheduler>=3.11.2,<4.0',
39
39
  'aws-requests-auth>=0.4.3',
40
- 'boto3>=1.40.59',
40
+ 'boto3>=1.42.89',
41
41
  'cffi>=2.0.0',
42
- 'croniter>=6.0.0',
42
+ 'croniter>=6.2.2',
43
43
  'elasticsearch==7.10.1',
44
44
  'envparse>=0.2.0',
45
45
  'exotel==0.1.5',
46
46
  'Jinja2>=3.1.6',
47
47
  'jira>=3.10.5',
48
- 'jsonpointer>=3.0.0',
49
- 'jsonschema>=4.25.1',
50
- 'prettytable>=3.16.0',
48
+ 'jsonpointer>=3.1.1',
49
+ 'jsonschema>=4.26.0',
50
+ 'prettytable>=3.17.0',
51
51
  'prison>=0.2.1',
52
- 'prometheus_client>=0.23.1',
52
+ 'prometheus-client>=0.25.0',
53
53
  'python-dateutil>=2.9.0.post0',
54
+ 'pytz>=2026.1.post1',
54
55
  'PyYAML>=6.0.3',
55
- 'zabbix-utils>=2.0.3',
56
- 'requests>=2.31.0',
56
+ 'zabbix-utils>=2.0.4',
57
+ 'requests>=2.33.1',
57
58
  'sortedcontainers>=2.4.0',
58
59
  'statsd-tags==3.2.1.post1',
59
60
  'stomp.py>=8.2.0',
60
- 'tencentcloud-sdk-python>=3.0.1479',
61
+ 'tencentcloud-sdk-python>=3.1.79',
61
62
  'texttable>=1.7.0',
62
- 'twilio>=9.8.4',
63
+ 'twilio>=9.10.5',
63
64
  ]
64
65
  )
@@ -49,6 +49,73 @@ def test_datadog_alerter(caplog):
49
49
  assert ('elastalert', logging.INFO, 'Alert sent to Datadog') == caplog.record_tuples[0]
50
50
 
51
51
 
52
+ def test_datadog_alerter_custom_api_url(caplog):
53
+ """datadog_api_url overrides the default endpoint when provided."""
54
+ caplog.set_level(logging.INFO)
55
+ rule = {
56
+ 'name': 'Test Datadog Event Alerter',
57
+ 'type': 'any',
58
+ 'datadog_api_key': 'test-api-key',
59
+ 'datadog_app_key': 'test-app-key',
60
+ 'datadog_api_url': 'https://api.us5.datadoghq.com/api/v1/events',
61
+ 'alert': [],
62
+ 'alert_subject': 'Test Datadog Event Alert'
63
+ }
64
+ rules_loader = FileRulesLoader({})
65
+ rules_loader.load_modules(rule)
66
+ alert = DatadogAlerter(rule)
67
+ match = {
68
+ '@timestamp': '2021-01-01T00:00:00',
69
+ 'name': 'datadog-test-name'
70
+ }
71
+ with mock.patch('requests.post') as mock_post_request:
72
+ alert.alert([match])
73
+
74
+ mock_post_request.assert_called_once_with(
75
+ 'https://api.us5.datadoghq.com/api/v1/events',
76
+ data=mock.ANY,
77
+ headers={
78
+ 'Content-Type': 'application/json',
79
+ 'DD-API-KEY': rule['datadog_api_key'],
80
+ 'DD-APPLICATION-KEY': rule['datadog_app_key']
81
+ }
82
+ )
83
+ assert ('elastalert', logging.INFO, 'Alert sent to Datadog') == caplog.record_tuples[0]
84
+
85
+
86
+ def test_datadog_alerter_default_api_url(caplog):
87
+ """datadog_api_url defaults to the US1 endpoint when not provided."""
88
+ caplog.set_level(logging.INFO)
89
+ rule = {
90
+ 'name': 'Test Datadog Event Alerter',
91
+ 'type': 'any',
92
+ 'datadog_api_key': 'test-api-key',
93
+ 'datadog_app_key': 'test-app-key',
94
+ 'alert': [],
95
+ 'alert_subject': 'Test Datadog Event Alert'
96
+ }
97
+ rules_loader = FileRulesLoader({})
98
+ rules_loader.load_modules(rule)
99
+ alert = DatadogAlerter(rule)
100
+ match = {
101
+ '@timestamp': '2021-01-01T00:00:00',
102
+ 'name': 'datadog-test-name'
103
+ }
104
+ with mock.patch('requests.post') as mock_post_request:
105
+ alert.alert([match])
106
+
107
+ mock_post_request.assert_called_once_with(
108
+ 'https://api.datadoghq.com/api/v1/events',
109
+ data=mock.ANY,
110
+ headers={
111
+ 'Content-Type': 'application/json',
112
+ 'DD-API-KEY': rule['datadog_api_key'],
113
+ 'DD-APPLICATION-KEY': rule['datadog_app_key']
114
+ }
115
+ )
116
+ assert ('elastalert', logging.INFO, 'Alert sent to Datadog') == caplog.record_tuples[0]
117
+
118
+
52
119
  def test_datadog_ea_exception():
53
120
  with pytest.raises(EAException) as ea:
54
121
  rule = {