elastalert2 2.29.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.29.0/elastalert2.egg-info → elastalert2-2.30.0}/PKG-INFO +13 -13
  2. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/datadog.py +2 -4
  3. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerts.py +24 -9
  4. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/elastalert.py +2 -2
  5. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/loaders.py +0 -2
  6. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/schema.yaml +12 -28
  7. {elastalert2-2.29.0 → elastalert2-2.30.0/elastalert2.egg-info}/PKG-INFO +13 -13
  8. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert2.egg-info/SOURCES.txt +0 -2
  9. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert2.egg-info/requires.txt +12 -12
  10. {elastalert2-2.29.0 → elastalert2-2.30.0}/setup.py +13 -13
  11. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/datadog_test.py +67 -0
  12. elastalert2-2.29.0/elastalert/alerters/teams.py +0 -120
  13. elastalert2-2.29.0/tests/alerters/teams_test.py +0 -650
  14. {elastalert2-2.29.0 → elastalert2-2.30.0}/LICENSE +0 -0
  15. {elastalert2-2.29.0 → elastalert2-2.30.0}/README.md +0 -0
  16. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/__init__.py +0 -0
  17. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/__init__.py +0 -0
  18. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/alerta.py +0 -0
  19. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/alertmanager.py +0 -0
  20. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/chatwork.py +0 -0
  21. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/command.py +0 -0
  22. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/debug.py +0 -0
  23. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/dingtalk.py +0 -0
  24. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/discord.py +0 -0
  25. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/email.py +0 -0
  26. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/exotel.py +0 -0
  27. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/flashduty.py +0 -0
  28. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/gelf.py +0 -0
  29. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/gitter.py +0 -0
  30. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/googlechat.py +0 -0
  31. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/httppost.py +0 -0
  32. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/httppost2.py +0 -0
  33. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/indexer.py +0 -0
  34. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/iris.py +0 -0
  35. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/jira.py +0 -0
  36. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/lark.py +0 -0
  37. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/line.py +0 -0
  38. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/matrixhookshot.py +0 -0
  39. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/mattermost.py +0 -0
  40. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/opsgenie.py +0 -0
  41. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/pagerduty.py +0 -0
  42. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/pagertree.py +0 -0
  43. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/powerautomate.py +0 -0
  44. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/rocketchat.py +0 -0
  45. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/servicenow.py +0 -0
  46. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/ses.py +0 -0
  47. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/slack.py +0 -0
  48. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/smseagle.py +0 -0
  49. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/sns.py +0 -0
  50. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/sqs.py +0 -0
  51. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/stomp.py +0 -0
  52. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/telegram.py +0 -0
  53. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/tencentsms.py +0 -0
  54. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/thehive.py +0 -0
  55. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/twilio.py +0 -0
  56. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/victorops.py +0 -0
  57. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/webex_webhook.py +0 -0
  58. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/workwechat.py +0 -0
  59. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/yzj.py +0 -0
  60. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/alerters/zabbix.py +0 -0
  61. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/auth.py +0 -0
  62. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/config.py +0 -0
  63. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/create_index.py +0 -0
  64. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/enhancements.py +0 -0
  65. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/eql.py +0 -0
  66. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/es_mappings/7/elastalert.json +0 -0
  67. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/es_mappings/7/elastalert_error.json +0 -0
  68. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/es_mappings/7/elastalert_status.json +0 -0
  69. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/es_mappings/7/past_elastalert.json +0 -0
  70. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/es_mappings/7/silence.json +0 -0
  71. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/es_mappings/8/elastalert.json +0 -0
  72. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/es_mappings/8/elastalert_error.json +0 -0
  73. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/es_mappings/8/elastalert_status.json +0 -0
  74. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/es_mappings/8/past_elastalert.json +0 -0
  75. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/es_mappings/8/silence.json +0 -0
  76. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/kibana_discover.py +0 -0
  77. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/kibana_external_url_formatter.py +0 -0
  78. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/opensearch_discover.py +0 -0
  79. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/opensearch_external_url_formatter.py +0 -0
  80. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/prometheus_wrapper.py +0 -0
  81. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/ruletypes.py +0 -0
  82. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/test_rule.py +0 -0
  83. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/util.py +0 -0
  84. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert/yaml.py +0 -0
  85. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert2.egg-info/dependency_links.txt +0 -0
  86. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert2.egg-info/entry_points.txt +0 -0
  87. {elastalert2-2.29.0 → elastalert2-2.30.0}/elastalert2.egg-info/top_level.txt +0 -0
  88. {elastalert2-2.29.0 → elastalert2-2.30.0}/setup.cfg +0 -0
  89. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/__init__.py +0 -0
  90. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/alerta_test.py +0 -0
  91. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/alertmanager_test.py +0 -0
  92. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/chatwork_test.py +0 -0
  93. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/command_test.py +0 -0
  94. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/debug_test.py +0 -0
  95. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/dingtalk_test.py +0 -0
  96. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/discord_test.py +0 -0
  97. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/email_test.py +0 -0
  98. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/exotel_test.py +0 -0
  99. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/flashduty_test.py +0 -0
  100. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/gelf_test.py +0 -0
  101. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/gitter_test.py +0 -0
  102. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/googlechat_test.py +0 -0
  103. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/httppost2_test.py +0 -0
  104. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/httppost_test.py +0 -0
  105. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/indexer_test.py +0 -0
  106. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/iris_test.py +0 -0
  107. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/jira_test.py +0 -0
  108. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/lark_test.py +0 -0
  109. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/line_test.py +0 -0
  110. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/matrixhookshot_test.py +0 -0
  111. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/mattermost_test.py +0 -0
  112. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/opsgenie_test.py +0 -0
  113. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/pagerduty_test.py +0 -0
  114. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/pagertree_test.py +0 -0
  115. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/powerautomate_test.py +0 -0
  116. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/rocketchat_test.py +0 -0
  117. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/servicenow_test.py +0 -0
  118. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/ses_test.py +0 -0
  119. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/slack_test.py +0 -0
  120. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/smseagle_test.py +0 -0
  121. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/sns_test.py +0 -0
  122. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/sqs_test.py +0 -0
  123. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/stomp_test.py +0 -0
  124. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/telegram_test.py +0 -0
  125. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/tencentsms_test.py +0 -0
  126. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/thehive_test.py +0 -0
  127. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/twilio_test.py +0 -0
  128. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/victorops_test.py +0 -0
  129. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/webex_webhook_test.py +0 -0
  130. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/workwechat_test.py +0 -0
  131. {elastalert2-2.29.0 → elastalert2-2.30.0}/tests/alerters/yzj_test.py +0 -0
  132. {elastalert2-2.29.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.29.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,32 +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
34
+ Requires-Dist: pytz>=2026.1.post1
35
35
  Requires-Dist: PyYAML>=6.0.3
36
- Requires-Dist: zabbix-utils>=2.0.3
37
- Requires-Dist: requests>=2.31.0
36
+ Requires-Dist: zabbix-utils>=2.0.4
37
+ Requires-Dist: requests>=2.33.1
38
38
  Requires-Dist: sortedcontainers>=2.4.0
39
39
  Requires-Dist: statsd-tags==3.2.1.post1
40
40
  Requires-Dist: stomp.py>=8.2.0
41
- Requires-Dist: tencentcloud-sdk-python>=3.0.1479
41
+ Requires-Dist: tencentcloud-sdk-python>=3.1.79
42
42
  Requires-Dist: texttable>=1.7.0
43
- Requires-Dist: twilio>=9.8.4
43
+ Requires-Dist: twilio>=9.10.5
44
44
  Dynamic: classifier
45
45
  Dynamic: description
46
46
  Dynamic: description-content-type
@@ -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)
@@ -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
@@ -53,7 +53,6 @@ from elastalert.alerters.slack import SlackAlerter
53
53
  from elastalert.alerters.smseagle import SMSEagleAlerter
