elastalert2 2.29.0__tar.gz → 2.31.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.
- {elastalert2-2.29.0/elastalert2.egg-info → elastalert2-2.31.0}/PKG-INFO +13 -13
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/__init__.py +10 -1
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/datadog.py +2 -4
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerts.py +24 -9
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/elastalert.py +5 -4
- elastalert2-2.31.0/elastalert/esql.py +76 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/kibana_discover.py +31 -1
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/loaders.py +0 -2
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/schema.yaml +12 -28
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/test_rule.py +1 -1
- {elastalert2-2.29.0 → elastalert2-2.31.0/elastalert2.egg-info}/PKG-INFO +13 -13
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert2.egg-info/SOURCES.txt +1 -2
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert2.egg-info/requires.txt +12 -12
- {elastalert2-2.29.0 → elastalert2-2.31.0}/setup.py +13 -13
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/datadog_test.py +67 -0
- elastalert2-2.29.0/elastalert/alerters/teams.py +0 -120
- elastalert2-2.29.0/tests/alerters/teams_test.py +0 -650
- {elastalert2-2.29.0 → elastalert2-2.31.0}/LICENSE +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/README.md +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/__init__.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/alerta.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/alertmanager.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/chatwork.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/command.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/debug.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/dingtalk.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/discord.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/email.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/exotel.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/flashduty.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/gelf.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/gitter.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/googlechat.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/httppost.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/httppost2.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/indexer.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/iris.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/jira.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/lark.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/line.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/matrixhookshot.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/mattermost.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/opsgenie.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/pagerduty.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/pagertree.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/powerautomate.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/rocketchat.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/servicenow.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/ses.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/slack.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/smseagle.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/sns.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/sqs.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/stomp.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/telegram.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/tencentsms.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/thehive.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/twilio.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/victorops.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/webex_webhook.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/workwechat.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/yzj.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/alerters/zabbix.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/auth.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/config.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/create_index.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/enhancements.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/eql.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/es_mappings/7/elastalert.json +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/es_mappings/7/elastalert_error.json +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/es_mappings/7/elastalert_status.json +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/es_mappings/7/past_elastalert.json +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/es_mappings/7/silence.json +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/es_mappings/8/elastalert.json +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/es_mappings/8/elastalert_error.json +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/es_mappings/8/elastalert_status.json +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/es_mappings/8/past_elastalert.json +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/es_mappings/8/silence.json +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/kibana_external_url_formatter.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/opensearch_discover.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/opensearch_external_url_formatter.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/prometheus_wrapper.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/ruletypes.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/util.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert/yaml.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert2.egg-info/dependency_links.txt +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert2.egg-info/entry_points.txt +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/elastalert2.egg-info/top_level.txt +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/setup.cfg +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/__init__.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/alerta_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/alertmanager_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/chatwork_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/command_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/debug_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/dingtalk_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/discord_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/email_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/exotel_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/flashduty_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/gelf_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/gitter_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/googlechat_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/httppost2_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/httppost_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/indexer_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/iris_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/jira_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/lark_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/line_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/matrixhookshot_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/mattermost_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/opsgenie_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/pagerduty_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/pagertree_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/powerautomate_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/rocketchat_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/servicenow_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/ses_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/slack_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/smseagle_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/sns_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/sqs_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/stomp_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/telegram_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/tencentsms_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/thehive_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/twilio_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/victorops_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/webex_webhook_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/workwechat_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.0}/tests/alerters/yzj_test.py +0 -0
- {elastalert2-2.29.0 → elastalert2-2.31.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.
|
|
3
|
+
Version: 2.31.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.
|
|
18
|
+
Requires-Dist: apscheduler<4.0,>=3.11.2
|
|
19
19
|
Requires-Dist: aws-requests-auth>=0.4.3
|
|
20
|
-
Requires-Dist: boto3>=1.
|
|
20
|
+
Requires-Dist: boto3>=1.42.89
|
|
21
21
|
Requires-Dist: cffi>=2.0.0
|
|
22
|
-
Requires-Dist: croniter>=6.
|
|
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.
|
|
29
|
-
Requires-Dist: jsonschema>=4.
|
|
30
|
-
Requires-Dist: prettytable>=3.
|
|
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:
|
|
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.
|
|
37
|
-
Requires-Dist: requests>=2.
|
|
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.
|
|
41
|
+
Requires-Dist: tencentcloud-sdk-python>=3.1.79
|
|
42
42
|
Requires-Dist: texttable>=1.7.0
|
|
43
|
-
Requires-Dist: twilio>=9.
|
|
43
|
+
Requires-Dist: twilio>=9.10.5
|
|
44
44
|
Dynamic: classifier
|
|
45
45
|
Dynamic: description
|
|
46
46
|
Dynamic: description-content-type
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
import copy
|
|
3
3
|
import elastalert.eql as eql
|
|
4
|
+
import elastalert.esql as esql
|
|
4
5
|
|
|
5
6
|
from elasticsearch import Elasticsearch
|
|
6
7
|
from elasticsearch import RequestsHttpConnection
|
|
@@ -134,6 +135,7 @@ class ElasticSearchClient(Elasticsearch):
|
|
|
134
135
|
|
|
135
136
|
path = _make_path(index, doc_type, "_search")
|
|
136
137
|
eql_body = eql.format_request(body)
|
|
138
|
+
esql_body = esql.format_request(body)
|
|
137
139
|
if eql_body is not None:
|
|
138
140
|
path = path.replace('/_search', '/_eql/search')
|
|
139
141
|
body = eql_body
|
|
@@ -143,6 +145,10 @@ class ElasticSearchClient(Elasticsearch):
|
|
|
143
145
|
params.pop('scroll')
|
|
144
146
|
if '_source_includes' in params:
|
|
145
147
|
params.pop('_source_includes')
|
|
148
|
+
elif esql_body is not None:
|
|
149
|
+
path = "/_query"
|
|
150
|
+
body = esql_body
|
|
151
|
+
params = {'format': 'json'}
|
|
146
152
|
|
|
147
153
|
results = self.transport.perform_request(
|
|
148
154
|
"POST",
|
|
@@ -152,6 +158,9 @@ class ElasticSearchClient(Elasticsearch):
|
|
|
152
158
|
body=body,
|
|
153
159
|
)
|
|
154
160
|
|
|
155
|
-
|
|
161
|
+
if esql_body is not None:
|
|
162
|
+
esql.format_results(results, default_index=index)
|
|
163
|
+
else:
|
|
164
|
+
eql.format_results(results)
|
|
156
165
|
|
|
157
166
|
return results
|
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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,
|
|
310
|
+
text += '| ' + ' | '.join(map(str, summary_table_headers_with_count)) + ' |\n'
|
|
296
311
|
# Create header separator
|
|
297
|
-
text += '|-----' * len(
|
|
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 =
|
|
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
|
|
@@ -337,9 +337,10 @@ class ElastAlerter(object):
|
|
|
337
337
|
|
|
338
338
|
# Convert the timestamp to a datetime
|
|
339
339
|
ts = lookup_es_key(hit['_source'], rule['timestamp_field'])
|
|
340
|
-
if
|
|
340
|
+
if ts is None:
|
|
341
341
|
raise EAException(
|
|
342
|
-
"Error:
|
|
342
|
+
f"Error: The configured timestamp_field '{rule['timestamp_field']}' was not found in the query results. "
|
|
343
|
+
f"Available fields: {list(hit['_source'].keys())}"
|
|
343
344
|
)
|
|
344
345
|
|
|
345
346
|
set_es_key(hit['_source'], rule['timestamp_field'], rule['ts_to_dt'](ts))
|
|
@@ -1606,7 +1607,7 @@ class ElastAlerter(object):
|
|
|
1606
1607
|
|
|
1607
1608
|
# Delete it from the index
|
|
1608
1609
|
try:
|
|
1609
|
-
self.writeback_es.
|
|
1610
|
+
self.writeback_es.delete_by_query(index=self.writeback_index, body={'query': {'term': {'_id': _id}}})
|
|
1610
1611
|
except ElasticsearchException: # TODO: Give this a more relevant exception, try:except: is evil.
|
|
1611
1612
|
self.handle_error("Failed to delete alert %s at %s" % (_id, alert_time))
|
|
1612
1613
|
|
|
@@ -1640,7 +1641,7 @@ class ElastAlerter(object):
|
|
|
1640
1641
|
size=self.max_aggregation)
|
|
1641
1642
|
for match in res['hits']['hits']:
|
|
1642
1643
|
matches.append(match['_source'])
|
|
1643
|
-
self.writeback_es.
|
|
1644
|
+
self.writeback_es.delete_by_query(index=self.writeback_index, body={'query': {'term': {'_id': match['_id']}}})
|
|
1644
1645
|
except (KeyError, ElasticsearchException) as e:
|
|
1645
1646
|
self.handle_error("Error fetching aggregated matches: %s" % (e), {'id': _id})
|
|
1646
1647
|
return matches
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def format_request(body):
|
|
6
|
+
query = body.get('query')
|
|
7
|
+
if not query:
|
|
8
|
+
return None
|
|
9
|
+
|
|
10
|
+
query_bool = query.get('bool')
|
|
11
|
+
if not query_bool:
|
|
12
|
+
return None
|
|
13
|
+
|
|
14
|
+
filt = query_bool.get('filter')
|
|
15
|
+
if not filt:
|
|
16
|
+
return None
|
|
17
|
+
|
|
18
|
+
filter_bool = filt.get('bool')
|
|
19
|
+
if not filter_bool:
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
filter_bool_must = filter_bool.get('must')
|
|
23
|
+
if not filter_bool_must:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
other_filters = []
|
|
27
|
+
esql = None
|
|
28
|
+
for f in filter_bool_must:
|
|
29
|
+
if f.get('esql'):
|
|
30
|
+
esql = f['esql']
|
|
31
|
+
else:
|
|
32
|
+
other_filters.append(f)
|
|
33
|
+
|
|
34
|
+
if esql:
|
|
35
|
+
new_body = {'filter': {'bool': {'must': other_filters}}, 'query': esql}
|
|
36
|
+
return new_body
|
|
37
|
+
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def format_results(results, default_index=None):
|
|
42
|
+
columns = results.pop('columns', None)
|
|
43
|
+
values = results.pop('values', None)
|
|
44
|
+
if columns is None or values is None:
|
|
45
|
+
return results
|
|
46
|
+
|
|
47
|
+
col_names = [col['name'] for col in columns]
|
|
48
|
+
hits = []
|
|
49
|
+
for val_row in values:
|
|
50
|
+
doc = dict(zip(col_names, val_row))
|
|
51
|
+
|
|
52
|
+
# Pull metadata fields if present
|
|
53
|
+
doc_id = doc.get('_id', None)
|
|
54
|
+
doc_index = doc.get('_index', default_index)
|
|
55
|
+
|
|
56
|
+
if not doc_id:
|
|
57
|
+
# Generate deterministic hash of the document to use as unique ID
|
|
58
|
+
doc_hash = hashlib.sha256(json.dumps(doc, sort_keys=True, default=str).encode('utf-8')).hexdigest()
|
|
59
|
+
doc_id = doc_hash
|
|
60
|
+
|
|
61
|
+
hit = {
|
|
62
|
+
'_index': doc_index,
|
|
63
|
+
'_id': doc_id,
|
|
64
|
+
'_source': doc
|
|
65
|
+
}
|
|
66
|
+
hits.append(hit)
|
|
67
|
+
|
|
68
|
+
results['hits'] = {
|
|
69
|
+
'total': {
|
|
70
|
+
'value': len(values),
|
|
71
|
+
'relation': 'eq'
|
|
72
|
+
},
|
|
73
|
+
'hits': hits
|
|
74
|
+
}
|
|
75
|
+
results['esql'] = True
|
|
76
|
+
return results
|
|
@@ -74,6 +74,36 @@ def kibana7_disover_global_state(from_time, to_time):
|
|
|
74
74
|
} )
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
def _normalize_query_value(value):
|
|
78
|
+
"""Collapse empty/single-element query_key values before filter emission.
|
|
79
|
+
|
|
80
|
+
Elasticsearch returns multi-valued fields as arrays even when they hold a
|
|
81
|
+
single item, and rules using such fields as ``query_key`` otherwise render
|
|
82
|
+
Kibana filters with ``params.query: ['']`` — a phrase filter that matches
|
|
83
|
+
no documents, producing an empty Discover view for the reviewer.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
- ``None`` when the value is absent, an empty string, or a list that
|
|
87
|
+
collapses to nothing after dropping ``None``/empty-string entries;
|
|
88
|
+
callers emit the existing ``negate exists`` filter for this case.
|
|
89
|
+
- The sole surviving element when the input is a single-item list.
|
|
90
|
+
- The original value otherwise (scalars pass through; multi-value lists
|
|
91
|
+
are returned unchanged so callers may extend to a ``phrases`` filter).
|
|
92
|
+
"""
|
|
93
|
+
if value is None:
|
|
94
|
+
return None
|
|
95
|
+
if isinstance(value, str):
|
|
96
|
+
return None if value == '' else value
|
|
97
|
+
if isinstance(value, list):
|
|
98
|
+
cleaned = [v for v in value if v not in (None, '')]
|
|
99
|
+
if not cleaned:
|
|
100
|
+
return None
|
|
101
|
+
if len(cleaned) == 1:
|
|
102
|
+
return cleaned[0]
|
|
103
|
+
return cleaned
|
|
104
|
+
return value
|
|
105
|
+
|
|
106
|
+
|
|
77
107
|
def kibana_discover_app_state(index, columns, filters, query_keys, match):
|
|
78
108
|
app_filters = []
|
|
79
109
|
|
|
@@ -105,7 +135,7 @@ def kibana_discover_app_state(index, columns, filters, query_keys, match):
|
|
|
105
135
|
} )
|
|
106
136
|
|
|
107
137
|
for query_key in query_keys:
|
|
108
|
-
query_value = lookup_es_key(match, query_key)
|
|
138
|
+
query_value = _normalize_query_value(lookup_es_key(match, query_key))
|
|
109
139
|
|
|
110
140
|
if query_value is None:
|
|
111
141
|
app_filters.append( {
|
|
@@ -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:
|
|
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.
|
|
3
|
+
Version: 2.31.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.
|
|
18
|
+
Requires-Dist: apscheduler<4.0,>=3.11.2
|
|
19
19
|
Requires-Dist: aws-requests-auth>=0.4.3
|
|
20
|
-
Requires-Dist: boto3>=1.
|
|
20
|
+
Requires-Dist: boto3>=1.42.89
|
|
21
21
|
Requires-Dist: cffi>=2.0.0
|
|
22
|
-
Requires-Dist: croniter>=6.
|
|
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.
|
|
29
|
-
Requires-Dist: jsonschema>=4.
|
|
30
|
-
Requires-Dist: prettytable>=3.
|
|
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:
|
|
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.
|
|
37
|
-
Requires-Dist: requests>=2.
|
|
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.
|
|
41
|
+
Requires-Dist: tencentcloud-sdk-python>=3.1.79
|
|
42
42
|
Requires-Dist: texttable>=1.7.0
|
|
43
|
-
Requires-Dist: twilio>=9.
|
|
43
|
+
Requires-Dist: twilio>=9.10.5
|
|
44
44
|
Dynamic: classifier
|
|
45
45
|
Dynamic: description
|
|
46
46
|
Dynamic: description-content-type
|
|
@@ -10,6 +10,7 @@ elastalert/create_index.py
|
|
|
10
10
|
elastalert/elastalert.py
|
|
11
11
|
elastalert/enhancements.py
|
|
12
12
|
elastalert/eql.py
|
|
13
|
+
elastalert/esql.py
|
|
13
14
|
elastalert/kibana_discover.py
|
|
14
15
|
elastalert/kibana_external_url_formatter.py
|
|
15
16
|
elastalert/loaders.py
|
|
@@ -57,7 +58,6 @@ elastalert/alerters/smseagle.py
|
|
|
57
58
|
elastalert/alerters/sns.py
|
|
58
59
|
elastalert/alerters/sqs.py
|
|
59
60
|
elastalert/alerters/stomp.py
|
|
60
|
-
elastalert/alerters/teams.py
|
|
61
61
|
elastalert/alerters/telegram.py
|
|
62
62
|
elastalert/alerters/tencentsms.py
|
|
63
63
|
elastalert/alerters/thehive.py
|
|
@@ -119,7 +119,6 @@ tests/alerters/smseagle_test.py
|
|
|
119
119
|
tests/alerters/sns_test.py
|
|
120
120
|
tests/alerters/sqs_test.py
|
|
121
121
|
tests/alerters/stomp_test.py
|
|
122
|
-
tests/alerters/teams_test.py
|
|
123
122
|
tests/alerters/telegram_test.py
|
|
124
123
|
tests/alerters/tencentsms_test.py
|
|
125
124
|
tests/alerters/thehive_test.py
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
apscheduler<4.0,>=3.11.
|
|
1
|
+
apscheduler<4.0,>=3.11.2
|
|
2
2
|
aws-requests-auth>=0.4.3
|
|
3
|
-
boto3>=1.
|
|
3
|
+
boto3>=1.42.89
|
|
4
4
|
cffi>=2.0.0
|
|
5
|
-
croniter>=6.
|
|
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.
|
|
12
|
-
jsonschema>=4.
|
|
13
|
-
prettytable>=3.
|
|
11
|
+
jsonpointer>=3.1.1
|
|
12
|
+
jsonschema>=4.26.0
|
|
13
|
+
prettytable>=3.17.0
|
|
14
14
|
prison>=0.2.1
|
|
15
|
-
|
|
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.
|
|
20
|
-
requests>=2.
|
|
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.
|
|
24
|
+
tencentcloud-sdk-python>=3.1.79
|
|
25
25
|
texttable>=1.7.0
|
|
26
|
-
twilio>=9.
|
|
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.
|
|
11
|
+
version='2.31.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.
|
|
38
|
+
'apscheduler>=3.11.2,<4.0',
|
|
39
39
|
'aws-requests-auth>=0.4.3',
|
|
40
|
-
'boto3>=1.
|
|
40
|
+
'boto3>=1.42.89',
|
|
41
41
|
'cffi>=2.0.0',
|
|
42
|
-
'croniter>=6.
|
|
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.
|
|
49
|
-
'jsonschema>=4.
|
|
50
|
-
'prettytable>=3.
|
|
48
|
+
'jsonpointer>=3.1.1',
|
|
49
|
+
'jsonschema>=4.26.0',
|
|
50
|
+
'prettytable>=3.17.0',
|
|
51
51
|
'prison>=0.2.1',
|
|
52
|
-
'
|
|
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.
|
|
57
|
-
'requests>=2.
|
|
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.
|
|
61
|
+
'tencentcloud-sdk-python>=3.1.79',
|
|
62
62
|
'texttable>=1.7.0',
|
|
63
|
-
'twilio>=9.
|
|
63
|
+
'twilio>=9.10.5',
|
|
64
64
|
]
|
|
65
65
|
)
|