elastalert2 2.26.0__tar.gz → 2.28.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.26.0/elastalert2.egg-info → elastalert2-2.28.0}/PKG-INFO +18 -16
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/chatwork.py +11 -4
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/jira.py +15 -3
- elastalert2-2.28.0/elastalert/alerters/line.py +61 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/zabbix.py +6 -6
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/elastalert.py +10 -3
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/loaders.py +29 -2
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/opensearch_external_url_formatter.py +1 -10
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/prometheus_wrapper.py +5 -2
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/ruletypes.py +24 -19
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/schema.yaml +7 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0/elastalert2.egg-info}/PKG-INFO +18 -16
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert2.egg-info/SOURCES.txt +2 -0
- elastalert2-2.28.0/elastalert2.egg-info/requires.txt +25 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/setup.py +18 -16
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/chatwork_test.py +18 -16
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/jira_test.py +172 -6
- elastalert2-2.28.0/tests/alerters/line_test.py +185 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/zabbix_test.py +6 -4
- elastalert2-2.26.0/elastalert2.egg-info/requires.txt +0 -25
- {elastalert2-2.26.0 → elastalert2-2.28.0}/LICENSE +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/README.md +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/__init__.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/__init__.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/alerta.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/alertmanager.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/command.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/datadog.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/debug.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/dingtalk.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/discord.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/email.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/exotel.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/flashduty.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/gelf.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/gitter.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/googlechat.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/httppost.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/httppost2.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/indexer.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/iris.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/lark.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/matrixhookshot.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/mattermost.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/opsgenie.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/pagerduty.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/pagertree.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/powerautomate.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/rocketchat.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/servicenow.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/ses.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/slack.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/smseagle.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/sns.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/stomp.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/teams.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/telegram.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/tencentsms.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/thehive.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/twilio.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/victorops.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/webex_webhook.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/workwechat.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerters/yzj.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/alerts.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/auth.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/config.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/create_index.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/enhancements.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/eql.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/es_mappings/7/elastalert.json +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/es_mappings/7/elastalert_error.json +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/es_mappings/7/elastalert_status.json +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/es_mappings/7/past_elastalert.json +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/es_mappings/7/silence.json +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/es_mappings/8/elastalert.json +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/es_mappings/8/elastalert_error.json +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/es_mappings/8/elastalert_status.json +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/es_mappings/8/past_elastalert.json +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/es_mappings/8/silence.json +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/kibana_discover.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/kibana_external_url_formatter.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/opensearch_discover.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/test_rule.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/util.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert/yaml.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert2.egg-info/dependency_links.txt +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert2.egg-info/entry_points.txt +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/elastalert2.egg-info/top_level.txt +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/setup.cfg +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/__init__.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/alerta_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/alertmanager_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/command_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/datadog_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/debug_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/dingtalk_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/discord_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/email_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/exotel_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/flashduty_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/gelf_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/gitter_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/googlechat_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/httppost2_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/httppost_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/indexer_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/iris_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/lark_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/matrixhookshot_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/mattermost_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/opsgenie_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/pagerduty_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/pagertree_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/powerautomate_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/rocketchat_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/servicenow_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/ses_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/slack_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/smseagle_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/sns_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/stomp_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/teams_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/telegram_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/tencentsms_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/thehive_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/twilio_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/victorops_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/webex_webhook_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/workwechat_test.py +0 -0
- {elastalert2-2.26.0 → elastalert2-2.28.0}/tests/alerters/yzj_test.py +0 -0
|
@@ -1,43 +1,45 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: elastalert2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.28.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
|
|
7
7
|
Project-URL: Documentation, https://elastalert2.readthedocs.io
|
|
8
8
|
Project-URL: Source Code, https://github.com/jertel/elastalert2
|
|
9
9
|
Project-URL: Discussion Forum, https://github.com/jertel/elastalert2/discussions
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
11
|
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
11
13
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
14
|
Classifier: Operating System :: OS Independent
|
|
13
15
|
Requires-Python: >=3.12
|
|
14
16
|
Description-Content-Type: text/markdown
|
|
15
17
|
License-File: LICENSE
|
|
16
|
-
Requires-Dist: apscheduler<4.0,>=3.
|
|
18
|
+
Requires-Dist: apscheduler<4.0,>=3.11.0
|
|
17
19
|
Requires-Dist: aws-requests-auth>=0.4.3
|
|
18
|
-
Requires-Dist: boto3>=1.
|
|
19
|
-
Requires-Dist: cffi>=
|
|
20
|
-
Requires-Dist: croniter>=
|
|
20
|
+
Requires-Dist: boto3>=1.40.59
|
|
21
|
+
Requires-Dist: cffi>=2.0.0
|
|
22
|
+
Requires-Dist: croniter>=6.0.0
|
|
21
23
|
Requires-Dist: elasticsearch==7.10.1
|
|
22
24
|
Requires-Dist: envparse>=0.2.0
|
|
23
25
|
Requires-Dist: exotel==0.1.5
|
|
24
|
-
Requires-Dist: Jinja2>=3.1.
|
|
25
|
-
Requires-Dist: jira>=3.
|
|
26
|
-
Requires-Dist: jsonpointer>=
|
|
27
|
-
Requires-Dist: jsonschema>=4.
|
|
28
|
-
Requires-Dist: prettytable>=3.
|
|
26
|
+
Requires-Dist: Jinja2>=3.1.6
|
|
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
|
|
29
31
|
Requires-Dist: prison>=0.2.1
|
|
30
|
-
Requires-Dist: prometheus_client>=0.
|
|
32
|
+
Requires-Dist: prometheus_client>=0.23.1
|
|
31
33
|
Requires-Dist: python-dateutil>=2.9.0.post0
|
|
32
|
-
Requires-Dist: PyYAML>=6.0.
|
|
33
|
-
Requires-Dist:
|
|
34
|
+
Requires-Dist: PyYAML>=6.0.3
|
|
35
|
+
Requires-Dist: zabbix-utils>=2.0.3
|
|
34
36
|
Requires-Dist: requests>=2.31.0
|
|
35
37
|
Requires-Dist: sortedcontainers>=2.4.0
|
|
36
38
|
Requires-Dist: statsd-tags==3.2.1.post1
|
|
37
|
-
Requires-Dist: stomp.py>=8.
|
|
38
|
-
Requires-Dist: tencentcloud-sdk-python>=3.0.
|
|
39
|
+
Requires-Dist: stomp.py>=8.2.0
|
|
40
|
+
Requires-Dist: tencentcloud-sdk-python>=3.0.1479
|
|
39
41
|
Requires-Dist: texttable>=1.7.0
|
|
40
|
-
Requires-Dist: twilio>=8.
|
|
42
|
+
Requires-Dist: twilio>=9.8.4
|
|
41
43
|
Dynamic: classifier
|
|
42
44
|
Dynamic: description
|
|
43
45
|
Dynamic: description-content-type
|
|
@@ -29,17 +29,24 @@ class ChatworkAlerter(Alerter):
|
|
|
29
29
|
body += '\n----------------------------------------\n'
|
|
30
30
|
if len(body) > 2047:
|
|
31
31
|
body = body[0:1950] + '\n *message was cropped according to chatwork embed description limits!*'
|
|
32
|
-
headers = {
|
|
32
|
+
headers = {
|
|
33
|
+
'accept': 'application/json',
|
|
34
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
35
|
+
'x-chatworktoken': self.chatwork_apikey
|
|
36
|
+
}
|
|
33
37
|
# set https proxy, if it was provided
|
|
34
38
|
proxies = {'https': self.chatwork_proxy} if self.chatwork_proxy else None
|
|
35
39
|
auth = HTTPProxyAuth(self.chatwork_proxy_login, self.chatwork_proxy_pass) if self.chatwork_proxy_login else None
|
|
36
|
-
|
|
40
|
+
payload = {
|
|
41
|
+
'self_unread': 0,
|
|
42
|
+
'body': body
|
|
43
|
+
}
|
|
37
44
|
|
|
38
45
|
try:
|
|
39
|
-
response = requests.post(self.url,
|
|
46
|
+
response = requests.post(self.url, data=payload, headers=headers, proxies=proxies, auth=auth)
|
|
40
47
|
response.raise_for_status()
|
|
41
48
|
except RequestException as e:
|
|
42
|
-
raise EAException("Error posting to
|
|
49
|
+
raise EAException("Error posting to Chatwork: %s. Details: %s" % (e, "" if e.response is None else e.response.text))
|
|
43
50
|
|
|
44
51
|
elastalert_logger.info(
|
|
45
52
|
"Alert sent to Chatwork room %s" % self.chatwork_room_id)
|
|
@@ -262,14 +262,26 @@ class JiraAlerter(Alerter):
|
|
|
262
262
|
if self.bump_not_in_statuses:
|
|
263
263
|
jql = '%s and status not in (%s)' % (jql, ','.join(["\"%s\"" % status if ' ' in status else status
|
|
264
264
|
for status in self.bump_not_in_statuses]))
|
|
265
|
+
|
|
266
|
+
# Force use of enhanced_search_issues (new API)
|
|
267
|
+
# This method should be available in updated jira library versions
|
|
268
|
+
if not hasattr(self.client, 'enhanced_search_issues'):
|
|
269
|
+
raise EAException("enhanced_search_issues method not available. Please update your jira library to a version that supports the new Jira API endpoints.")
|
|
270
|
+
|
|
265
271
|
try:
|
|
266
|
-
issues = self.client.
|
|
272
|
+
issues = self.client.enhanced_search_issues(jql)
|
|
273
|
+
|
|
274
|
+
# Ensure issues is a list-like object before checking length
|
|
275
|
+
if issues and len(issues):
|
|
276
|
+
return issues[0]
|
|
267
277
|
except JIRAError as e:
|
|
268
278
|
elastalert_logger.exception("Error while searching for Jira ticket using jql '%s': %s" % (jql, e))
|
|
269
279
|
return None
|
|
280
|
+
except Exception as e:
|
|
281
|
+
elastalert_logger.exception("Unexpected error while searching for Jira ticket: %s" % e)
|
|
282
|
+
return None
|
|
270
283
|
|
|
271
|
-
|
|
272
|
-
return issues[0]
|
|
284
|
+
return None
|
|
273
285
|
|
|
274
286
|
def comment_on_ticket(self, ticket, match):
|
|
275
287
|
text = str(JiraFormattedMatchString(self.rule, match))
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from requests import RequestException
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from elastalert.alerts import Alerter, BasicMatchString
|
|
6
|
+
from elastalert.util import EAException, elastalert_logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LineMessageAPIAlerter(Alerter):
|
|
10
|
+
"""Creates a LINE Messaging API for each alert"""
|
|
11
|
+
required_option = frozenset(['line_channel_access_token', 'line_to'])
|
|
12
|
+
|
|
13
|
+
def __init__(self, rule):
|
|
14
|
+
super(LineMessageAPIAlerter, self).__init__(rule)
|
|
15
|
+
|
|
16
|
+
self.line_channel_access_token = self.rule.get('line_channel_access_token')
|
|
17
|
+
self.line_to = self.rule.get('line_to')
|
|
18
|
+
|
|
19
|
+
def alert(self, matches):
|
|
20
|
+
body = ''
|
|
21
|
+
for match in matches:
|
|
22
|
+
body += str(BasicMatchString(self.rule, match))
|
|
23
|
+
if len(matches) > 1:
|
|
24
|
+
body += '\n----------------------------------------\n'
|
|
25
|
+
|
|
26
|
+
if len(body) > 4800:
|
|
27
|
+
body = body[:4700] + '\n\n(message cropped due to LINE limit)'
|
|
28
|
+
|
|
29
|
+
headers = {
|
|
30
|
+
'Content-Type': 'application/json',
|
|
31
|
+
'Authorization': 'Bearer {}'.format(self.line_channel_access_token)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
payload = {
|
|
35
|
+
'to': self.line_to,
|
|
36
|
+
'messages': [
|
|
37
|
+
{
|
|
38
|
+
'type': 'text',
|
|
39
|
+
'text': body
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
response = requests.post(
|
|
46
|
+
'https://api.line.me/v2/bot/message/push',
|
|
47
|
+
headers=headers,
|
|
48
|
+
data=json.dumps(payload)
|
|
49
|
+
)
|
|
50
|
+
response.raise_for_status()
|
|
51
|
+
except RequestException as e:
|
|
52
|
+
raise EAException('Error posting to LINE Messaging API: %s' % e)
|
|
53
|
+
|
|
54
|
+
elastalert_logger.info('Alert sent to LINE Messaging API')
|
|
55
|
+
|
|
56
|
+
def get_info(self):
|
|
57
|
+
return {
|
|
58
|
+
'type': 'line',
|
|
59
|
+
'line_to': self.line_to,
|
|
60
|
+
'line_channel_access_token': self.line_channel_access_token
|
|
61
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from zabbix_utils import Sender, ItemValue, ZabbixAPI
|
|
4
4
|
|
|
5
5
|
from elastalert.alerts import Alerter
|
|
6
6
|
from elastalert.util import elastalert_logger, lookup_es_key, EAException
|
|
@@ -23,20 +23,20 @@ class ZabbixClient(ZabbixAPI):
|
|
|
23
23
|
password=password)
|
|
24
24
|
|
|
25
25
|
def send_metric(self, hostname, key, data):
|
|
26
|
-
zm =
|
|
26
|
+
zm = ItemValue(hostname, key, data)
|
|
27
27
|
if self.send_aggregated_metrics:
|
|
28
28
|
self.aggregated_metrics.append(zm)
|
|
29
29
|
if len(self.aggregated_metrics) > self.metrics_chunk_size:
|
|
30
30
|
elastalert_logger.info("Sending: %s metrics" % (len(self.aggregated_metrics)))
|
|
31
31
|
try:
|
|
32
|
-
|
|
32
|
+
Sender(server=self.sender_host, port=self.sender_port) \
|
|
33
33
|
.send(self.aggregated_metrics)
|
|
34
34
|
self.aggregated_metrics = []
|
|
35
35
|
except Exception as e:
|
|
36
36
|
elastalert_logger.exception(e)
|
|
37
37
|
else:
|
|
38
38
|
try:
|
|
39
|
-
|
|
39
|
+
Sender(server=self.sender_host, port=self.sender_port).send([zm])
|
|
40
40
|
except Exception as e:
|
|
41
41
|
elastalert_logger.exception(e)
|
|
42
42
|
|
|
@@ -81,10 +81,10 @@ class ZabbixAlerter(Alerter):
|
|
|
81
81
|
zbx_host = lookup_es_key(match, self.rule["zbx_host"])
|
|
82
82
|
else:
|
|
83
83
|
zbx_host = self.zbx_host
|
|
84
|
-
zm.append(
|
|
84
|
+
zm.append(ItemValue(host=zbx_host, key=self.zbx_key, value='1', clock=ts_epoch))
|
|
85
85
|
|
|
86
86
|
try:
|
|
87
|
-
response =
|
|
87
|
+
response = Sender(server=self.zbx_sender_host, port=self.zbx_sender_port).send(zm)
|
|
88
88
|
if response.failed:
|
|
89
89
|
if self.zbx_host_from_field and not zbx_host:
|
|
90
90
|
elastalert_logger.warning("Missing term '%s' or host's item '%s', alert will be discarded"
|
|
@@ -926,7 +926,14 @@ class ElastAlerter(object):
|
|
|
926
926
|
if query_key_value is not None:
|
|
927
927
|
silence_cache_key += '.' + query_key_value
|
|
928
928
|
|
|
929
|
-
|
|
929
|
+
# Check cache first to reduce unnecessary ES queries
|
|
930
|
+
# Effectively, this checks query key cache first. Then checks rule cache. Then checks the rule via ES.
|
|
931
|
+
# Followed by a second (duplicated) query key cache check, and then finally a query key check via ES.
|
|
932
|
+
# It could be further improved to eliminate the second query key cache check but this implementation
|
|
933
|
+
# favors both cache checks up front, before any ES hit, which is an overall performance improvement
|
|
934
|
+
# compared to the original logic, which required a rule check via ES before checking the query key cache.
|
|
935
|
+
if self.is_silenced(silence_cache_key, check_cache_only=True) or \
|
|
936
|
+
self.is_silenced(rule['name'] + "._silence") or self.is_silenced(silence_cache_key):
|
|
930
937
|
elastalert_logger.info('Ignoring match for silenced rule %s' % (silence_cache_key,))
|
|
931
938
|
continue
|
|
932
939
|
|
|
@@ -1785,13 +1792,13 @@ class ElastAlerter(object):
|
|
|
1785
1792
|
self.silence_cache[silence_cache_key] = (timestamp, exponent)
|
|
1786
1793
|
return self.writeback('silence', body)
|
|
1787
1794
|
|
|
1788
|
-
def is_silenced(self, rule_name):
|
|
1795
|
+
def is_silenced(self, rule_name, check_cache_only=False):
|
|
1789
1796
|
""" Checks if rule_name is currently silenced. Returns false on exception. """
|
|
1790
1797
|
if rule_name in self.silence_cache:
|
|
1791
1798
|
if ts_now() < self.silence_cache[rule_name][0]:
|
|
1792
1799
|
return True
|
|
1793
1800
|
|
|
1794
|
-
if self.debug:
|
|
1801
|
+
if self.debug or check_cache_only:
|
|
1795
1802
|
return False
|
|
1796
1803
|
query = {'term': {'rule_name': rule_name}}
|
|
1797
1804
|
sort = {'sort': {'until': {'order': 'desc'}}}
|
|
@@ -11,7 +11,6 @@ import yaml.scanner
|
|
|
11
11
|
from elastalert.alerters.flashduty import FlashdutyAlerter
|
|
12
12
|
from jinja2 import Environment
|
|
13
13
|
from jinja2 import FileSystemLoader
|
|
14
|
-
from jinja2 import Template
|
|
15
14
|
|
|
16
15
|
import elastalert.alerters.alerta
|
|
17
16
|
import elastalert.alerters.chatwork
|
|
@@ -28,6 +27,7 @@ import elastalert.alerters.httppost
|
|
|
28
27
|
import elastalert.alerters.httppost2
|
|
29
28
|
import elastalert.alerters.iris
|
|
30
29
|
import elastalert.alerters.lark
|
|
30
|
+
import elastalert.alerters.line
|
|
31
31
|
import elastalert.alerters.pagertree
|
|
32
32
|
import elastalert.alerters.rocketchat
|
|
33
33
|
import elastalert.alerters.servicenow
|
|
@@ -136,6 +136,7 @@ class RulesLoader(object):
|
|
|
136
136
|
'discord': elastalert.alerters.discord.DiscordAlerter,
|
|
137
137
|
'dingtalk': elastalert.alerters.dingtalk.DingTalkAlerter,
|
|
138
138
|
'lark': elastalert.alerters.lark.LarkAlerter,
|
|
139
|
+
'line': elastalert.alerters.line.LineMessageAPIAlerter,
|
|
139
140
|
'webex_webhook': elastalert.alerters.webex_webhook.WebexWebhookAlerter,
|
|
140
141
|
'workwechat': elastalert.alerters.workwechat.WorkWechatAlerter,
|
|
141
142
|
'chatwork': elastalert.alerters.chatwork.ChatworkAlerter,
|
|
@@ -161,6 +162,7 @@ class RulesLoader(object):
|
|
|
161
162
|
base_config = {}
|
|
162
163
|
|
|
163
164
|
jinja_environment = Environment(loader=FileSystemLoader(""))
|
|
165
|
+
jinja_filters = []
|
|
164
166
|
|
|
165
167
|
def __init__(self, conf):
|
|
166
168
|
self.rule_schema = load_rule_schema()
|
|
@@ -479,13 +481,38 @@ class RulesLoader(object):
|
|
|
479
481
|
|
|
480
482
|
self.load_jinja_template(rule)
|
|
481
483
|
|
|
484
|
+
def load_jinja_filters(self, filters = []):
|
|
485
|
+
""" Load custom Jinja2 filters into the Environment
|
|
486
|
+
:param list filters: List of filter paths to load, format should be path to a module, ie 'jinja2.filters.MyFilterClass'
|
|
487
|
+
"""
|
|
488
|
+
for module_path in filters:
|
|
489
|
+
|
|
490
|
+
filter_class = get_module(module_path)
|
|
491
|
+
for method in dir(filter_class):
|
|
492
|
+
if not method.startswith('filter_'):
|
|
493
|
+
continue
|
|
494
|
+
filter_func = getattr(filter_class, method)
|
|
495
|
+
filter_name = method.removeprefix('filter_')
|
|
496
|
+
|
|
497
|
+
# Skip if filter already loaded
|
|
498
|
+
if filter_name in self.jinja_filters:
|
|
499
|
+
continue
|
|
500
|
+
|
|
501
|
+
self.jinja_environment.filters[filter_name] = filter_func
|
|
502
|
+
self.jinja_filters.append(filter_name)
|
|
503
|
+
elastalert_logger.debug('Loaded custom Jinja2 filter: %s.%s', module_path, filter_name)
|
|
504
|
+
|
|
482
505
|
def load_jinja_template(self, rule):
|
|
483
506
|
if rule.get('alert_text_type') == 'alert_text_jinja':
|
|
484
507
|
jinja_template_path = rule.get('jinja_template_path')
|
|
508
|
+
|
|
509
|
+
if rule.get('jinja_filters'):
|
|
510
|
+
self.load_jinja_filters(rule.get('jinja_filters'))
|
|
511
|
+
|
|
485
512
|
if jinja_template_path:
|
|
486
513
|
rule["jinja_template"] = self.jinja_environment.get_or_select_template(jinja_template_path)
|
|
487
514
|
else:
|
|
488
|
-
rule["jinja_template"] =
|
|
515
|
+
rule["jinja_template"] = self.jinja_environment.from_string(str(rule.get('alert_text', '')))
|
|
489
516
|
|
|
490
517
|
def load_modules(self, rule, args=None):
|
|
491
518
|
""" Loads things that could be modules. Enhancements, alerts and rule type. """
|
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import os
|
|
3
|
-
from urllib.parse import parse_qsl, urlencode, urljoin, urlparse, urlsplit, urlunsplit
|
|
4
|
-
|
|
5
|
-
import requests
|
|
6
|
-
from requests import RequestException
|
|
7
|
-
from requests.auth import AuthBase, HTTPBasicAuth
|
|
8
|
-
|
|
9
|
-
from elastalert.auth import RefeshableAWSRequestsAuth
|
|
10
|
-
from elastalert.util import EAException
|
|
1
|
+
from urllib.parse import urljoin
|
|
11
2
|
|
|
12
3
|
class OpensearchExternalUrlFormatter:
|
|
13
4
|
'''Interface for formatting external Opensearch urls'''
|
|
@@ -29,10 +29,12 @@ class PrometheusWrapper:
|
|
|
29
29
|
|
|
30
30
|
def metrics_run_rule(self, rule, endtime, starttime=None):
|
|
31
31
|
""" Increment counter every time rule is run """
|
|
32
|
+
result = None
|
|
32
33
|
try:
|
|
33
34
|
self.prom_scrapes.labels(rule['name']).inc()
|
|
34
35
|
finally:
|
|
35
|
-
|
|
36
|
+
result = self.run_rule(rule, endtime, starttime)
|
|
37
|
+
return result
|
|
36
38
|
|
|
37
39
|
def metrics_writeback(self, doc_type, body, rule=None, match_body=None):
|
|
38
40
|
""" Update various prometheus metrics accoording to the doc_type """
|
|
@@ -53,4 +55,5 @@ class PrometheusWrapper:
|
|
|
53
55
|
elif doc_type == 'silence':
|
|
54
56
|
self.prom_alerts_silenced.labels(body['rule_name']).inc()
|
|
55
57
|
finally:
|
|
56
|
-
|
|
58
|
+
pass
|
|
59
|
+
return res
|
|
@@ -1030,8 +1030,22 @@ class BaseAggregationRule(RuleType):
|
|
|
1030
1030
|
if total_seconds(self.rules['buffer_time']) % total_seconds(self.rules['bucket_interval_timedelta']) != 0:
|
|
1031
1031
|
raise EAException("Buffer_time must be evenly divisible by bucket_interval if specified")
|
|
1032
1032
|
|
|
1033
|
+
allowed_percent_aggregations = frozenset(['percentiles'])
|
|
1034
|
+
|
|
1033
1035
|
def generate_aggregation_query(self):
|
|
1034
|
-
|
|
1036
|
+
if self.rules.get('metric_agg_script'):
|
|
1037
|
+
agg_data = self.rules['metric_agg_script']
|
|
1038
|
+
if isinstance(agg_data, dict):
|
|
1039
|
+
agg_data = agg_data.copy()
|
|
1040
|
+
else:
|
|
1041
|
+
agg_data = {'script': agg_data}
|
|
1042
|
+
query = {self.metric_key: {self.rules['metric_agg_type']: agg_data}}
|
|
1043
|
+
else:
|
|
1044
|
+
query = {self.metric_key: {self.rules['metric_agg_type']: {'field': self.rules['metric_agg_key']}}}
|
|
1045
|
+
if self.rules['metric_agg_type'] in self.allowed_percent_aggregations:
|
|
1046
|
+
if 'percents' not in query[self.metric_key][self.rules['metric_agg_type']]:
|
|
1047
|
+
query[self.metric_key][self.rules['metric_agg_type']]['percents'] = [self.rules['percentile_range']]
|
|
1048
|
+
return query
|
|
1035
1049
|
|
|
1036
1050
|
def add_aggregation_data(self, payload):
|
|
1037
1051
|
for timestamp, payload_data in payload.items():
|
|
@@ -1072,8 +1086,9 @@ class MetricAggregationRule(BaseAggregationRule):
|
|
|
1072
1086
|
|
|
1073
1087
|
self.metric_key = 'metric_' + self.rules['metric_agg_key'] + '_' + self.rules['metric_agg_type']
|
|
1074
1088
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1089
|
+
all_allowed_aggregations = self.allowed_aggregations.union(self.allowed_percent_aggregations)
|
|
1090
|
+
if not self.rules['metric_agg_type'] in all_allowed_aggregations:
|
|
1091
|
+
raise EAException("metric_agg_type must be one of %s" % (str(all_allowed_aggregations)))
|
|
1077
1092
|
if self.rules['metric_agg_type'] in self.allowed_percent_aggregations and self.rules['percentile_range'] is None:
|
|
1078
1093
|
raise EAException("percentile_range must be specified for percentiles aggregation")
|
|
1079
1094
|
|
|
@@ -1090,13 +1105,7 @@ class MetricAggregationRule(BaseAggregationRule):
|
|
|
1090
1105
|
)
|
|
1091
1106
|
return message
|
|
1092
1107
|
|
|
1093
|
-
|
|
1094
|
-
if self.rules.get('metric_agg_script'):
|
|
1095
|
-
return {self.metric_key: {self.rules['metric_agg_type']: self.rules['metric_agg_script']}}
|
|
1096
|
-
query = {self.metric_key: {self.rules['metric_agg_type']: {'field': self.rules['metric_agg_key']}}}
|
|
1097
|
-
if self.rules['metric_agg_type'] in self.allowed_percent_aggregations:
|
|
1098
|
-
query[self.metric_key][self.rules['metric_agg_type']]['percents'] = [self.rules['percentile_range']]
|
|
1099
|
-
return query
|
|
1108
|
+
|
|
1100
1109
|
|
|
1101
1110
|
def check_matches(self, timestamp, query_key, aggregation_data):
|
|
1102
1111
|
if "compound_query_key" in self.rules:
|
|
@@ -1137,7 +1146,10 @@ class MetricAggregationRule(BaseAggregationRule):
|
|
|
1137
1146
|
if 'interval_aggs' in aggregation_data:
|
|
1138
1147
|
metric_val_arr = [term[self.metric_key]['value'] for term in aggregation_data['interval_aggs']['buckets']]
|
|
1139
1148
|
else:
|
|
1140
|
-
|
|
1149
|
+
if self.rules['metric_agg_type'] in self.allowed_percent_aggregations:
|
|
1150
|
+
metric_val_arr = list(aggregation_data[self.metric_key]['values'].values())
|
|
1151
|
+
else:
|
|
1152
|
+
metric_val_arr = [aggregation_data[self.metric_key]['value']]
|
|
1141
1153
|
for metric_val in metric_val_arr:
|
|
1142
1154
|
if self.crossed_thresholds(metric_val):
|
|
1143
1155
|
match_data[self.rules['timestamp_field']] = timestamp
|
|
@@ -1186,14 +1198,7 @@ class SpikeMetricAggregationRule(BaseAggregationRule, SpikeRule):
|
|
|
1186
1198
|
|
|
1187
1199
|
self.rules['aggregation_query_element'] = self.generate_aggregation_query()
|
|
1188
1200
|
|
|
1189
|
-
|
|
1190
|
-
"""Lifted from MetricAggregationRule"""
|
|
1191
|
-
if self.rules.get('metric_agg_script'):
|
|
1192
|
-
return {self.metric_key: {self.rules['metric_agg_type']: self.rules['metric_agg_script']}}
|
|
1193
|
-
query = {self.metric_key: {self.rules['metric_agg_type']: {'field': self.rules['metric_agg_key']}}}
|
|
1194
|
-
if self.rules['metric_agg_type'] in self.allowed_percent_aggregations:
|
|
1195
|
-
query[self.metric_key][self.rules['metric_agg_type']]['percents'] = [self.rules['percentile_range']]
|
|
1196
|
-
return query
|
|
1201
|
+
|
|
1197
1202
|
|
|
1198
1203
|
def add_aggregation_data(self, payload):
|
|
1199
1204
|
"""
|
|
@@ -184,6 +184,7 @@ oneOf:
|
|
|
184
184
|
threshold_cur: {type: number}
|
|
185
185
|
min_doc_count: {type: integer}
|
|
186
186
|
percentile_range: {type: integer}
|
|
187
|
+
metric_agg_script: {type: [string, object]}
|
|
187
188
|
|
|
188
189
|
- title: Flatline
|
|
189
190
|
required: [threshold, timeframe]
|
|
@@ -231,6 +232,7 @@ oneOf:
|
|
|
231
232
|
bucket_interval: *timeframe
|
|
232
233
|
sync_bucket_interval: {type: boolean}
|
|
233
234
|
metric_format_string: {type: string}
|
|
235
|
+
metric_agg_script: {type: [string, object]}
|
|
234
236
|
|
|
235
237
|
- title: Percentage Match
|
|
236
238
|
required: [match_bucket_filter]
|
|
@@ -307,6 +309,7 @@ properties:
|
|
|
307
309
|
use_local_time: {type: boolean}
|
|
308
310
|
custom_pretty_ts_format: {type: string}
|
|
309
311
|
match_enhancements: {type: array, items: {type: string}}
|
|
312
|
+
jinja_filters: {type: array, items: {type: string}}
|
|
310
313
|
query_key: *arrayOfString
|
|
311
314
|
replace_dots_in_field_names: {type: boolean}
|
|
312
315
|
scan_entire_timeframe: {type: boolean}
|
|
@@ -659,6 +662,10 @@ properties:
|
|
|
659
662
|
lark_bot_id: { type: string }
|
|
660
663
|
lark_msgtype: { type: string, enum: [ 'text' ] }
|
|
661
664
|
|
|
665
|
+
### LINE Messaging API
|
|
666
|
+
line_channel_access_token: {type: string}
|
|
667
|
+
line_to: {type: string}
|
|
668
|
+
|
|
662
669
|
### Matrix Hookshot
|
|
663
670
|
matrixhookshot_ca_certs: {type: [boolean, string]}
|
|
664
671
|
matrixhookshot_webhook_url: *arrayOfString
|
|
@@ -1,43 +1,45 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: elastalert2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.28.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
|
|
7
7
|
Project-URL: Documentation, https://elastalert2.readthedocs.io
|
|
8
8
|
Project-URL: Source Code, https://github.com/jertel/elastalert2
|
|
9
9
|
Project-URL: Discussion Forum, https://github.com/jertel/elastalert2/discussions
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
11
|
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
11
13
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
14
|
Classifier: Operating System :: OS Independent
|
|
13
15
|
Requires-Python: >=3.12
|
|
14
16
|
Description-Content-Type: text/markdown
|
|
15
17
|
License-File: LICENSE
|
|
16
|
-
Requires-Dist: apscheduler<4.0,>=3.
|
|
18
|
+
Requires-Dist: apscheduler<4.0,>=3.11.0
|
|
17
19
|
Requires-Dist: aws-requests-auth>=0.4.3
|
|
18
|
-
Requires-Dist: boto3>=1.
|
|
19
|
-
Requires-Dist: cffi>=
|
|
20
|
-
Requires-Dist: croniter>=
|
|
20
|
+
Requires-Dist: boto3>=1.40.59
|
|
21
|
+
Requires-Dist: cffi>=2.0.0
|
|
22
|
+
Requires-Dist: croniter>=6.0.0
|
|
21
23
|
Requires-Dist: elasticsearch==7.10.1
|
|
22
24
|
Requires-Dist: envparse>=0.2.0
|
|
23
25
|
Requires-Dist: exotel==0.1.5
|
|
24
|
-
Requires-Dist: Jinja2>=3.1.
|
|
25
|
-
Requires-Dist: jira>=3.
|
|
26
|
-
Requires-Dist: jsonpointer>=
|
|
27
|
-
Requires-Dist: jsonschema>=4.
|
|
28
|
-
Requires-Dist: prettytable>=3.
|
|
26
|
+
Requires-Dist: Jinja2>=3.1.6
|
|
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
|
|
29
31
|
Requires-Dist: prison>=0.2.1
|
|
30
|
-
Requires-Dist: prometheus_client>=0.
|
|
32
|
+
Requires-Dist: prometheus_client>=0.23.1
|
|
31
33
|
Requires-Dist: python-dateutil>=2.9.0.post0
|
|
32
|
-
Requires-Dist: PyYAML>=6.0.
|
|
33
|
-
Requires-Dist:
|
|
34
|
+
Requires-Dist: PyYAML>=6.0.3
|
|
35
|
+
Requires-Dist: zabbix-utils>=2.0.3
|
|
34
36
|
Requires-Dist: requests>=2.31.0
|
|
35
37
|
Requires-Dist: sortedcontainers>=2.4.0
|
|
36
38
|
Requires-Dist: statsd-tags==3.2.1.post1
|
|
37
|
-
Requires-Dist: stomp.py>=8.
|
|
38
|
-
Requires-Dist: tencentcloud-sdk-python>=3.0.
|
|
39
|
+
Requires-Dist: stomp.py>=8.2.0
|
|
40
|
+
Requires-Dist: tencentcloud-sdk-python>=3.0.1479
|
|
39
41
|
Requires-Dist: texttable>=1.7.0
|
|
40
|
-
Requires-Dist: twilio>=8.
|
|
42
|
+
Requires-Dist: twilio>=9.8.4
|
|
41
43
|
Dynamic: classifier
|
|
42
44
|
Dynamic: description
|
|
43
45
|
Dynamic: description-content-type
|
|
@@ -42,6 +42,7 @@ elastalert/alerters/indexer.py
|
|
|
42
42
|
elastalert/alerters/iris.py
|
|
43
43
|
elastalert/alerters/jira.py
|
|
44
44
|
elastalert/alerters/lark.py
|
|
45
|
+
elastalert/alerters/line.py
|
|
45
46
|
elastalert/alerters/matrixhookshot.py
|
|
46
47
|
elastalert/alerters/mattermost.py
|
|
47
48
|
elastalert/alerters/opsgenie.py
|
|
@@ -102,6 +103,7 @@ tests/alerters/indexer_test.py
|
|
|
102
103
|
tests/alerters/iris_test.py
|
|
103
104
|
tests/alerters/jira_test.py
|
|
104
105
|
tests/alerters/lark_test.py
|
|
106
|
+
tests/alerters/line_test.py
|
|
105
107
|
tests/alerters/matrixhookshot_test.py
|
|
106
108
|
tests/alerters/mattermost_test.py
|
|
107
109
|
tests/alerters/opsgenie_test.py
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
apscheduler<4.0,>=3.11.0
|
|
2
|
+
aws-requests-auth>=0.4.3
|
|
3
|
+
boto3>=1.40.59
|
|
4
|
+
cffi>=2.0.0
|
|
5
|
+
croniter>=6.0.0
|
|
6
|
+
elasticsearch==7.10.1
|
|
7
|
+
envparse>=0.2.0
|
|
8
|
+
exotel==0.1.5
|
|
9
|
+
Jinja2>=3.1.6
|
|
10
|
+
jira>=3.10.5
|
|
11
|
+
jsonpointer>=3.0.0
|
|
12
|
+
jsonschema>=4.25.1
|
|
13
|
+
prettytable>=3.16.0
|
|
14
|
+
prison>=0.2.1
|
|
15
|
+
prometheus_client>=0.23.1
|
|
16
|
+
python-dateutil>=2.9.0.post0
|
|
17
|
+
PyYAML>=6.0.3
|
|
18
|
+
zabbix-utils>=2.0.3
|
|
19
|
+
requests>=2.31.0
|
|
20
|
+
sortedcontainers>=2.4.0
|
|
21
|
+
statsd-tags==3.2.1.post1
|
|
22
|
+
stomp.py>=8.2.0
|
|
23
|
+
tencentcloud-sdk-python>=3.0.1479
|
|
24
|
+
texttable>=1.7.0
|
|
25
|
+
twilio>=9.8.4
|
|
@@ -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.28.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",
|
|
@@ -21,7 +21,9 @@ setup(
|
|
|
21
21
|
"Discussion Forum": "https://github.com/jertel/elastalert2/discussions",
|
|
22
22
|
},
|
|
23
23
|
classifiers=[
|
|
24
|
+
'Programming Language :: Python :: 3.12',
|
|
24
25
|
'Programming Language :: Python :: 3.13',
|
|
26
|
+
'Programming Language :: Python :: 3.14',
|
|
25
27
|
'License :: OSI Approved :: Apache Software License',
|
|
26
28
|
'Operating System :: OS Independent',
|
|
27
29
|
],
|
|
@@ -33,30 +35,30 @@ setup(
|
|
|
33
35
|
package_data={'elastalert': ['schema.yaml', 'es_mappings/**/*.json']},
|
|
34
36
|
python_requires='>=3.12',
|
|
35
37
|
install_requires=[
|
|
36
|
-
'apscheduler>=3.
|
|
38
|
+
'apscheduler>=3.11.0,<4.0',
|
|
37
39
|
'aws-requests-auth>=0.4.3',
|
|
38
|
-
'boto3>=1.
|
|
39
|
-
'cffi>=
|
|
40
|
-
'croniter>=
|
|
40
|
+
'boto3>=1.40.59',
|
|
41
|
+
'cffi>=2.0.0',
|
|
42
|
+
'croniter>=6.0.0',
|
|
41
43
|
'elasticsearch==7.10.1',
|
|
42
44
|
'envparse>=0.2.0',
|
|
43
45
|
'exotel==0.1.5',
|
|
44
|
-
'Jinja2>=3.1.
|
|
45
|
-
'jira>=3.
|
|
46
|
-
'jsonpointer>=
|
|
47
|
-
'jsonschema>=4.
|
|
48
|
-
'prettytable>=3.
|
|
46
|
+
'Jinja2>=3.1.6',
|
|
47
|
+
'jira>=3.10.5',
|
|
48
|
+
'jsonpointer>=3.0.0',
|
|
49
|
+
'jsonschema>=4.25.1',
|
|
50
|
+
'prettytable>=3.16.0',
|
|
49
51
|
'prison>=0.2.1',
|
|
50
|
-
'prometheus_client>=0.
|
|
52
|
+
'prometheus_client>=0.23.1',
|
|
51
53
|
'python-dateutil>=2.9.0.post0',
|
|
52
|
-
'PyYAML>=6.0.
|
|
53
|
-
'
|
|
54
|
+
'PyYAML>=6.0.3',
|
|
55
|
+
'zabbix-utils>=2.0.3',
|
|
54
56
|
'requests>=2.31.0',
|
|
55
57
|
'sortedcontainers>=2.4.0',
|
|
56
58
|
'statsd-tags==3.2.1.post1',
|
|
57
|
-
'stomp.py>=8.
|
|
58
|
-
'tencentcloud-sdk-python>=3.0.
|
|
59
|
+
'stomp.py>=8.2.0',
|
|
60
|
+
'tencentcloud-sdk-python>=3.0.1479',
|
|
59
61
|
'texttable>=1.7.0',
|
|
60
|
-
'twilio>=8.
|
|
62
|
+
'twilio>=9.8.4',
|
|
61
63
|
]
|
|
62
64
|
)
|