54
54
  from elastalert.alerters.sns import SnsAlerter
55
55
  from elastalert.alerters.sqs import SqsAlerter
56
- from elastalert.alerters.teams import MsTeamsAlerter
57
56
  from elastalert.alerters.powerautomate import MsPowerAutomateAlerter
58
57
  from elastalert.alerters.yzj import YzjAlerter
59
58
  from elastalert.alerters.zabbix import ZabbixAlerter
@@ -117,7 +116,6 @@ class RulesLoader(object):
117
116
  'command': elastalert.alerters.command.CommandAlerter,
118
117
  'sns': SnsAlerter,
119
118
  'sqs': SqsAlerter,
120
- 'ms_teams': MsTeamsAlerter,
121
119
  'ms_power_automate': MsPowerAutomateAlerter,
122
120
  'slack': SlackAlerter,
123
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}
@@ -468,6 +467,7 @@ properties:
468
467
  ### Datadog
469
468
  datadog_api_key: {type: string}
470
469
  datadog_app_key: {type: string}
470
+ datadog_api_url: {type: string}
471
471
 
472
472
  ### Dingtalk
473
473
  dingtalk_access_token: {type: string}
@@ -710,22 +710,6 @@ properties:
710
710
  mattermost_opensearch_discover_color: {type: string}
711
711
  mattermost_opensearch_discover_title: {type: string}
712
712
 
713
-
714
- ### Microsoft Teams
715
- ms_teams_webhook_url: *arrayOfString
716
- ms_teams_alert_summary: {type: string}
717
- ms_teams_theme_color: {type: string}
718
- ms_teams_proxy: {type: string}
719
- ms_teams_alert_fixed_width: {type: boolean}
720
- ms_teams_alert_facts: *arrayOfMsTeamsFacts
721
- ms_teams_attach_kibana_discover_url: {type: boolean}
722
- ms_teams_kibana_discover_title: {type: string}
723
- ms_teams_ca_certs: {type: [boolean, string]}
724
- ms_teams_ignore_ssl_errors: {type: boolean}
725
- ms_teams_attach_opensearch_discover_url: {type: boolean}
726
- ms_teams_opensearch_discover_title: {type: string}
727
-
728
-
729
713
  ### Microsoft Power Automate
730
714
  ms_power_automate_webhook_url: *arrayOfString
731
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.29.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,32 +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
34
+ Requires-Dist: pytz>=2026.1.post1
35
35
  Requires-Dist: PyYAML>=6.0.3
36
- Requires-Dist: zabbix-utils>=2.0.3
37
- Requires-Dist: requests>=2.31.0
36
+ Requires-Dist: zabbix-utils>=2.0.4
37
+ Requires-Dist: requests>=2.33.1
38
38
  Requires-Dist: sortedcontainers>=2.4.0
39
39
  Requires-Dist: statsd-tags==3.2.1.post1
40
40
  Requires-Dist: stomp.py>=8.2.0
41
- Requires-Dist: tencentcloud-sdk-python>=3.0.1479
41
+ Requires-Dist: tencentcloud-sdk-python>=3.1.79
42
42
  Requires-Dist: texttable>=1.7.0
43
- Requires-Dist: twilio>=9.8.4
43
+ Requires-Dist: twilio>=9.10.5
44
44
  Dynamic: classifier
45
45
  Dynamic: description
46
46
  Dynamic: description-content-type
@@ -57,7 +57,6 @@ elastalert/alerters/smseagle.py
57
57
  elastalert/alerters/sns.py
58
58
  elastalert/alerters/sqs.py
59
59
  elastalert/alerters/stomp.py
60
- elastalert/alerters/teams.py
61
60
  elastalert/alerters/telegram.py
62
61
  elastalert/alerters/tencentsms.py
63
62
  elastalert/alerters/thehive.py
@@ -119,7 +118,6 @@ tests/alerters/smseagle_test.py
119
118
  tests/alerters/sns_test.py
120
119
  tests/alerters/sqs_test.py
121
120
  tests/alerters/stomp_test.py
122
- tests/alerters/teams_test.py
123
121
  tests/alerters/telegram_test.py
124
122
  tests/alerters/tencentsms_test.py
125
123
  tests/alerters/thehive_test.py
@@ -1,26 +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
17
+ pytz>=2026.1.post1
18
18
  PyYAML>=6.0.3
19
- zabbix-utils>=2.0.3
20
- requests>=2.31.0
19
+ zabbix-utils>=2.0.4
20
+ requests>=2.33.1
21
21
  sortedcontainers>=2.4.0
22
22
  statsd-tags==3.2.1.post1
23
23
  stomp.py>=8.2.0
24
- tencentcloud-sdk-python>=3.0.1479
24
+ tencentcloud-sdk-python>=3.1.79
25
25
  texttable>=1.7.0
26
- 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.29.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,31 +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',
54
+ 'pytz>=2026.1.post1',
55
55
  'PyYAML>=6.0.3',
56
- 'zabbix-utils>=2.0.3',
57
- 'requests>=2.31.0',
56
+ 'zabbix-utils>=2.0.4',
57
+ 'requests>=2.33.1',
58
58
  'sortedcontainers>=2.4.0',
59
59
  'statsd-tags==3.2.1.post1',
60
60
  'stomp.py>=8.2.0',
61
- 'tencentcloud-sdk-python>=3.0.1479',
61
+ 'tencentcloud-sdk-python>=3.1.79',
62
62
  'texttable>=1.7.0',
63
- 'twilio>=9.8.4',
63
+ 'twilio>=9.10.5',
64
64
  ]
65
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 = {
@@ -1,120 +0,0 @@
1
- import copy
2
- import json
3
- import requests
4
-
5
- from elastalert.alerts import Alerter, DateTimeEncoder
6
- from elastalert.util import EAException, elastalert_logger, lookup_es_key
7
- from requests.exceptions import RequestException
8
-
9
-
10
- class MsTeamsAlerter(Alerter):
11
- """ Creates a Microsoft Teams Conversation Message for each alert """
12
- required_options = frozenset(['ms_teams_webhook_url'])
13
-
14
- def __init__(self, rule):
15
- super(MsTeamsAlerter, self).__init__(rule)
16
- self.ms_teams_webhook_url = self.rule.get('ms_teams_webhook_url', None)
17
- if isinstance(self.ms_teams_webhook_url, str):
18
- self.ms_teams_webhook_url = [self.ms_teams_webhook_url]
19
- self.ms_teams_proxy = self.rule.get('ms_teams_proxy', None)
20
- self.ms_teams_alert_summary = self.rule.get('ms_teams_alert_summary', None)
21
- self.ms_teams_alert_fixed_width = self.rule.get('ms_teams_alert_fixed_width', False)
22
- self.ms_teams_theme_color = self.rule.get('ms_teams_theme_color', '')
23
- self.ms_teams_ca_certs = self.rule.get('ms_teams_ca_certs')
24
- self.ms_teams_ignore_ssl_errors = self.rule.get('ms_teams_ignore_ssl_errors', False)
25
- self.ms_teams_alert_facts = self.rule.get('ms_teams_alert_facts', '')
26
- self.ms_teams_attach_kibana_discover_url = self.rule.get('ms_teams_attach_kibana_discover_url', False)
27
- self.ms_teams_kibana_discover_title = self.rule.get('ms_teams_kibana_discover_title', 'Discover in Kibana')
28
- self.ms_teams_attach_opensearch_discover_url = self.rule.get('ms_teams_attach_opensearch_discover_url', False)
29
- self.ms_teams_opensearch_discover_title = self.rule.get('ms_teams_opensearch_discover_title', 'Discover in opensearch')
30
-
31
- def format_body(self, body):
32
- if self.ms_teams_alert_fixed_width:
33
- body = body.replace('`', "'")
34
- body = "```{0}```".format('```\n\n```'.join(x for x in body.split('\n'))).replace('\n``````', '')
35
- return body
36
-
37
- def populate_facts(self, matches):
38
- alert_facts = []
39
- for arg in self.ms_teams_alert_facts:
40
- arg = copy.copy(arg)
41
- matched_value = lookup_es_key(matches[0], arg['value'])
42
- arg['value'] = matched_value if matched_value is not None else arg['value']
43
- alert_facts.append(arg)
44
- return alert_facts
45
-
46
- def alert(self, matches):
47
- body = self.create_alert_body(matches)
48
- body = self.format_body(body)
49
-
50
- title = self.create_title(matches)
51
- summary = title if self.ms_teams_alert_summary is None else self.ms_teams_alert_summary
52
- # post to Teams
53
- headers = {'content-type': 'application/json'}
54
-
55
- if self.ms_teams_ca_certs:
56
- verify = self.ms_teams_ca_certs
57
- else:
58
- verify = not self.ms_teams_ignore_ssl_errors
59
- if self.ms_teams_ignore_ssl_errors:
60
- requests.packages.urllib3.disable_warnings()
61
-
62
- # set https proxy, if it was provided
63
- proxies = {'https': self.ms_teams_proxy} if self.ms_teams_proxy else None
64
- payload = {
65
- '@type': 'MessageCard',
66
- '@context': 'http://schema.org/extensions',
67
- 'summary': summary ,
68
- 'title': title,
69
- 'sections': [{'text': body}],
70
- }
71
-
72
- if self.ms_teams_alert_facts != '':
73
- payload['sections'][0]['facts'] = self.populate_facts(matches)
74
-
75
- if self.ms_teams_theme_color != '':
76
- payload['themeColor'] = self.ms_teams_theme_color
77
-
78
- if self.ms_teams_attach_kibana_discover_url:
79
- kibana_discover_url = lookup_es_key(matches[0], 'kibana_discover_url')
80
- if kibana_discover_url:
81
- payload['potentialAction'] = [
82
- {
83
- '@type': 'OpenUri',
84
- 'name': self.ms_teams_kibana_discover_title,
85
- 'targets': [
86
- {
87
- 'os': 'default',
88
- 'uri': kibana_discover_url,
89
- }
90
- ],
91
- }
92
- ]
93
- if self.ms_teams_attach_opensearch_discover_url:
94
- opensearch_discover_url = lookup_es_key(matches[0], 'opensearch_discover_url')
95
- if opensearch_discover_url:
96
- payload['potentialAction'] = [
97
- {
98
- '@type': 'OpenUri',
99
- 'name': self.ms_teams_opensearch_discover_title,
100
- 'targets': [
101
- {
102
- 'os': 'default',
103
- 'uri': opensearch_discover_url,
104
- }
105
- ],
106
- }
107
- ]
108
-
109
- for url in self.ms_teams_webhook_url:
110
- try:
111
- response = requests.post(url, data=json.dumps(payload, cls=DateTimeEncoder),
112
- headers=headers, proxies=proxies, verify=verify)
113
- response.raise_for_status()
114
- except RequestException as e:
115
- raise EAException("Error posting to MS Teams: %s" % e)
116
- elastalert_logger.info("Alert sent to MS Teams")
117
-
118
- def get_info(self):
119
- return {'type': 'ms_teams',
120
- 'ms_teams_webhook_url': self.ms_teams_webhook_url}