Fix multiple linting errors
This commit is contained in:
parent
faff3787c5
commit
2a6632b78f
127 changed files with 1692 additions and 1371 deletions
.isort.cfg.pre-commit-config.yamlAUTHORSREADME.md
integrations
consul
fail2ban
mailer
opsgenie
opsweekly
pinger
snmptrap
sqs
supervisor
syslog
urlmon
plugins
alertops
amqp
cachet
debug
dingtalk
enhance
forward
geoip
goalert
influxdb
jira
logstash
matrix
mattermost
msteams
normalise
op5
opsgenie
pagerduty
prometheus
pubsub
pushover
rocketchat
slack
sns
syslog
telegram
timeout
2
.isort.cfg
Normal file
2
.isort.cfg
Normal file
|
@ -0,0 +1,2 @@
|
|||
[settings]
|
||||
known_third_party = Queue,alerta,alerta_azuremonitor,alerta_msteamswebhook,alerta_sentry,alerta_slack,alertaclient,boto,cachetclient,consul,dateutil,dingtalkchatbot,flask,google,influxdb,jinja2,kombu,mailer,matterhook,mock,op5,pymsteams,pytest,pyzabbix,requests,settings,setuptools,telepot,twilio,yaml
|
46
.pre-commit-config.yaml
Normal file
46
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,46 @@
|
|||
repos:
|
||||
- repo: https://github.com/pre-commit/mirrors-autopep8
|
||||
rev: v1.5.1
|
||||
hooks:
|
||||
- id: autopep8
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks.git
|
||||
rev: v2.5.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-ast
|
||||
- id: check-byte-order-marker
|
||||
- id: check-case-conflict
|
||||
- id: check-docstring-first
|
||||
- id: check-json
|
||||
- id: check-merge-conflict
|
||||
- id: check-yaml
|
||||
- id: debug-statements
|
||||
- id: double-quote-string-fixer
|
||||
- id: end-of-file-fixer
|
||||
- id: fix-encoding-pragma
|
||||
args: ['--remove']
|
||||
- id: pretty-format-json
|
||||
args: ['--autofix']
|
||||
- id: name-tests-test
|
||||
args: ['--django']
|
||||
exclude: ^tests/helpers/
|
||||
- id: requirements-txt-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: 3.9.2
|
||||
hooks:
|
||||
- id: flake8
|
||||
args: ['--config=setup.cfg', '--ignore=E501']
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v1.27.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: ['--py3-plus']
|
||||
- repo: https://github.com/asottile/seed-isort-config
|
||||
rev: v1.9.4
|
||||
hooks:
|
||||
- id: seed-isort-config
|
||||
- repo: https://github.com/pre-commit/mirrors-isort
|
||||
rev: v4.3.21
|
||||
hooks:
|
||||
- id: isort
|
1
AUTHORS
1
AUTHORS
|
@ -63,4 +63,3 @@ Yoshiharu Mori <y-mori@sraoss.co.jp>
|
|||
Карамышев Степан <karamyshev@corp.sputnik.ru>
|
||||
|
||||
$ git log --format='%aN <%aE>' | sort -f | uniq
|
||||
|
||||
|
|
|
@ -96,4 +96,3 @@ License
|
|||
-------
|
||||
|
||||
Copyright (c) 2014-2020 Nick Satterly and [AUTHORS](AUTHORS). Available under the MIT License.
|
||||
|
||||
|
|
|
@ -2,112 +2,117 @@
|
|||
|
||||
import json
|
||||
import os
|
||||
|
||||
import consul
|
||||
import sys
|
||||
import time
|
||||
|
||||
import consul
|
||||
from alertaclient.api import Client
|
||||
|
||||
CONSUL_HOST = os.environ.get('CONSUL_HOST', '127.0.0.1')
|
||||
CONSUL_PORT = int(os.environ.get('CONSUL_PORT', 8500))
|
||||
|
||||
client = consul.Consul(host=CONSUL_HOST, port=CONSUL_PORT, token=None, scheme='http', consistency='default', dc=None, verify=True)
|
||||
client = consul.Consul(host=CONSUL_HOST, port=CONSUL_PORT, token=None,
|
||||
scheme='http', consistency='default', dc=None, verify=True)
|
||||
|
||||
j = json.load(sys.stdin)
|
||||
print("Request:")
|
||||
print('Request:')
|
||||
print(j)
|
||||
|
||||
try:
|
||||
url = client.kv.get('alerta/apiurl')[1]['Value']
|
||||
except Exception:
|
||||
print("No URL defined, exiting")
|
||||
print('No URL defined, exiting')
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
key = client.kv.get('alerta/apikey')[1]['Value']
|
||||
except Exception:
|
||||
print("No key defined, exiting")
|
||||
print('No key defined, exiting')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
try:
|
||||
max_retries = int(client.kv.get('alerta/max_retries')[1]['Value'])
|
||||
except TypeError:
|
||||
print("No value defined, using default")
|
||||
print('No value defined, using default')
|
||||
max_retries = 3
|
||||
|
||||
try:
|
||||
sleep = int(client.kv.get('alerta/sleep')[1]['Value'])
|
||||
except TypeError:
|
||||
print("No value defined, using default")
|
||||
print('No value defined, using default')
|
||||
sleep = 2
|
||||
|
||||
try:
|
||||
timeout = int(client.kv.get('alerta/timeout')[1]['Value'])
|
||||
except TypeError:
|
||||
print("No value defined, using default")
|
||||
print('No value defined, using default')
|
||||
timeout = 900
|
||||
|
||||
try:
|
||||
origin = client.kv.get('alerta/origin')[1]['Value']
|
||||
except TypeError:
|
||||
print("No value defined, using default")
|
||||
origin = "consul"
|
||||
print('No value defined, using default')
|
||||
origin = 'consul'
|
||||
|
||||
try:
|
||||
alerttype = client.kv.get('alerta/alerttype')[1]['Value']
|
||||
except TypeError:
|
||||
print("No value defined, using default")
|
||||
alerttype = "ConsulAlert"
|
||||
print('No value defined, using default')
|
||||
alerttype = 'ConsulAlert'
|
||||
|
||||
|
||||
api = Client(endpoint=url, key=key)
|
||||
|
||||
SEVERITY_MAP = {
|
||||
'critical': 'critical',
|
||||
'warning': 'warning',
|
||||
'passing': 'ok',
|
||||
'critical': 'critical',
|
||||
'warning': 'warning',
|
||||
'passing': 'ok',
|
||||
}
|
||||
|
||||
def createalert( data ):
|
||||
|
||||
def createalert(data):
|
||||
try:
|
||||
environment = client.kv.get('alerta/env/{0}'.format(data['Node']))[1]['Value']
|
||||
environment = client.kv.get(
|
||||
'alerta/env/{}'.format(data['Node']))[1]['Value']
|
||||
except Exception:
|
||||
try:
|
||||
environment = client.kv.get('alerta/defaultenv')[1]['Value']
|
||||
except Exception:
|
||||
environment = "Production"
|
||||
environment = 'Production'
|
||||
|
||||
for _ in range(max_retries):
|
||||
try:
|
||||
print("Response:")
|
||||
print('Response:')
|
||||
response = api.send_alert(
|
||||
resource=data['Node'],
|
||||
event=data['CheckId'],
|
||||
value=data['Status'],
|
||||
correlate=SEVERITY_MAP.keys(),
|
||||
environment=environment,
|
||||
service=[data['CheckId']],
|
||||
severity=SEVERITY_MAP[data['Status']],
|
||||
text=data['Output'],
|
||||
timeout=timeout,
|
||||
origin=origin,
|
||||
type=alerttype
|
||||
resource=data['Node'],
|
||||
event=data['CheckId'],
|
||||
value=data['Status'],
|
||||
correlate=SEVERITY_MAP.keys(),
|
||||
environment=environment,
|
||||
service=[data['CheckId']],
|
||||
severity=SEVERITY_MAP[data['Status']],
|
||||
text=data['Output'],
|
||||
timeout=timeout,
|
||||
origin=origin,
|
||||
type=alerttype
|
||||
)
|
||||
print(response)
|
||||
except Exception as e:
|
||||
print("HTTP Error: {}".format(e))
|
||||
print('HTTP Error: {}'.format(e))
|
||||
time.sleep(sleep)
|
||||
continue
|
||||
else:
|
||||
break
|
||||
else:
|
||||
print("api is down")
|
||||
print('api is down')
|
||||
|
||||
|
||||
def main():
|
||||
for item in enumerate(j):
|
||||
i=item[0]
|
||||
i = item[0]
|
||||
createalert(j[i])
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from alertaclient.api import Client
|
||||
import consul
|
||||
import time
|
||||
|
||||
client = consul.Consul(host='127.0.0.1', port=8500, token=None, scheme='http', consistency='default', dc=None, verify=True)
|
||||
import consul
|
||||
from alertaclient.api import Client
|
||||
|
||||
client = consul.Consul(host='127.0.0.1', port=8500, token=None,
|
||||
scheme='http', consistency='default', dc=None, verify=True)
|
||||
|
||||
url = client.kv.get('alerta/apiurl')[1]['Value']
|
||||
key = client.kv.get('alerta/apikey')[1]['Value']
|
||||
|
@ -16,21 +18,24 @@ timeout = int(client.kv.get('alerta/timeout')[1]['Value'])
|
|||
origin = client.kv.get('alerta/origin')[1]['Value']
|
||||
api = Client(endpoint=url, key=key)
|
||||
|
||||
|
||||
def createheartbeat():
|
||||
for _ in range(max_retries):
|
||||
try:
|
||||
print(api.heartbeat(origin=origin, timeout=timeout))
|
||||
except Exception as e:
|
||||
print("HTTP Error: {}".format(e))
|
||||
print('HTTP Error: {}'.format(e))
|
||||
time.sleep(sleep)
|
||||
continue
|
||||
else:
|
||||
break
|
||||
else:
|
||||
print("api is down")
|
||||
print('api is down')
|
||||
|
||||
|
||||
def main():
|
||||
createheartbeat()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.1.1'
|
||||
|
||||
setup(
|
||||
name="alerta-consul",
|
||||
name='alerta-consul',
|
||||
version=version,
|
||||
description='Send emails from Alerta',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
license='MIT',
|
||||
author='Marco Supino',
|
||||
author_email='marco@supino.org',
|
||||
py_modules=['consulalerta','consulheartbeat'],
|
||||
py_modules=['consulalerta', 'consulheartbeat'],
|
||||
install_requires=[
|
||||
'alerta',
|
||||
'python-consul'
|
||||
|
@ -24,7 +23,7 @@ setup(
|
|||
'consul-heartbeat = consulheartbeat:main'
|
||||
]
|
||||
},
|
||||
keywords="alerta monitoring consul",
|
||||
keywords='alerta monitoring consul',
|
||||
classifiers=[
|
||||
'Topic :: System :: Monitoring',
|
||||
]
|
||||
|
|
|
@ -48,13 +48,13 @@ actionban = echo <matches> | grep -q "Failed password for [a-z][-a-z0-9_]* from
|
|||
# Notes.: Alerta API URL
|
||||
# Values: STRING
|
||||
#
|
||||
alertaurl =
|
||||
alertaurl =
|
||||
|
||||
# Option: alertaapikey
|
||||
# Notes.: Alerta API key
|
||||
# Values: STRING
|
||||
#
|
||||
alertaapikey =
|
||||
alertaapikey =
|
||||
|
||||
# Option: logpath
|
||||
# Notes.: Absolute path to the log file
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<strong>Duplicate Count</strong>: {{ alert.duplicate_count }} <br>
|
||||
<strong>Origin</strong>: {{ alert.origin }} <br>
|
||||
<strong>Tags</strong>: {{ alert.tags|join(', ') }} <br>
|
||||
{% for key,value in alert.attributes.items() -%}
|
||||
{% for key,value in alert.attributes.items() -%}
|
||||
{{ key|title }}: {{ value }}
|
||||
{% endfor -%}
|
||||
<br>
|
||||
|
|
|
@ -30,4 +30,3 @@ To acknowledge this alert visit this URL:
|
|||
{{ dashboard_url }}/#/alert/{{ alert.id }}
|
||||
|
||||
Generated by {{ program }} on {{ hostname }} at {{ now }}
|
||||
|
||||
|
|
|
@ -9,16 +9,14 @@ import re
|
|||
import signal
|
||||
import smtplib
|
||||
import socket
|
||||
from configparser import RawConfigParser
|
||||
from functools import reduce
|
||||
import six
|
||||
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from configparser import RawConfigParser
|
||||
from email.header import Header
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from functools import reduce
|
||||
|
||||
import jinja2
|
||||
from alertaclient.api import Client
|
||||
|
@ -33,7 +31,7 @@ DNS_RESOLVER_AVAILABLE = False
|
|||
try:
|
||||
import dns.resolver
|
||||
DNS_RESOLVER_AVAILABLE = True
|
||||
except:
|
||||
except Exception:
|
||||
sys.stdout.write('Python dns.resolver unavailable. The skip_mta option will be forced to False\n') # nopep8
|
||||
|
||||
|
||||
|
@ -42,34 +40,39 @@ LOG = logging.getLogger(__name__)
|
|||
root = logging.getLogger()
|
||||
|
||||
DEFAULT_OPTIONS = {
|
||||
'config_file': '~/.alerta.conf',
|
||||
'profile': None,
|
||||
'endpoint': 'http://localhost:8080',
|
||||
'key': '',
|
||||
'amqp_url': 'redis://localhost:6379/',
|
||||
'amqp_topic': 'notify',
|
||||
'amqp_queue_name': '', # Name of the AMQP queue. Default is no name (default queue destination).
|
||||
'amqp_queue_exclusive': True, # Exclusive queues may only be consumed by the current connection.
|
||||
'smtp_host': 'smtp.gmail.com',
|
||||
'smtp_port': 587,
|
||||
'smtp_username': '', # application-specific username if it differs from the specified 'mail_from' user
|
||||
'config_file': '~/.alerta.conf',
|
||||
'profile': None,
|
||||
'endpoint': 'http://localhost:8080',
|
||||
'key': '',
|
||||
'amqp_url': 'redis://localhost:6379/',
|
||||
'amqp_topic': 'notify',
|
||||
# Name of the AMQP queue. Default is no name (default queue destination).
|
||||
'amqp_queue_name': '',
|
||||
# Exclusive queues may only be consumed by the current connection.
|
||||
'amqp_queue_exclusive': True,
|
||||
'smtp_host': 'smtp.gmail.com',
|
||||
'smtp_port': 587,
|
||||
# application-specific username if it differs from the specified 'mail_from' user
|
||||
'smtp_username': '',
|
||||
'smtp_password': '', # application-specific password if gmail used
|
||||
'smtp_starttls': True, # use the STARTTLS SMTP extension
|
||||
'smtp_use_ssl': False, # whether or not SSL is being used for the SMTP connection
|
||||
'ssl_key_file': None, # a PEM formatted private key file for the SSL connection
|
||||
'ssl_cert_file': None, # a certificate chain file for the SSL connection
|
||||
'mail_from': '', # alerta@example.com
|
||||
'mail_to': [], # devops@example.com, support@example.com
|
||||
'ssl_key_file': None, # a PEM formatted private key file for the SSL connection
|
||||
'ssl_cert_file': None, # a certificate chain file for the SSL connection
|
||||
'mail_from': '', # alerta@example.com
|
||||
'mail_to': [], # devops@example.com, support@example.com
|
||||
'mail_localhost': None, # fqdn to use in the HELO/EHLO command
|
||||
'mail_template': os.path.dirname(__file__) + os.sep + 'email.tmpl',
|
||||
'mail_template': os.path.dirname(__file__) + os.sep + 'email.tmpl',
|
||||
'mail_template_html': os.path.dirname(__file__) + os.sep + 'email.html.tmpl', # nopep8
|
||||
'mail_subject': ('[{{ alert.status|capitalize }}] {{ alert.environment }}: '
|
||||
'{{ alert.severity|capitalize }} {{ alert.event }} on '
|
||||
'{{ alert.service|join(\',\') }} {{ alert.resource }}'),
|
||||
'mail_subject': (
|
||||
'[{{ alert.status|capitalize }}] {{ alert.environment }}: '
|
||||
'{{ alert.severity|capitalize }} {{ alert.event }} on '
|
||||
'{{ alert.service|join(\',\') }} {{ alert.resource }}'
|
||||
),
|
||||
'dashboard_url': 'http://try.alerta.io',
|
||||
'debug': False,
|
||||
'skip_mta': False,
|
||||
'email_type': 'text', # options are: text, html
|
||||
'debug': False,
|
||||
'skip_mta': False,
|
||||
'email_type': 'text', # options are: text, html
|
||||
'severities': []
|
||||
}
|
||||
|
||||
|
@ -135,10 +138,7 @@ class FanoutConsumer(ConsumerMixin):
|
|||
message.ack()
|
||||
return
|
||||
|
||||
if (
|
||||
alert.severity not in sevs and
|
||||
alert.previous_severity not in sevs
|
||||
):
|
||||
if alert.severity not in sevs and alert.previous_severity not in sevs:
|
||||
LOG.debug('Ignored alert %s: severity or previous_severity does not matche the severities configuration (%s)',
|
||||
alertid, sevs)
|
||||
message.ack()
|
||||
|
@ -177,7 +177,7 @@ class MailSender(threading.Thread):
|
|||
self._template_name_html = os.path.basename(
|
||||
OPTIONS['mail_template_html'])
|
||||
|
||||
super(MailSender, self).__init__()
|
||||
super().__init__()
|
||||
|
||||
def run(self):
|
||||
|
||||
|
@ -199,9 +199,10 @@ class MailSender(threading.Thread):
|
|||
|
||||
if keep_alive >= 10:
|
||||
try:
|
||||
origin = '{}/{}'.format('alerta-mailer', OPTIONS['smtp_host'])
|
||||
origin = '{}/{}'.format('alerta-mailer',
|
||||
OPTIONS['smtp_host'])
|
||||
api.heartbeat(origin, tags=[__version__])
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
time.sleep(5)
|
||||
continue
|
||||
keep_alive = 0
|
||||
|
@ -221,7 +222,7 @@ class MailSender(threading.Thread):
|
|||
return True
|
||||
LOG.debug('Regex %s matches nothing', regex)
|
||||
return False
|
||||
elif isinstance(value, six.string_types): # pylint: disable=undefined-variable
|
||||
elif isinstance(value, str): # pylint: disable=undefined-variable
|
||||
LOG.debug('Trying to match %s to %s',
|
||||
value, regex)
|
||||
return re.search(regex, value) is not None
|
||||
|
@ -266,7 +267,7 @@ class MailSender(threading.Thread):
|
|||
' adding for this rule only')
|
||||
del contacts[:]
|
||||
contacts.extend(new_contacts)
|
||||
|
||||
|
||||
# Don't loose time (and try to send an email) if there is no contact...
|
||||
if not contacts:
|
||||
return
|
||||
|
@ -284,10 +285,7 @@ class MailSender(threading.Thread):
|
|||
text = self._template_env.get_template(
|
||||
self._template_name).render(**template_vars)
|
||||
|
||||
if (
|
||||
OPTIONS['email_type'] == 'html' and
|
||||
self._template_name_html
|
||||
):
|
||||
if OPTIONS['email_type'] == 'html' and self._template_name_html:
|
||||
html = self._template_env.get_template(
|
||||
self._template_name_html).render(**template_vars)
|
||||
else:
|
||||
|
@ -296,7 +294,7 @@ class MailSender(threading.Thread):
|
|||
msg = MIMEMultipart('alternative')
|
||||
msg['Subject'] = Header(subject, 'utf-8').encode()
|
||||
msg['From'] = OPTIONS['mail_from']
|
||||
msg['To'] = ", ".join(contacts)
|
||||
msg['To'] = ', '.join(contacts)
|
||||
msg.preamble = msg['Subject']
|
||||
|
||||
# by default we are going to assume that the email is going to be text
|
||||
|
@ -308,15 +306,15 @@ class MailSender(threading.Thread):
|
|||
|
||||
try:
|
||||
self._send_email_message(msg, contacts)
|
||||
LOG.debug('%s : Email sent to %s' % (alert.get_id(),
|
||||
','.join(contacts)))
|
||||
LOG.debug('{} : Email sent to {}'.format(alert.get_id(),
|
||||
','.join(contacts)))
|
||||
return (msg, contacts)
|
||||
except smtplib.SMTPException as e:
|
||||
LOG.error('Failed to send mail to %s on %s:%s : %s',
|
||||
", ".join(contacts),
|
||||
', '.join(contacts),
|
||||
OPTIONS['smtp_host'], OPTIONS['smtp_port'], e)
|
||||
return None
|
||||
except (socket.error, socket.herror, socket.gaierror) as e:
|
||||
except (OSError, socket.herror, socket.gaierror) as e:
|
||||
LOG.error('Mail server connection error: %s', e)
|
||||
return None
|
||||
except Exception as e:
|
||||
|
@ -337,10 +335,10 @@ class MailSender(threading.Thread):
|
|||
msg['To'] = dest
|
||||
if OPTIONS['smtp_use_ssl']:
|
||||
mx = smtplib.SMTP_SSL(mxhost,
|
||||
OPTIONS['smtp_port'],
|
||||
local_hostname=OPTIONS['mail_localhost'],
|
||||
keyfile=OPTIONS['ssl_key_file'],
|
||||
certfile=OPTIONS['ssl_cert_file'])
|
||||
OPTIONS['smtp_port'],
|
||||
local_hostname=OPTIONS['mail_localhost'],
|
||||
keyfile=OPTIONS['ssl_key_file'],
|
||||
certfile=OPTIONS['ssl_cert_file'])
|
||||
else:
|
||||
mx = smtplib.SMTP(mxhost,
|
||||
OPTIONS['smtp_port'],
|
||||
|
@ -356,10 +354,10 @@ class MailSender(threading.Thread):
|
|||
else:
|
||||
if OPTIONS['smtp_use_ssl']:
|
||||
mx = smtplib.SMTP_SSL(OPTIONS['smtp_host'],
|
||||
OPTIONS['smtp_port'],
|
||||
local_hostname=OPTIONS['mail_localhost'],
|
||||
keyfile=OPTIONS['ssl_key_file'],
|
||||
certfile=OPTIONS['ssl_cert_file'])
|
||||
OPTIONS['smtp_port'],
|
||||
local_hostname=OPTIONS['mail_localhost'],
|
||||
keyfile=OPTIONS['ssl_key_file'],
|
||||
certfile=OPTIONS['ssl_cert_file'])
|
||||
else:
|
||||
mx = smtplib.SMTP(OPTIONS['smtp_host'],
|
||||
OPTIONS['smtp_port'],
|
||||
|
@ -428,7 +426,7 @@ def validate_rules(rules):
|
|||
|
||||
|
||||
def parse_group_rules(config_file):
|
||||
rules_dir = "{}/alerta.rules.d".format(os.path.dirname(config_file))
|
||||
rules_dir = '{}/alerta.rules.d'.format(os.path.dirname(config_file))
|
||||
LOG.debug('Looking for rules files in %s', rules_dir)
|
||||
if os.path.exists(rules_dir):
|
||||
rules_d = []
|
||||
|
@ -436,11 +434,11 @@ def parse_group_rules(config_file):
|
|||
for filename in files[2]:
|
||||
LOG.debug('Parsing %s', filename)
|
||||
try:
|
||||
with open(os.path.join(files[0], filename), 'r') as f:
|
||||
with open(os.path.join(files[0], filename)) as f:
|
||||
rules = validate_rules(json.load(f))
|
||||
if rules is not None:
|
||||
rules_d.extend(rules)
|
||||
except:
|
||||
except Exception:
|
||||
LOG.exception('Could not parse file')
|
||||
return rules_d
|
||||
return ()
|
||||
|
@ -460,12 +458,12 @@ def main():
|
|||
defopts = {k: str(v) if type(v) is bool else v for k, v in DEFAULT_OPTIONS.items()} # nopep8
|
||||
config = RawConfigParser(defaults=defopts)
|
||||
|
||||
if os.path.exists("{}.d".format(config_file)):
|
||||
config_path = "{}.d".format(config_file)
|
||||
if os.path.exists('{}.d'.format(config_file)):
|
||||
config_path = '{}.d'.format(config_file)
|
||||
config_list = []
|
||||
for files in os.walk(config_path):
|
||||
for filename in files[2]:
|
||||
config_list.append("{}/{}".format(config_path, filename))
|
||||
config_list.append('{}/{}'.format(config_path, filename))
|
||||
|
||||
config_list.append(os.path.expanduser(config_file))
|
||||
config_file = config_list
|
||||
|
@ -477,8 +475,8 @@ def main():
|
|||
config.read(config_file)
|
||||
else:
|
||||
config.read(os.path.expanduser(config_file))
|
||||
except Exception as e:
|
||||
LOG.warning("Problem reading configuration file %s - is this an ini file?", config_file) # nopep8
|
||||
except Exception:
|
||||
LOG.warning('Problem reading configuration file %s - is this an ini file?', config_file) # nopep8
|
||||
sys.exit(1)
|
||||
|
||||
if config.has_section(CONFIG_SECTION):
|
||||
|
@ -489,7 +487,8 @@ def main():
|
|||
int: config.getint,
|
||||
float: config.getfloat,
|
||||
bool: config.getboolean,
|
||||
list: lambda s, o: [e.strip() for e in config.get(s, o).split(',')] if len(config.get(s, o)) else []
|
||||
list: lambda s, o: [e.strip() for e in config.get(
|
||||
s, o).split(',')] if len(config.get(s, o)) else []
|
||||
}
|
||||
for opt in DEFAULT_OPTIONS:
|
||||
# Convert the options to the expected type
|
||||
|
@ -500,7 +499,8 @@ def main():
|
|||
|
||||
OPTIONS['endpoint'] = os.environ.get('ALERTA_ENDPOINT') or OPTIONS['endpoint'] # nopep8
|
||||
OPTIONS['key'] = os.environ.get('ALERTA_API_KEY') or OPTIONS['key']
|
||||
OPTIONS['smtp_username'] = os.environ.get('SMTP_USERNAME') or OPTIONS['smtp_username'] or OPTIONS['mail_from']
|
||||
OPTIONS['smtp_username'] = os.environ.get(
|
||||
'SMTP_USERNAME') or OPTIONS['smtp_username'] or OPTIONS['mail_from']
|
||||
OPTIONS['smtp_password'] = os.environ.get('SMTP_PASSWORD') or OPTIONS['smtp_password'] # nopep8
|
||||
|
||||
if os.environ.get('DEBUG'):
|
||||
|
@ -543,5 +543,6 @@ def main():
|
|||
print(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
alerta>=5.0.2
|
||||
jinja2
|
||||
kombu
|
||||
redis
|
||||
jinja2
|
||||
|
|
|
@ -5,7 +5,7 @@ import setuptools
|
|||
version = '5.2.1'
|
||||
|
||||
setuptools.setup(
|
||||
name="alerta-mailer",
|
||||
name='alerta-mailer',
|
||||
version=version,
|
||||
description='Send emails from Alerta',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
@ -29,7 +29,7 @@ setuptools.setup(
|
|||
'alerta-mailer = mailer:main'
|
||||
]
|
||||
},
|
||||
keywords="alerta monitoring mailer sendmail smtp",
|
||||
keywords='alerta monitoring mailer sendmail smtp',
|
||||
classifiers=[
|
||||
'Topic :: System :: Monitoring',
|
||||
]
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
'''
|
||||
Unit test definitions for all rules
|
||||
'''
|
||||
import pytest
|
||||
import mailer
|
||||
import pytest
|
||||
from alertaclient.models.alert import Alert
|
||||
from mock import MagicMock, patch, DEFAULT
|
||||
from mock import DEFAULT, MagicMock, patch
|
||||
|
||||
|
||||
def test_rules_dont_exist():
|
||||
|
@ -13,7 +13,7 @@ def test_rules_dont_exist():
|
|||
'''
|
||||
with patch('mailer.os') as system_os:
|
||||
system_os.path.exists.return_value = False
|
||||
res = mailer.parse_group_rules('config_file')
|
||||
# res = mailer.parse_group_rules('config_file')
|
||||
system_os.path.exists.called_once_with('confile_file')
|
||||
# assert res is None
|
||||
|
||||
|
@ -57,38 +57,38 @@ TESTDOCS = [
|
|||
({}, False),
|
||||
([], True),
|
||||
([
|
||||
{"name": "invalid_no_fields",
|
||||
"contacts": []}
|
||||
{'name': 'invalid_no_fields',
|
||||
'contacts': []}
|
||||
], False),
|
||||
([
|
||||
{"name": "invalid_empty_fields",
|
||||
"fields": [],
|
||||
"contacts": []}
|
||||
{'name': 'invalid_empty_fields',
|
||||
'fields': [],
|
||||
'contacts': []}
|
||||
], False),
|
||||
([
|
||||
{"name": "invalid_no_contacts",
|
||||
"fields": [{"field": "resource", "regex": r"\d{4}"}]}
|
||||
{'name': 'invalid_no_contacts',
|
||||
'fields': [{'field': 'resource', 'regex': r'\d{4}'}]}
|
||||
], False),
|
||||
([
|
||||
{"name": "invalid_no_field_on_fields",
|
||||
"fields": [{"regex": r"\d{4}"}],
|
||||
"contacts": []}
|
||||
{'name': 'invalid_no_field_on_fields',
|
||||
'fields': [{'regex': r'\d{4}'}],
|
||||
'contacts': []}
|
||||
], False),
|
||||
([
|
||||
{"name": "invalid_fields_not_list",
|
||||
"fields": {"regex": r"\d{4}"},
|
||||
"contacts": []}
|
||||
{'name': 'invalid_fields_not_list',
|
||||
'fields': {'regex': r'\d{4}'},
|
||||
'contacts': []}
|
||||
], False),
|
||||
([
|
||||
{"name": "invalid_no_fields_regex",
|
||||
"fields": [{"field": "test"}],
|
||||
"contacts": []}
|
||||
{'name': 'invalid_no_fields_regex',
|
||||
'fields': [{'field': 'test'}],
|
||||
'contacts': []}
|
||||
], False),
|
||||
([
|
||||
{"name": "invalid_no_fields_regex",
|
||||
"fields": [{"field": "tags", "regex": "atag"}],
|
||||
"exclude": True,
|
||||
"contacts": []}
|
||||
{'name': 'invalid_no_fields_regex',
|
||||
'fields': [{'field': 'tags', 'regex': 'atag'}],
|
||||
'exclude': True,
|
||||
'contacts': []}
|
||||
], True),
|
||||
]
|
||||
|
||||
|
@ -108,9 +108,9 @@ def test_rules_validation(doc, is_valid):
|
|||
RULES_DATA = [
|
||||
# ({'resource': 'server-1234', 'event': '5678'}, [], []),
|
||||
({'resource': '1234', 'event': '5678'},
|
||||
[{"name": "Test1",
|
||||
"fields": [{"field": "resource", "regex": r"(\w.*)?\d{4}"}],
|
||||
"contacts": ["test@example.com"]}],
|
||||
[{'name': 'Test1',
|
||||
'fields': [{'field': 'resource', 'regex': r'(\w.*)?\d{4}'}],
|
||||
'contacts': ['test@example.com']}],
|
||||
['test@example.com'])
|
||||
]
|
||||
|
||||
|
@ -144,7 +144,7 @@ def test_rule_matches_list():
|
|||
regex.match.side_effect = [MagicMock(), None]
|
||||
assert mail_sender._rule_matches('regex', ['item1']) is True
|
||||
regex.match.assert_called_with('regex', 'item1')
|
||||
assert mail_sender._rule_matches('regex', ['item2']) is False
|
||||
assert mail_sender._rule_matches('regex', ['item2']) is False
|
||||
regex.match.assert_called_with('regex', 'item2')
|
||||
|
||||
|
||||
|
@ -162,4 +162,3 @@ def test_rule_matches_string():
|
|||
regex.search.assert_called_with('regex', 'value1')
|
||||
assert mail_sender._rule_matches('regex', 'value2') is False
|
||||
regex.search.assert_called_with('regex', 'value2')
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@ Set up OpsGenie with an OpsGenie Edge Connector integration.
|
|||
==================
|
||||
|
||||
|
||||
While the OpsGenie plugin provided by Alerta can send alerts to OpsGenie it does not allow OpsGenie to update Alerta.
|
||||
Fortunately, OpsGenie has an edge connector we can install and configure to use some code to do this for us.
|
||||
While the OpsGenie plugin provided by Alerta can send alerts to OpsGenie it does not allow OpsGenie to update Alerta.
|
||||
Fortunately, OpsGenie has an edge connector we can install and configure to use some code to do this for us.
|
||||
|
||||
|
||||
Set up OpsGenie Edge Connector (oec)
|
||||
Set up OpsGenie Edge Connector (oec)
|
||||
------------------
|
||||
|
||||
Log in to OpsGenie
|
||||
|
@ -17,10 +17,10 @@ Set up OpsGenie Edge Connector (oec)
|
|||
|
||||
- from OEC "alert is acknowledged" -> Alerta will ack the alert
|
||||
- from OEC "alert is closed" -> Alerta will close the alert
|
||||
- from OEC "alert is unacknowledged" -> Alerta will unack the alert
|
||||
- from OEC "a note is added" -> Alerta will add a note to the alert
|
||||
- from OEC "A user executes assign ownership" -> Alerta will assign the alert
|
||||
- from OEC "A user takes ownership" -> Alerta will assign the alert
|
||||
- from OEC "alert is unacknowledged" -> Alerta will unack the alert
|
||||
- from OEC "a note is added" -> Alerta will add a note to the alert
|
||||
- from OEC "A user executes assign ownership" -> Alerta will assign the alert
|
||||
- from OEC "A user takes ownership" -> Alerta will assign the alert
|
||||
|
||||
|
||||
Click "Add new action" and add whichever actions you desire from OEC.
|
||||
|
@ -47,7 +47,7 @@ As mentioned all actions will be shown to be executed by the user you chose to a
|
|||
Install and configure OpsGenie Edge Connector on a host in your network. Alerta has been tested with OEC version 1.1.3
|
||||
------------------
|
||||
|
||||
Some links to OpsGenie OEC documentation:
|
||||
Some links to OpsGenie OEC documentation:
|
||||
|
||||
[Installation docs for OEC provided by Atlassian](https://support.atlassian.com/opsgenie/docs/opsgenie-edge-connector-installation-packs/)
|
||||
|
||||
|
@ -110,10 +110,10 @@ Remove some things that OEC installs by default
|
|||
|
||||
If Alerta is configured to send alerts to OpsGenie then OEC should get updates and be able to update alerts in Alerta from any of the OpsGenie interfaces (web/phone etc..)
|
||||
|
||||
Troubleshooting
|
||||
Troubleshooting
|
||||
------------------
|
||||
If alerts are not firing it could be due to the alert source not being set. This requires an update to the OpsGenie plugin that hasn't been accepted yet.
|
||||
Including a line in the plugin to set the source from the config or a reasonable default should address this.
|
||||
If alerts are not firing it could be due to the alert source not being set. This requires an update to the OpsGenie plugin that hasn't been accepted yet.
|
||||
Including a line in the plugin to set the source from the config or a reasonable default should address this.
|
||||
|
||||
```OPSGENIE_ALERT_SOURCE = os.environ.get('OPSGENIE_ALERT_SOURCE') or app.config.get('OPSGENIE_ALERT_SOURCE', 'Alerta'```
|
||||
|
||||
|
@ -131,8 +131,8 @@ Troubleshooting
|
|||
}
|
||||
```
|
||||
|
||||
This is useful for OpsGenie Edge Connector to not update ALL Edge connector integrations if you have more than one running in your env. This will send updates to Alerta when the
|
||||
source was Alerta. So if JIRA is also integrated through OEC it won't be trying to send any updates to Alerta etc.
|
||||
This is useful for OpsGenie Edge Connector to not update ALL Edge connector integrations if you have more than one running in your env. This will send updates to Alerta when the
|
||||
source was Alerta. So if JIRA is also integrated through OEC it won't be trying to send any updates to Alerta etc.
|
||||
|
||||

|
||||
|
||||
|
|
|
@ -3,15 +3,21 @@ import argparse
|
|||
import json
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import requests
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-payload', '--queuePayload', help='Payload from queue', required=True)
|
||||
parser.add_argument('-apiKey', '--apiKey', help='The apiKey of the integration', required=True)
|
||||
parser.add_argument('-opsgenieUrl', '--opsgenieUrl', help='The url', required=True)
|
||||
parser.add_argument('-payload', '--queuePayload',
|
||||
help='Payload from queue', required=True)
|
||||
parser.add_argument('-apiKey', '--apiKey',
|
||||
help='The apiKey of the integration', required=True)
|
||||
parser.add_argument('-opsgenieUrl', '--opsgenieUrl',
|
||||
help='The url', required=True)
|
||||
parser.add_argument('-logLevel', '--logLevel', help='Log level', required=True)
|
||||
parser.add_argument('-alertaApiUrl', '--alertaApiUrl', help='The url to do alerta api operations', required=True)
|
||||
parser.add_argument('-alertaApiKey', '--alertaApiKey', help='The api key to do alerta api operations', required=True)
|
||||
parser.add_argument('-alertaApiUrl', '--alertaApiUrl',
|
||||
help='The url to do alerta api operations', required=True)
|
||||
parser.add_argument('-alertaApiKey', '--alertaApiKey',
|
||||
help='The api key to do alerta api operations', required=True)
|
||||
args = vars(parser.parse_args())
|
||||
|
||||
logging.basicConfig(stream=sys.stdout, level=args['logLevel'])
|
||||
|
@ -20,11 +26,14 @@ LOG_PREFIX = 'oec_action'
|
|||
|
||||
def do_alerta_things(alerta_api_target, alerta_headers, payload):
|
||||
try:
|
||||
r = requests.put(alerta_api_target, json=payload, headers=alerta_headers, timeout=2)
|
||||
r = requests.put(alerta_api_target, json=payload,
|
||||
headers=alerta_headers, timeout=2)
|
||||
except Exception as e:
|
||||
logging.error("{} - Error updating {}. Error: {}".format(LOG_PREFIX, alerta_api_target, e))
|
||||
logging.error(
|
||||
'{} - Error updating {}. Error: {}'.format(LOG_PREFIX, alerta_api_target, e))
|
||||
|
||||
logging.info('{} - Call to {} return status code: {}'.format(LOG_PREFIX, alerta_api_target, r.status_code))
|
||||
logging.info('{} - Call to {} return status code: {}'.format(LOG_PREFIX,
|
||||
alerta_api_target, r.status_code))
|
||||
return r.status_code
|
||||
|
||||
|
||||
|
@ -32,7 +41,8 @@ def get_alert_status(alerta_api_target, alerta_headers):
|
|||
try:
|
||||
r = requests.get(alerta_api_target, headers=alerta_headers, timeout=2)
|
||||
except Exception as e:
|
||||
logging.error("{} - Error getting {} : {}".format(LOG_PREFIX, alerta_api_target, e))
|
||||
logging.error(
|
||||
'{} - Error getting {} : {}'.format(LOG_PREFIX, alerta_api_target, e))
|
||||
|
||||
contents = json.loads(r.content)
|
||||
cur_status = contents['alert'].get('status', None)
|
||||
|
@ -45,111 +55,128 @@ def main():
|
|||
queue_message_string = args['queuePayload']
|
||||
queue_message = json.loads(queue_message_string)
|
||||
|
||||
action = queue_message["action"]
|
||||
LOG_PREFIX = "[ {} ]".format(action)
|
||||
action = queue_message['action']
|
||||
LOG_PREFIX = '[ {} ]'.format(action)
|
||||
|
||||
alert_id = queue_message["alert"]["alertId"]
|
||||
origin = queue_message["alert"]["source"]
|
||||
username = queue_message["alert"]["username"]
|
||||
logging.debug("{} - Username is: {}, Origin is: {}".format(LOG_PREFIX, username, origin))
|
||||
alert_id = queue_message['alert']['alertId']
|
||||
origin = queue_message['alert']['source']
|
||||
username = queue_message['alert']['username']
|
||||
logging.debug(
|
||||
'{} - Username is: {}, Origin is: {}'.format(LOG_PREFIX, username, origin))
|
||||
alerta_url = args['alertaApiUrl']
|
||||
alerta_headers = {'Content-type': 'application/json', 'Authorization': 'Key {}'.format(args['alertaApiKey'])}
|
||||
alerta_headers = {'Content-type': 'application/json',
|
||||
'Authorization': 'Key {}'.format(args['alertaApiKey'])}
|
||||
|
||||
logging.info("{} - Using Alerta URL : {}".format(LOG_PREFIX, alerta_url))
|
||||
logging.debug("{} - Message: {}".format(LOG_PREFIX, queue_message))
|
||||
logging.info('{} - Using Alerta URL : {}'.format(LOG_PREFIX, alerta_url))
|
||||
logging.debug('{} - Message: {}'.format(LOG_PREFIX, queue_message))
|
||||
timeout = 300 # default timeout for connections to opsgenie api
|
||||
action_timeout = 7200 # default alerta action timeout
|
||||
|
||||
logging.info("{} - Will execute {} for alertId {}".format(LOG_PREFIX, action, alert_id))
|
||||
logging.info(
|
||||
'{} - Will execute {} for alertId {}'.format(LOG_PREFIX, action, alert_id))
|
||||
|
||||
action_map = {"Acknowledge": "ack",
|
||||
"AddNote": "note",
|
||||
"AssignOwnership": "assign",
|
||||
"TakeOwnership": "assign",
|
||||
"UnAcknowledge": "unack",
|
||||
"Close": "close",
|
||||
"Snooze": "shelve"}
|
||||
action_map = {'Acknowledge': 'ack',
|
||||
'AddNote': 'note',
|
||||
'AssignOwnership': 'assign',
|
||||
'TakeOwnership': 'assign',
|
||||
'UnAcknowledge': 'unack',
|
||||
'Close': 'close',
|
||||
'Snooze': 'shelve'}
|
||||
|
||||
if alert_id:
|
||||
alert_api_url = "{}/v2/alerts/{}".format(args['opsgenieUrl'], alert_id)
|
||||
alert_api_url = '{}/v2/alerts/{}'.format(args['opsgenieUrl'], alert_id)
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Accept-Language": "application/json",
|
||||
"Authorization": "GenieKey {}".format(args['apiKey'])
|
||||
'Content-Type': 'application/json',
|
||||
'Accept-Language': 'application/json',
|
||||
'Authorization': 'GenieKey {}'.format(args['apiKey'])
|
||||
}
|
||||
alert_response = requests.get(alert_api_url, headers=headers, timeout=timeout)
|
||||
alert_response = requests.get(
|
||||
alert_api_url, headers=headers, timeout=timeout)
|
||||
if alert_response.status_code < 299 and alert_response.json()['data']:
|
||||
if action in action_map.keys() and origin == 'Alerta':
|
||||
|
||||
alias = queue_message["alert"]["alias"]
|
||||
logging.info("{} - {} {} from {}".format(LOG_PREFIX, action, alias, username))
|
||||
alias = queue_message['alert']['alias']
|
||||
logging.info(
|
||||
'{} - {} {} from {}'.format(LOG_PREFIX, action, alias, username))
|
||||
alerta_action = action_map[action]
|
||||
|
||||
# set default target and payload
|
||||
alerta_api_target = "{}/{}/action".format(alerta_url, alias)
|
||||
payload = {"action": alerta_action, "text": "{}d by {}.".format(action, username), "timeout": action_timeout}
|
||||
alerta_api_target = '{}/{}/action'.format(alerta_url, alias)
|
||||
payload = {'action': alerta_action, 'text': '{}d by {}.'.format(
|
||||
action, username), 'timeout': action_timeout}
|
||||
|
||||
# payload will change according to action and then fall through to the
|
||||
# default api call unless the alerta_api_target is set to None on its way down
|
||||
if action == 'Snooze':
|
||||
# snooze_end = queue_message["alert"]["snoozedUntil"]
|
||||
snooze_end = queue_message["alert"]["snoozeEndDate"]
|
||||
snooze_end = queue_message['alert']['snoozeEndDate']
|
||||
# snooze_end = dt.fromtimestamp(int("{}".format(snooze_end)[:-3])) # < - datetime object
|
||||
# now = dt.fromtimestamp(dt.timestamp(dt.utcnow()))
|
||||
# snooze_seconds = int((snooze_end - now).total_seconds())
|
||||
# if snooze_seconds > 0:
|
||||
# logging.info("{} - Snoozing for {} seconds".format(LOG_PREFIX, snooze_seconds))
|
||||
payload["text"] = "Shelved until: {} by {}".format(snooze_end, username)
|
||||
payload['text'] = 'Shelved until: {} by {}'.format(
|
||||
snooze_end, username)
|
||||
|
||||
elif action == 'AddNote':
|
||||
# payload and target for notes is different than actions
|
||||
alerta_api_target = "{}/{}/note".format(alerta_url, alias)
|
||||
alerta_api_target = '{}/{}/note'.format(alerta_url, alias)
|
||||
|
||||
# since we have one api key assigned to a default 'opsgenie' user
|
||||
# include the username with the note so we know who wrote it
|
||||
payload = {"note": "{} Added by {}".format(queue_message["alert"]["note"], username)}
|
||||
payload = {'note': '{} Added by {}'.format(
|
||||
queue_message['alert']['note'], username)}
|
||||
|
||||
elif action == 'AssignOwnership': #
|
||||
owner = queue_message["alert"]["owner"]
|
||||
owner = queue_message['alert']['owner']
|
||||
# update the payload
|
||||
payload["text"] = "Assigned to {} by {}".format(owner, username)
|
||||
payload['text'] = 'Assigned to {} by {}'.format(
|
||||
owner, username)
|
||||
elif action == 'TakeOwnership': #
|
||||
# open_payload = { "action": "open", "text": "transisition to open for assignment", "timeout": action_timeout }
|
||||
# do_alerta_things(alerta_api_target,open_payload)
|
||||
|
||||
# update the payload
|
||||
payload["text"] = "{} took ownership".format(username)
|
||||
elif action == 'Acknowledge': # update the acked-by attribute too..
|
||||
payload['text'] = '{} took ownership'.format(username)
|
||||
# update the acked-by attribute too..
|
||||
elif action == 'Acknowledge':
|
||||
# opsgenie does not send an action when an alert comes out of snooze
|
||||
# we will check the alert and if it has a 'shelved' status unshelve it
|
||||
# this is silly but the tags opgsgenie has are NOT the alert tags.
|
||||
# or I would just look at those
|
||||
# Get the alert so we can check the status
|
||||
alert_url = "{}/{}".format(alerta_url, alias)
|
||||
alert_url = '{}/{}'.format(alerta_url, alias)
|
||||
status = get_alert_status(alert_url, alerta_headers)
|
||||
if status == 'shelved':
|
||||
# unshelve the thing (default)
|
||||
# and then the normal action can run
|
||||
unshelve_payload = {"action": "unshelve", "text": "Unshelved by {}.".format(username), "timeout": action_timeout}
|
||||
unshelve_payload = {'action': 'unshelve', 'text': 'Unshelved by {}.'.format(
|
||||
username), 'timeout': action_timeout}
|
||||
|
||||
do_alerta_things(alerta_api_target, alerta_headers, unshelve_payload)
|
||||
do_alerta_things(alerta_api_target,
|
||||
alerta_headers, unshelve_payload)
|
||||
# update the api target to None unshelving will put it back to Ack
|
||||
alerta_api_target = None
|
||||
|
||||
# update the acked-by attribute
|
||||
ack_by_payload = {"attributes": {"acked-by": username}}
|
||||
ack_by_target = "{}/{}/attributes".format(alerta_url, alias)
|
||||
do_alerta_things(ack_by_target, alerta_headers, ack_by_payload)
|
||||
ack_by_payload = {'attributes': {'acked-by': username}}
|
||||
ack_by_target = '{}/{}/attributes'.format(
|
||||
alerta_url, alias)
|
||||
do_alerta_things(
|
||||
ack_by_target, alerta_headers, ack_by_payload)
|
||||
|
||||
if alerta_api_target:
|
||||
# as long as none of the above set the
|
||||
# alerta_api_target to None we should do the original action
|
||||
do_alerta_things(alerta_api_target, alerta_headers, payload)
|
||||
do_alerta_things(alerta_api_target,
|
||||
alerta_headers, payload)
|
||||
|
||||
else:
|
||||
logging.warning("{} - Alert with id [ {} ] does not exist in Opsgenie. It is probably deleted.".format(LOG_PREFIX, alert_id))
|
||||
logging.warning(
|
||||
'{} - Alert with id [ {} ] does not exist in Opsgenie. It is probably deleted.'.format(LOG_PREFIX, alert_id))
|
||||
else:
|
||||
logging.warning("{} - Alert id was not sent in the payload. Ignoring.".format(LOG_PREFIX))
|
||||
logging.warning(
|
||||
'{} - Alert id was not sent in the payload. Ignoring.'.format(LOG_PREFIX))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -95,4 +95,3 @@ function doAlertaAPICall($path, $parameters, $alerta_baseurl, $alerta_apikey) {
|
|||
}
|
||||
}
|
||||
?>
|
||||
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
|
||||
import sys
|
||||
import platform
|
||||
import time
|
||||
import subprocess
|
||||
import threading
|
||||
import Queue
|
||||
import re
|
||||
import logging
|
||||
import yaml
|
||||
import platform
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
import Queue
|
||||
import yaml
|
||||
from alertaclient.api import Client
|
||||
|
||||
__version__ = '3.3.0'
|
||||
|
@ -75,19 +74,23 @@ class WorkerThread(threading.Thread):
|
|||
environment, service, resource, retries, queue_time = item
|
||||
|
||||
if time.time() - queue_time > LOOP_EVERY:
|
||||
LOG.warning('Ping request to %s expired after %d seconds.', resource, int(time.time() - queue_time))
|
||||
LOG.warning('Ping request to %s expired after %d seconds.',
|
||||
resource, int(time.time() - queue_time))
|
||||
self.queue.task_done()
|
||||
continue
|
||||
|
||||
LOG.info('%s pinging %s...', self.getName(), resource)
|
||||
if retries > 1:
|
||||
rc, rtt, loss, stdout = self.pinger(resource, count=2, timeout=5)
|
||||
rc, rtt, loss, stdout = self.pinger(
|
||||
resource, count=2, timeout=5)
|
||||
else:
|
||||
rc, rtt, loss, stdout = self.pinger(resource, count=5, timeout=PING_MAX_TIMEOUT)
|
||||
rc, rtt, loss, stdout = self.pinger(
|
||||
resource, count=5, timeout=PING_MAX_TIMEOUT)
|
||||
|
||||
if rc != PING_OK and retries:
|
||||
LOG.info('Retrying ping %s %s more times', resource, retries)
|
||||
self.queue.put((environment, service, resource, retries - 1, time.time()))
|
||||
self.queue.put((environment, service, resource,
|
||||
retries - 1, time.time()))
|
||||
self.queue.task_done()
|
||||
continue
|
||||
|
||||
|
@ -96,15 +99,18 @@ class WorkerThread(threading.Thread):
|
|||
if avg > PING_SLOW_CRITICAL:
|
||||
event = 'PingSlow'
|
||||
severity = 'critical'
|
||||
text = 'Node responded to ping in %s ms avg (> %s ms)' % (avg, PING_SLOW_CRITICAL)
|
||||
text = 'Node responded to ping in {} ms avg (> {} ms)'.format(
|
||||
avg, PING_SLOW_CRITICAL)
|
||||
elif avg > PING_SLOW_WARNING:
|
||||
event = 'PingSlow'
|
||||
severity = 'warning'
|
||||
text = 'Node responded to ping in %s ms avg (> %s ms)' % (avg, PING_SLOW_WARNING)
|
||||
text = 'Node responded to ping in {} ms avg (> {} ms)'.format(
|
||||
avg, PING_SLOW_WARNING)
|
||||
else:
|
||||
event = 'PingOK'
|
||||
severity = 'normal'
|
||||
text = 'Node responding to ping avg/max %s/%s ms.' % tuple(rtt)
|
||||
text = 'Node responding to ping avg/max %s/%s ms.' % tuple(
|
||||
rtt)
|
||||
value = '%s/%s ms' % tuple(rtt)
|
||||
elif rc == PING_FAILED:
|
||||
event = 'PingFailed'
|
||||
|
@ -156,22 +162,26 @@ class WorkerThread(threading.Thread):
|
|||
if timeout > PING_MAX_TIMEOUT:
|
||||
timeout = PING_MAX_TIMEOUT
|
||||
|
||||
if sys.platform == "darwin":
|
||||
cmd = "ping -q -c %s -i %s -t %s %s" % (count, interval, timeout, node)
|
||||
if sys.platform == 'darwin':
|
||||
cmd = 'ping -q -c {} -i {} -t {} {}'.format(
|
||||
count, interval, timeout, node)
|
||||
else:
|
||||
cmd = "ping -q -c %s -i %s -w %s %s" % (count, interval, timeout, node)
|
||||
ping = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
cmd = 'ping -q -c {} -i {} -w {} {}'.format(
|
||||
count, interval, timeout, node)
|
||||
ping = subprocess.Popen(
|
||||
cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
stdout = ping.communicate()[0].rstrip('\n')
|
||||
rc = ping.returncode
|
||||
LOG.debug('Ping %s => %s (rc=%d)', cmd, stdout, rc)
|
||||
|
||||
m = re.search('(?P<loss>\d+(\.\d+)?)% packet loss', stdout)
|
||||
m = re.search(r'(?P<loss>\d+(\.\d+)?)% packet loss', stdout)
|
||||
if m:
|
||||
loss = m.group('loss')
|
||||
else:
|
||||
loss = 'n/a'
|
||||
|
||||
m = re.search('(?P<min>\d+\.\d+)/(?P<avg>\d+\.\d+)/(?P<max>\d+\.\d+)/(?P<mdev>\d+\.\d+)\s+ms', stdout)
|
||||
m = re.search(
|
||||
r'(?P<min>\d+\.\d+)/(?P<avg>\d+\.\d+)/(?P<max>\d+\.\d+)/(?P<mdev>\d+\.\d+)\s+ms', stdout)
|
||||
if m:
|
||||
rtt = (float(m.group('avg')), float(m.group('max')))
|
||||
else:
|
||||
|
@ -185,7 +195,7 @@ class WorkerThread(threading.Thread):
|
|||
return rc, rtt, loss, stdout
|
||||
|
||||
|
||||
class PingerDaemon(object):
|
||||
class PingerDaemon:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
|
@ -222,7 +232,8 @@ class PingerDaemon(object):
|
|||
environment = p['environment']
|
||||
service = p['service']
|
||||
retries = p.get('retries', PING_MAX_RETRIES)
|
||||
self.queue.put((environment, service, target, retries, time.time()))
|
||||
self.queue.put(
|
||||
(environment, service, target, retries, time.time()))
|
||||
|
||||
LOG.debug('Send heartbeat...')
|
||||
try:
|
||||
|
@ -250,5 +261,6 @@ def main():
|
|||
pinger = PingerDaemon()
|
||||
pinger.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name="alerta-pinger",
|
||||
name='alerta-pinger',
|
||||
version='3.3.0',
|
||||
description="Alerta Pinger daemon",
|
||||
license="MIT",
|
||||
author="Nick Satterly",
|
||||
author_email="nick.satterly@theguardian.com",
|
||||
url="http://github.com/alerta/alerta-contrib",
|
||||
description='Alerta Pinger daemon',
|
||||
license='MIT',
|
||||
author='Nick Satterly',
|
||||
author_email='nick.satterly@theguardian.com',
|
||||
url='http://github.com/alerta/alerta-contrib',
|
||||
py_modules=['pinger'],
|
||||
install_requires=[
|
||||
'alerta',
|
||||
|
@ -19,7 +18,7 @@ setup(
|
|||
'alerta-pinger = pinger:main'
|
||||
]
|
||||
},
|
||||
keywords="alerta ping daemon",
|
||||
keywords='alerta ping daemon',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
|
|
|
@ -165,8 +165,8 @@ Snmptrap Format
|
|||
| $A | the hostname corresponding to the contents of the agent-addr field of the PDU, if available, |
|
||||
| | otherwise the contents of the agent-addr field of the PDU (v1 TRAPs only). |
|
||||
| $b | PDU source address (Note: this is not necessarily an IPv4 address) |
|
||||
| $B | PDU source hostname if available, otherwise PDU source address (see note above) |
|
||||
| $N | enterprise string |
|
||||
| $B | PDU source hostname if available, otherwise PDU source address (see note above) |
|
||||
| $N | enterprise string |
|
||||
| $O | oid as name or numbers |
|
||||
| $P | security information from the PDU (community name for v1/v2c, user and context for v3) |
|
||||
| $q | trap sub-type (numeric, in decimal) |
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import datetime
|
||||
import logging
|
||||
import os
|
||||
|
@ -11,11 +10,12 @@ from alertaclient.api import Client
|
|||
__version__ = '5.0.0'
|
||||
|
||||
|
||||
LOG = logging.getLogger("alerta.snmptrap")
|
||||
logging.basicConfig(format="%(asctime)s - %(name)s: %(levelname)s - %(message)s", level=logging.DEBUG)
|
||||
LOG = logging.getLogger('alerta.snmptrap')
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s: %(levelname)s - %(message)s', level=logging.DEBUG)
|
||||
|
||||
|
||||
class SnmpTrapHandler(object):
|
||||
class SnmpTrapHandler:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
|
@ -37,7 +37,8 @@ class SnmpTrapHandler(object):
|
|||
LOG.debug('unicoded -> %s', data)
|
||||
|
||||
try:
|
||||
resource, event, correlate, trap_version, trapvars = self.parse_snmptrap(data)
|
||||
resource, event, correlate, trap_version, trapvars = self.parse_snmptrap(
|
||||
data)
|
||||
if resource and event:
|
||||
self.api.send_alert(
|
||||
resource=resource,
|
||||
|
@ -50,9 +51,11 @@ class SnmpTrapHandler(object):
|
|||
service=['Network'],
|
||||
text=trapvars['$W'],
|
||||
event_type='snmptrapAlert',
|
||||
attributes={'trapvars': {k.replace('$', '_'): v for k, v in trapvars.items()}},
|
||||
attributes={'trapvars': {
|
||||
k.replace('$', '_'): v for k, v in trapvars.items()}},
|
||||
tags=[trap_version],
|
||||
create_time=datetime.datetime.strptime('%sT%s.000Z' % (trapvars['$x'], trapvars['$X']), '%Y-%m-%dT%H:%M:%S.%fZ'),
|
||||
create_time=datetime.datetime.strptime('{}T{}.000Z'.format(
|
||||
trapvars['$x'], trapvars['$X']), '%Y-%m-%dT%H:%M:%S.%fZ'),
|
||||
raw_data=data
|
||||
)
|
||||
except Exception as e:
|
||||
|
@ -107,7 +110,8 @@ class SnmpTrapHandler(object):
|
|||
trapvars['$' + str(idx)] = value # $n
|
||||
LOG.debug('$%s %s', str(idx), value)
|
||||
|
||||
trapvars['$q'] = trapvars['$q'].lstrip('.') # if numeric, remove leading '.'
|
||||
trapvars['$q'] = trapvars['$q'].lstrip(
|
||||
'.') # if numeric, remove leading '.'
|
||||
trapvars['$#'] = str(idx)
|
||||
|
||||
LOG.debug('varbinds = %s', varbinds)
|
||||
|
@ -132,7 +136,8 @@ class SnmpTrapHandler(object):
|
|||
trapvars['$O'] = 'egpNeighborLoss'
|
||||
elif trapvars['$w'] == '6': # enterpriseSpecific(6)
|
||||
if trapvars['$q'].isdigit(): # XXX - specific trap number was not decoded
|
||||
trapvars['$O'] = '%s.0.%s' % (trapvars['$N'], trapvars['$q'])
|
||||
trapvars['$O'] = '{}.0.{}'.format(
|
||||
trapvars['$N'], trapvars['$q'])
|
||||
else:
|
||||
trapvars['$O'] = trapvars['$q']
|
||||
|
||||
|
@ -161,7 +166,8 @@ class SnmpTrapHandler(object):
|
|||
trapvars['$O'] = trapvars['$2'] # SNMPv2-MIB::snmpTrapOID.0
|
||||
LOG.debug('trapvars = %s', trapvars)
|
||||
|
||||
LOG.info('%s-Trap-PDU %s from %s at %s %s', trap_version, trapvars['$O'], trapvars['$B'], trapvars['$x'], trapvars['$X'])
|
||||
LOG.info('%s-Trap-PDU %s from %s at %s %s', trap_version,
|
||||
trapvars['$O'], trapvars['$B'], trapvars['$x'], trapvars['$X'])
|
||||
|
||||
if trapvars['$B'] != '<UNKNOWN>':
|
||||
resource = trapvars['$B']
|
||||
|
@ -179,16 +185,17 @@ class SnmpTrapHandler(object):
|
|||
|
||||
def main():
|
||||
|
||||
LOG = logging.getLogger("alerta.snmptrap")
|
||||
LOG = logging.getLogger('alerta.snmptrap')
|
||||
|
||||
try:
|
||||
SnmpTrapHandler().run()
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
LOG.info("Exiting alerta SNMP trapper.")
|
||||
LOG.info('Exiting alerta SNMP trapper.')
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
LOG.error(e, exc_info=1)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
main()
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
name="alerta-snmptrap",
|
||||
name='alerta-snmptrap',
|
||||
version='5.0.0',
|
||||
description='Alerta script for SNMP traps',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
@ -21,7 +21,7 @@ setuptools.setup(
|
|||
'alerta-snmptrap = handler:main'
|
||||
]
|
||||
},
|
||||
keywords="alerta snmp trap monitoring",
|
||||
keywords='alerta snmp trap monitoring',
|
||||
classifiers=[
|
||||
'Topic :: System :: Monitoring',
|
||||
]
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
|
||||
from flask.config import Config
|
||||
|
||||
import boto.sqs
|
||||
from boto.sqs.message import RawMessage
|
||||
from boto import exception
|
||||
from flask.config import Config
|
||||
|
||||
LOG = logging.getLogger('alerta.sqs')
|
||||
|
||||
|
@ -20,13 +18,17 @@ config.from_envvar('ALERTA_SVR_CONF_FILE', silent=True)
|
|||
DEFAULT_AWS_REGION = 'eu-west-1'
|
||||
DEFAULT_AWS_SQS_QUEUE = 'alerts'
|
||||
|
||||
AWS_REGION = os.environ.get('AWS_REGION') or config.get('AWS_REGION', DEFAULT_AWS_REGION)
|
||||
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID') or config.get('AWS_ACCESS_KEY_ID')
|
||||
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY') or config.get('AWS_SECRET_ACCESS_KEY')
|
||||
AWS_SQS_QUEUE = os.environ.get('AWS_SQS_QUEUE') or config.get('AWS_SQS_QUEUE', DEFAULT_AWS_SQS_QUEUE)
|
||||
AWS_REGION = os.environ.get('AWS_REGION') or config.get(
|
||||
'AWS_REGION', DEFAULT_AWS_REGION)
|
||||
AWS_ACCESS_KEY_ID = os.environ.get(
|
||||
'AWS_ACCESS_KEY_ID') or config.get('AWS_ACCESS_KEY_ID')
|
||||
AWS_SECRET_ACCESS_KEY = os.environ.get(
|
||||
'AWS_SECRET_ACCESS_KEY') or config.get('AWS_SECRET_ACCESS_KEY')
|
||||
AWS_SQS_QUEUE = os.environ.get('AWS_SQS_QUEUE') or config.get(
|
||||
'AWS_SQS_QUEUE', DEFAULT_AWS_SQS_QUEUE)
|
||||
|
||||
|
||||
class Worker(object):
|
||||
class Worker:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
|
@ -72,5 +74,6 @@ def main():
|
|||
except (SystemExit, KeyboardInterrupt):
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -5,7 +5,7 @@ import setuptools
|
|||
version = '3.3.0'
|
||||
|
||||
setuptools.setup(
|
||||
name="alerta-sqs",
|
||||
name='alerta-sqs',
|
||||
version=version,
|
||||
description='Alerta integration for AWS SQS',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
@ -24,7 +24,7 @@ setuptools.setup(
|
|||
'alerta-sqs = alerta_sqs:main'
|
||||
]
|
||||
},
|
||||
keywords="alerta monitoring amazon sqs",
|
||||
keywords='alerta monitoring amazon sqs',
|
||||
classifiers=[
|
||||
'Topic :: System :: Monitoring',
|
||||
]
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
import json
|
||||
import platform
|
||||
import sys
|
||||
|
||||
from alertaclient.api import Client
|
||||
|
||||
|
||||
class Listener(object):
|
||||
class Listener:
|
||||
|
||||
def wait(self):
|
||||
data = sys.stdin.readline()
|
||||
|
@ -55,7 +55,8 @@ def main():
|
|||
severity = 'normal'
|
||||
try:
|
||||
api.send_alert(
|
||||
resource='%s:%s' % (platform.uname()[1], body['processname']),
|
||||
resource='{}:{}'.format(
|
||||
platform.uname()[1], body['processname']),
|
||||
environment='Production',
|
||||
service=['supervisord'],
|
||||
event=event,
|
||||
|
@ -72,8 +73,10 @@ def main():
|
|||
value='serial=%s' % headers['serial'],
|
||||
severity=severity,
|
||||
origin=headers['server'],
|
||||
text='State changed from %s to %s.' % (body['from_state'], event),
|
||||
raw_data='%s\n\n%s' % (json.dumps(headers), json.dumps(body))
|
||||
text='State changed from {} to {}.'.format(
|
||||
body['from_state'], event),
|
||||
raw_data='{}\n\n{}'.format(
|
||||
json.dumps(headers), json.dumps(body))
|
||||
)
|
||||
except Exception as e:
|
||||
listener.log_stderr(e)
|
||||
|
@ -81,5 +84,6 @@ def main():
|
|||
else:
|
||||
listener.send_cmd('RESULT 2\nOK')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -5,7 +5,7 @@ import setuptools
|
|||
version = '3.5.0'
|
||||
|
||||
setuptools.setup(
|
||||
name="alerta-syslog",
|
||||
name='alerta-syslog',
|
||||
version=version,
|
||||
description='Alerta script for Syslog messages',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
@ -23,7 +23,7 @@ setuptools.setup(
|
|||
'alerta-syslog = syslogfwder:main'
|
||||
]
|
||||
},
|
||||
keywords="alerta syslog monitoring",
|
||||
keywords='alerta syslog monitoring',
|
||||
classifiers=[
|
||||
'Topic :: System :: Monitoring',
|
||||
]
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
import socket
|
||||
import select
|
||||
import re
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import select
|
||||
import socket
|
||||
import sys
|
||||
|
||||
from alertaclient.api import Client
|
||||
|
||||
|
||||
__version__ = '3.5.0'
|
||||
|
||||
SYSLOG_TCP_PORT = int(os.environ.get('SYSLOG_TCP_PORT', 514))
|
||||
|
@ -17,59 +15,60 @@ SYSLOG_UDP_PORT = int(os.environ.get('SYSLOG_UDP_PORT', 514))
|
|||
|
||||
|
||||
SYSLOG_FACILITY_NAMES = [
|
||||
"kern",
|
||||
"user",
|
||||
"mail",
|
||||
"daemon",
|
||||
"auth",
|
||||
"syslog",
|
||||
"lpr",
|
||||
"news",
|
||||
"uucp",
|
||||
"cron",
|
||||
"authpriv",
|
||||
"ftp",
|
||||
"ntp",
|
||||
"audit",
|
||||
"alert",
|
||||
"clock",
|
||||
"local0",
|
||||
"local1",
|
||||
"local2",
|
||||
"local3",
|
||||
"local4",
|
||||
"local5",
|
||||
"local6",
|
||||
"local7"
|
||||
'kern',
|
||||
'user',
|
||||
'mail',
|
||||
'daemon',
|
||||
'auth',
|
||||
'syslog',
|
||||
'lpr',
|
||||
'news',
|
||||
'uucp',
|
||||
'cron',
|
||||
'authpriv',
|
||||
'ftp',
|
||||
'ntp',
|
||||
'audit',
|
||||
'alert',
|
||||
'clock',
|
||||
'local0',
|
||||
'local1',
|
||||
'local2',
|
||||
'local3',
|
||||
'local4',
|
||||
'local5',
|
||||
'local6',
|
||||
'local7'
|
||||
]
|
||||
|
||||
SYSLOG_SEVERITY_NAMES = [
|
||||
"emerg",
|
||||
"alert",
|
||||
"crit",
|
||||
"err",
|
||||
"warning",
|
||||
"notice",
|
||||
"info",
|
||||
"debug"
|
||||
'emerg',
|
||||
'alert',
|
||||
'crit',
|
||||
'err',
|
||||
'warning',
|
||||
'notice',
|
||||
'info',
|
||||
'debug'
|
||||
]
|
||||
|
||||
SYSLOG_SEVERITY_MAP = {
|
||||
"emerg": "critical",
|
||||
"alert": "critical",
|
||||
"crit": "major",
|
||||
"err": "minor",
|
||||
"warning": "warning",
|
||||
"notice": "normal",
|
||||
"info": "informational",
|
||||
"debug": "debug",
|
||||
'emerg': 'critical',
|
||||
'alert': 'critical',
|
||||
'crit': 'major',
|
||||
'err': 'minor',
|
||||
'warning': 'warning',
|
||||
'notice': 'normal',
|
||||
'info': 'informational',
|
||||
'debug': 'debug',
|
||||
}
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
unicode = str
|
||||
|
||||
|
||||
|
||||
def priority_to_code(name):
|
||||
return SYSLOG_SEVERITY_MAP.get(name, "unknown")
|
||||
return SYSLOG_SEVERITY_MAP.get(name, 'unknown')
|
||||
|
||||
|
||||
def decode_priority(priority):
|
||||
|
@ -77,13 +76,15 @@ def decode_priority(priority):
|
|||
level = priority & 7
|
||||
return SYSLOG_FACILITY_NAMES[facility], SYSLOG_SEVERITY_NAMES[level]
|
||||
|
||||
|
||||
LOOP_EVERY = 20 # seconds
|
||||
|
||||
LOG = logging.getLogger("alerta.syslog")
|
||||
logging.basicConfig(format="%(asctime)s - %(name)s: %(levelname)s - %(message)s", level=logging.DEBUG)
|
||||
LOG = logging.getLogger('alerta.syslog')
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s: %(levelname)s - %(message)s', level=logging.DEBUG)
|
||||
|
||||
|
||||
class SyslogDaemon(object):
|
||||
class SyslogDaemon:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
|
@ -94,7 +95,7 @@ class SyslogDaemon(object):
|
|||
try:
|
||||
self.udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.udp.bind(('', SYSLOG_UDP_PORT))
|
||||
except socket.error as e:
|
||||
except OSError as e:
|
||||
LOG.error('Syslog UDP error: %s', e)
|
||||
sys.exit(2)
|
||||
LOG.info('Listening on syslog port %s/udp' % SYSLOG_UDP_PORT)
|
||||
|
@ -106,7 +107,7 @@ class SyslogDaemon(object):
|
|||
self.tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.tcp.bind(('', SYSLOG_TCP_PORT))
|
||||
self.tcp.listen(5)
|
||||
except socket.error as e:
|
||||
except OSError as e:
|
||||
LOG.error('Syslog TCP error: %s', e)
|
||||
sys.exit(2)
|
||||
LOG.info('Listening on syslog port %s/tcp' % SYSLOG_TCP_PORT)
|
||||
|
@ -119,19 +120,22 @@ class SyslogDaemon(object):
|
|||
while not self.shuttingdown:
|
||||
try:
|
||||
LOG.debug('Waiting for syslog messages...')
|
||||
ip, op, rdy = select.select([self.udp, self.tcp], [], [], LOOP_EVERY)
|
||||
ip, op, rdy = select.select(
|
||||
[self.udp, self.tcp], [], [], LOOP_EVERY)
|
||||
if ip:
|
||||
for i in ip:
|
||||
if i == self.udp:
|
||||
data, addr = self.udp.recvfrom(4096)
|
||||
data = unicode(data, 'utf-8', errors='ignore')
|
||||
LOG.debug('Syslog UDP data received from %s: %s', addr, data)
|
||||
LOG.debug(
|
||||
'Syslog UDP data received from %s: %s', addr, data)
|
||||
if i == self.tcp:
|
||||
client, addr = self.tcp.accept()
|
||||
data = client.recv(4096)
|
||||
data = unicode(data, 'utf-8', errors='ignore')
|
||||
client.close()
|
||||
LOG.debug('Syslog TCP data received from %s: %s', addr, data)
|
||||
LOG.debug(
|
||||
'Syslog TCP data received from %s: %s', addr, data)
|
||||
|
||||
alerts = self.parse_syslog(ip=addr[0], data=data)
|
||||
for alert in alerts:
|
||||
|
@ -166,48 +170,54 @@ class SyslogDaemon(object):
|
|||
if not msg or 'last message repeated' in msg:
|
||||
continue
|
||||
|
||||
if re.match('<\d+>1', msg):
|
||||
if re.match(r'<\d+>1', msg):
|
||||
# Parse RFC 5424 compliant message
|
||||
m = re.match(r'<(\d+)>1 (\S+) (\S+) (\S+) (\S+) (\S+) (.*)', msg)
|
||||
m = re.match(
|
||||
r'<(\d+)>1 (\S+) (\S+) (\S+) (\S+) (\S+) (.*)', msg)
|
||||
if m:
|
||||
PRI = int(m.group(1))
|
||||
ISOTIMESTAMP = m.group(2)
|
||||
# ISOTIMESTAMP = m.group(2)
|
||||
HOSTNAME = m.group(3)
|
||||
APPNAME = m.group(4)
|
||||
PROCID = m.group(5)
|
||||
MSGID = m.group(6)
|
||||
TAG = '%s[%s] %s' % (APPNAME, PROCID, MSGID)
|
||||
TAG = '{}[{}] {}'.format(APPNAME, PROCID, MSGID)
|
||||
MSG = m.group(7)
|
||||
LOG.info("Parsed RFC 5424 message OK")
|
||||
LOG.info('Parsed RFC 5424 message OK')
|
||||
else:
|
||||
LOG.error("Could not parse RFC 5424 syslog message: %s", msg)
|
||||
LOG.error(
|
||||
'Could not parse RFC 5424 syslog message: %s', msg)
|
||||
continue
|
||||
|
||||
elif re.match(r'<(\d{1,3})>\S{3}\s', msg):
|
||||
# Parse RFC 3164 compliant message
|
||||
m = re.match(r'<(\d{1,3})>\S{3}\s{1,2}\d?\d \d{2}:\d{2}:\d{2} (\S+)( (\S+):)? (.*)', msg)
|
||||
m = re.match(
|
||||
r'<(\d{1,3})>\S{3}\s{1,2}\d?\d \d{2}:\d{2}:\d{2} (\S+)( (\S+):)? (.*)', msg)
|
||||
if m:
|
||||
PRI = int(m.group(1))
|
||||
HOSTNAME = m.group(2)
|
||||
TAG = m.group(4)
|
||||
MSG = m.group(5)
|
||||
LOG.info("Parsed RFC 3164 message OK")
|
||||
LOG.info('Parsed RFC 3164 message OK')
|
||||
else:
|
||||
LOG.error("Could not parse RFC 3164 syslog message: %s", msg)
|
||||
LOG.error(
|
||||
'Could not parse RFC 3164 syslog message: %s', msg)
|
||||
continue
|
||||
|
||||
elif re.match('<\d+>.*%[A-Z0-9_-]+', msg):
|
||||
elif re.match(r'<\d+>.*%[A-Z0-9_-]+', msg):
|
||||
# Parse Cisco Syslog message
|
||||
m = re.match('<(\d+)>.*(%([A-Z0-9_-]+)):? (.*)', msg)
|
||||
m = re.match(r'<(\d+)>.*(%([A-Z0-9_-]+)):? (.*)', msg)
|
||||
if m:
|
||||
LOG.debug(m.groups())
|
||||
PRI = int(m.group(1))
|
||||
CISCO_SYSLOG = m.group(2)
|
||||
try:
|
||||
CISCO_FACILITY, CISCO_SEVERITY, CISCO_MNEMONIC = m.group(3).split('-')
|
||||
CISCO_FACILITY, CISCO_MNEMONIC = m.group(
|
||||
3).split('-')
|
||||
except ValueError as e:
|
||||
LOG.error('Could not parse Cisco syslog - %s: %s', e, m.group(3))
|
||||
CISCO_FACILITY = CISCO_SEVERITY = CISCO_MNEMONIC = 'na'
|
||||
LOG.error(
|
||||
'Could not parse Cisco syslog - %s: %s', e, m.group(3))
|
||||
CISCO_FACILITY = CISCO_MNEMONIC = 'na'
|
||||
|
||||
TAG = CISCO_MNEMONIC
|
||||
MSG = m.group(4)
|
||||
|
@ -218,27 +228,30 @@ class SyslogDaemon(object):
|
|||
try:
|
||||
socket.inet_aton(ip)
|
||||
(resource, _, _) = socket.gethostbyaddr(ip)
|
||||
except (socket.error, socket.herror):
|
||||
except (OSError, socket.herror):
|
||||
resource = ip
|
||||
|
||||
resource = '%s:%s' % (resource, CISCO_FACILITY)
|
||||
resource = '{}:{}'.format(resource, CISCO_FACILITY)
|
||||
else:
|
||||
LOG.error("Could not parse Cisco syslog message: %s", msg)
|
||||
LOG.error('Could not parse Cisco syslog message: %s', msg)
|
||||
continue
|
||||
|
||||
facility, level = decode_priority(PRI)
|
||||
|
||||
# Defaults
|
||||
event = event or '%s%s' % (facility.capitalize(), level.capitalize())
|
||||
resource = resource or '%s%s' % (HOSTNAME, ':' + TAG if TAG else '')
|
||||
event = event or '{}{}'.format(
|
||||
facility.capitalize(), level.capitalize())
|
||||
resource = resource or '{}{}'.format(
|
||||
HOSTNAME, ':' + TAG if TAG else '')
|
||||
severity = priority_to_code(level)
|
||||
group = 'Syslog'
|
||||
value = level
|
||||
text = MSG
|
||||
environment = 'Production'
|
||||
service = ['Platform']
|
||||
tags = ['%s.%s' % (facility, level)]
|
||||
correlate = ['%s%s' % (facility.capitalize(), s.capitalize()) for s in SYSLOG_SEVERITY_NAMES]
|
||||
tags = ['{}.{}'.format(facility, level)]
|
||||
correlate = ['{}{}'.format(facility.capitalize(), s.capitalize())
|
||||
for s in SYSLOG_SEVERITY_NAMES]
|
||||
raw_data = msg
|
||||
|
||||
syslogAlert = {
|
||||
|
@ -246,7 +259,7 @@ class SyslogDaemon(object):
|
|||
'event': event,
|
||||
'environment': environment,
|
||||
'severity': severity,
|
||||
'correlate':correlate,
|
||||
'correlate': correlate,
|
||||
'service': service,
|
||||
'group': group,
|
||||
'value': value,
|
||||
|
@ -262,14 +275,13 @@ class SyslogDaemon(object):
|
|||
|
||||
def main():
|
||||
|
||||
LOG = logging.getLogger("alerta.syslog")
|
||||
LOG = logging.getLogger('alerta.syslog')
|
||||
|
||||
try:
|
||||
SyslogDaemon().run()
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
LOG.info("Exiting alerta syslog.")
|
||||
LOG.info('Exiting alerta syslog.')
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
LOG.error(e, exc_info=1)
|
||||
sys.exit(1)
|
||||
|
||||
|
|
|
@ -1,24 +1,22 @@
|
|||
|
||||
# ENDPOINT = "http://10.0.2.2:8080"
|
||||
ENDPOINT = "http://localhost:8080"
|
||||
ENDPOINT = 'http://localhost:8080'
|
||||
API_KEY = None
|
||||
|
||||
checks = [
|
||||
{
|
||||
"resource": "www.google.com",
|
||||
"url": "http://www.google.com?q=foo#q=foo",
|
||||
"environment": "Production",
|
||||
"service": ["Google", "Search"],
|
||||
"api_endpoint": "http://localhost:8080",
|
||||
"api_key": None,
|
||||
'resource': 'www.google.com',
|
||||
'url': 'http://www.google.com?q=foo#q=foo',
|
||||
'environment': 'Production',
|
||||
'service': ['Google', 'Search'],
|
||||
'api_endpoint': 'http://localhost:8080',
|
||||
'api_key': None,
|
||||
},
|
||||
{
|
||||
"resource": "guardian-football",
|
||||
"url": "https://www.guardian.co.uk/football",
|
||||
"environment": "Production",
|
||||
"service": ["theguardian.com", "Sport"],
|
||||
"tags": ["football"],
|
||||
"check_ssl": True
|
||||
'resource': 'guardian-football',
|
||||
'url': 'https://www.guardian.co.uk/football',
|
||||
'environment': 'Production',
|
||||
'service': ['theguardian.com', 'Sport'],
|
||||
'tags': ['football'],
|
||||
'check_ssl': True
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import setuptools
|
|||
version = '3.3.0'
|
||||
|
||||
setuptools.setup(
|
||||
name="alerta-urlmon",
|
||||
name='alerta-urlmon',
|
||||
version=version,
|
||||
description='Alerta script for URL monitoring',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
@ -23,7 +23,7 @@ setuptools.setup(
|
|||
'alerta-urlmon = urlmon:main'
|
||||
]
|
||||
},
|
||||
keywords="alerta url monitoring",
|
||||
keywords='alerta url monitoring',
|
||||
classifiers=[
|
||||
'Topic :: System :: Monitoring',
|
||||
]
|
||||
|
|
|
@ -6,17 +6,20 @@ import queue
|
|||
import re
|
||||
import socket
|
||||
import ssl
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from http.server import BaseHTTPRequestHandler as BHRH
|
||||
from urllib.error import URLError # pylint: disable=no-name-in-module
|
||||
from urllib.parse import urlparse # pylint: disable=no-name-in-module
|
||||
from urllib.request import build_opener, ProxyHandler, HTTPBasicAuthHandler, install_opener, Request, urlopen # pylint: disable=no-name-in-module
|
||||
from urllib.request import ( # pylint: disable=no-name-in-module
|
||||
HTTPBasicAuthHandler, ProxyHandler, Request, build_opener, install_opener,
|
||||
urlopen)
|
||||
|
||||
import sys
|
||||
import time
|
||||
import settings
|
||||
from alertaclient.api import Client
|
||||
|
||||
HTTP_RESPONSES = dict([(k, v[0]) for k, v in list(BHRH.responses.items())])
|
||||
HTTP_RESPONSES = {k: v[0] for k, v in list(BHRH.responses.items())}
|
||||
|
||||
# Add missing responses
|
||||
HTTP_RESPONSES[102] = 'Processing'
|
||||
|
@ -43,7 +46,7 @@ _HTTP_ALERTS = [
|
|||
__version__ = '3.3.0'
|
||||
|
||||
LOOP_EVERY = 60 # seconds
|
||||
#TARGET_FILE = 'urlmon.targets' # FIXME -- or settings.py ???
|
||||
# TARGET_FILE = 'urlmon.targets' # FIXME -- or settings.py ???
|
||||
SERVER_THREADS = 20
|
||||
SLOW_WARNING_THRESHOLD = 5000 # ms
|
||||
SLOW_CRITICAL_THRESHOLD = 10000 # ms
|
||||
|
@ -51,10 +54,10 @@ MAX_TIMEOUT = 15000 # ms
|
|||
SSL_DAYS = 30
|
||||
SSL_DAYS_PANIC = 7
|
||||
|
||||
import settings
|
||||
|
||||
LOG = logging.getLogger("alerta.urlmon")
|
||||
logging.basicConfig(format="%(asctime)s - %(name)s: %(levelname)s - %(message)s", level=logging.DEBUG)
|
||||
LOG = logging.getLogger('alerta.urlmon')
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s: %(levelname)s - %(message)s', level=logging.DEBUG)
|
||||
|
||||
|
||||
class WorkerThread(threading.Thread):
|
||||
|
@ -116,42 +119,49 @@ class WorkerThread(threading.Thread):
|
|||
event = 'HttpResponseRegexOK'
|
||||
severity = 'normal'
|
||||
value = '%s (%d)' % (description, status)
|
||||
text = 'HTTP server responded with status code %d that matched "%s" in %dms' % (status, status_regex, rtt)
|
||||
text = 'HTTP server responded with status code %d that matched "%s" in %dms' % (
|
||||
status, status_regex, rtt)
|
||||
else:
|
||||
event = 'HttpResponseRegexError'
|
||||
severity = 'major'
|
||||
value = '%s (%d)' % (description, status)
|
||||
text = 'HTTP server responded with status code %d that failed to match "%s"' % (status, status_regex)
|
||||
text = 'HTTP server responded with status code %d that failed to match "%s"' % (
|
||||
status, status_regex)
|
||||
|
||||
elif 100 <= status <= 199:
|
||||
event = 'HttpInformational'
|
||||
severity = 'normal'
|
||||
value = '%s (%d)' % (description, status)
|
||||
text = 'HTTP server responded with status code %d in %dms' % (status, rtt)
|
||||
text = 'HTTP server responded with status code %d in %dms' % (
|
||||
status, rtt)
|
||||
|
||||
elif 200 <= status <= 299:
|
||||
event = 'HttpResponseOK'
|
||||
severity = 'normal'
|
||||
value = '%s (%d)' % (description, status)
|
||||
text = 'HTTP server responded with status code %d in %dms' % (status, rtt)
|
||||
text = 'HTTP server responded with status code %d in %dms' % (
|
||||
status, rtt)
|
||||
|
||||
elif 300 <= status <= 399:
|
||||
event = 'HttpRedirection'
|
||||
severity = 'minor'
|
||||
value = '%s (%d)' % (description, status)
|
||||
text = 'HTTP server responded with status code %d in %dms' % (status, rtt)
|
||||
text = 'HTTP server responded with status code %d in %dms' % (
|
||||
status, rtt)
|
||||
|
||||
elif 400 <= status <= 499:
|
||||
event = 'HttpClientError'
|
||||
severity = 'minor'
|
||||
value = '%s (%d)' % (description, status)
|
||||
text = 'HTTP server responded with status code %d in %dms' % (status, rtt)
|
||||
text = 'HTTP server responded with status code %d in %dms' % (
|
||||
status, rtt)
|
||||
|
||||
elif 500 <= status <= 599:
|
||||
event = 'HttpServerError'
|
||||
severity = 'major'
|
||||
value = '%s (%d)' % (description, status)
|
||||
text = 'HTTP server responded with status code %d in %dms' % (status, rtt)
|
||||
text = 'HTTP server responded with status code %d in %dms' % (
|
||||
status, rtt)
|
||||
|
||||
else:
|
||||
event = 'HttpUnknownError'
|
||||
|
@ -177,7 +187,8 @@ class WorkerThread(threading.Thread):
|
|||
m = re.search(search_string, line)
|
||||
if m:
|
||||
found = True
|
||||
LOG.debug("Regex: Found %s in %s", search_string, line)
|
||||
LOG.debug('Regex: Found %s in %s',
|
||||
search_string, line)
|
||||
break
|
||||
if not found:
|
||||
event = 'HttpContentError'
|
||||
|
@ -191,9 +202,11 @@ class WorkerThread(threading.Thread):
|
|||
try:
|
||||
body = json.loads(body)
|
||||
except ValueError as e:
|
||||
LOG.error('Could not evaluate rule %s: %s', rule, e)
|
||||
LOG.error(
|
||||
'Could not evaluate rule %s: %s', rule, e)
|
||||
try:
|
||||
eval(rule) # NOTE: assumes request body in variable called 'body'
|
||||
# NOTE: assumes request body in variable called 'body'
|
||||
eval(rule)
|
||||
except (SyntaxError, NameError, ZeroDivisionError) as e:
|
||||
LOG.error('Could not evaluate rule %s: %s', rule, e)
|
||||
except Exception as e:
|
||||
|
@ -205,7 +218,7 @@ class WorkerThread(threading.Thread):
|
|||
value = 'Rule failed'
|
||||
text = 'Website available but rule evaluation failed (%s)' % rule
|
||||
|
||||
LOG.debug("URL: %s, Status: %s (%s), Round-Trip Time: %dms -> %s",
|
||||
LOG.debug('URL: %s, Status: %s (%s), Round-Trip Time: %dms -> %s',
|
||||
check['url'], description, status, rtt, event)
|
||||
|
||||
resource = check['resource']
|
||||
|
@ -215,7 +228,8 @@ class WorkerThread(threading.Thread):
|
|||
service = check['service']
|
||||
text = text
|
||||
tags = check.get('tags', list())
|
||||
threshold_info = "%s : RT > %d RT > %d x %s" % (check['url'], warn_thold, crit_thold, check.get('count', 1))
|
||||
threshold_info = '%s : RT > %d RT > %d x %s' % (
|
||||
check['url'], warn_thold, crit_thold, check.get('count', 1))
|
||||
|
||||
try:
|
||||
local_api.send_alert(
|
||||
|
@ -249,15 +263,18 @@ class WorkerThread(threading.Thread):
|
|||
conn.settimeout(3.0)
|
||||
conn.connect((domain, port))
|
||||
ssl_info = conn.getpeercert()
|
||||
days_left = datetime.datetime.strptime(ssl_info['notAfter'], ssl_date_fmt) - datetime.datetime.utcnow()
|
||||
days_left = datetime.datetime.strptime(
|
||||
ssl_info['notAfter'], ssl_date_fmt) - datetime.datetime.utcnow()
|
||||
if days_left < datetime.timedelta(days=0):
|
||||
text = 'HTTPS cert for %s expired' % check['resource']
|
||||
severity = 'critical'
|
||||
elif days_left < datetime.timedelta(days=SSL_DAYS) and days_left > datetime.timedelta(days=SSL_DAYS_PANIC):
|
||||
text = 'HTTPS cert for %s will expire at %s' % (check['resource'], days_left)
|
||||
text = 'HTTPS cert for {} will expire at {}'.format(
|
||||
check['resource'], days_left)
|
||||
severity = 'major'
|
||||
elif days_left <= datetime.timedelta(days=SSL_DAYS_PANIC):
|
||||
text = 'HTTPS cert for %s will expire at %s' % (check['resource'], days_left)
|
||||
text = 'HTTPS cert for {} will expire at {}'.format(
|
||||
check['resource'], days_left)
|
||||
severity = 'critical'
|
||||
else:
|
||||
severity = 'normal'
|
||||
|
@ -363,7 +380,7 @@ class WorkerThread(threading.Thread):
|
|||
return status, reason, body, rtt
|
||||
|
||||
|
||||
class UrlmonDaemon(object):
|
||||
class UrlmonDaemon:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
|
@ -395,7 +412,8 @@ class UrlmonDaemon(object):
|
|||
LOG.debug('Send heartbeat...')
|
||||
try:
|
||||
origin = '{}/{}'.format('urlmon', platform.uname()[1])
|
||||
self.api.heartbeat(origin, tags=[__version__], timeout=3600)
|
||||
self.api.heartbeat(
|
||||
origin, tags=[__version__], timeout=3600)
|
||||
except Exception as e:
|
||||
LOG.warning('Failed to send heartbeat: %s', e)
|
||||
|
||||
|
@ -431,17 +449,17 @@ class UrlmonDaemon(object):
|
|||
|
||||
def main():
|
||||
|
||||
LOG = logging.getLogger("alerta.urlmon")
|
||||
LOG = logging.getLogger('alerta.urlmon')
|
||||
|
||||
try:
|
||||
UrlmonDaemon().run()
|
||||
except Exception as e:
|
||||
LOG.error(e, exc_info=1)
|
||||
sys.exit(1)
|
||||
except KeyboardInterrupt as e:
|
||||
LOG.warning("Exiting alerta urlmon.")
|
||||
except KeyboardInterrupt:
|
||||
LOG.warning('Exiting alerta urlmon.')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
|
|
@ -33,10 +33,10 @@ server configuration file or the environment variables:
|
|||
The `ALERTOPS_URL` variable is generated during integration configuration within the AlertOps console. This should be added to the server configuration file.
|
||||
|
||||
```python
|
||||
PLUGINS = ['alertops']
|
||||
PLUGINS = ['alertops']
|
||||
ALERTOPS_URL = '' # default="Not configured"
|
||||
```
|
||||
The `DASHBOARD_URL` setting should be configured in the server configuration file to link pushover messages to the Alerta console through the AlertOps webhook:
|
||||
The `DASHBOARD_URL` setting should be configured in the server configuration file to link pushover messages to the Alerta console through the AlertOps webhook:
|
||||
|
||||
```python
|
||||
DASHBOARD_URL = '' # default="Not Set"
|
||||
|
@ -53,11 +53,10 @@ DASHBOARD_URL = 'https://try.alerta.io'
|
|||
References
|
||||
----------
|
||||
|
||||
* AlertOps Integration Docs:
|
||||
* AlertOps Integration Docs:
|
||||
https://help.alertops.com/integrations/pre-built-integration-guides/alerta
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Copyright (c) 2019 AlertOps.
|
||||
|
||||
Copyright (c) 2019 AlertOps.
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
import logging
|
||||
import os
|
||||
import re
|
||||
import requests
|
||||
|
||||
from alerta.exceptions import RejectException
|
||||
import requests
|
||||
# from alerta.exceptions import RejectException
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins')
|
||||
|
||||
|
@ -20,58 +18,57 @@ DASHBOARD_URL = os.environ.get('DASHBOARD_URL') or app.config['DASHBOARD_URL']
|
|||
|
||||
class TriggerEvent(PluginBase):
|
||||
|
||||
|
||||
def pre_receive(self, alert, **kwargs):
|
||||
return alert
|
||||
|
||||
@staticmethod
|
||||
def _event_type(severity):
|
||||
if severity in ['cleared', 'normal', 'ok']:
|
||||
return "close"
|
||||
return 'close'
|
||||
else:
|
||||
return "open"
|
||||
return 'open'
|
||||
|
||||
def post_receive(self, alert, **kwargs):
|
||||
if alert.repeat:
|
||||
return
|
||||
return
|
||||
|
||||
message = '{}: {} alert for {} - {}'.format(
|
||||
alert.environment, alert.severity.capitalize(), ','.join(alert.service), alert.resource)
|
||||
|
||||
message = "%s: %s alert for %s - %s" %( alert.environment, alert.severity.capitalize(), ','.join(alert.service), alert.resource)
|
||||
|
||||
payload = {
|
||||
"source_id": alert.id,
|
||||
"source_status": TriggerEvent._event_type(alert.severity),
|
||||
"description": message,
|
||||
"resource": alert.resource,
|
||||
"source": "alerta",
|
||||
"source_url": '%s/#/alert/%s' % (DASHBOARD_URL, alert.id),
|
||||
"details": alert.get_body(history=False)}
|
||||
'source_id': alert.id,
|
||||
'source_status': TriggerEvent._event_type(alert.severity),
|
||||
'description': message,
|
||||
'resource': alert.resource,
|
||||
'source': 'alerta',
|
||||
'source_url': '{}/#/alert/{}'.format(DASHBOARD_URL, alert.id),
|
||||
'details': alert.get_body(history=False)}
|
||||
LOG.debug('AlertOps Payload: %s', payload)
|
||||
|
||||
try:
|
||||
r = requests.post(ALERTOPS_URL, json=payload, timeout=2)
|
||||
except Exception as e:
|
||||
raise RuntimeError("AlertOps connection error: %s" % e)
|
||||
LOG.debug('AlertOps response: %s - %s' % (r.status_code, r.text))
|
||||
raise RuntimeError('AlertOps connection error: %s' % e)
|
||||
LOG.debug('AlertOps response: {} - {}'.format(r.status_code, r.text))
|
||||
return
|
||||
|
||||
def status_change(self, alert, status, text, **kwargs):
|
||||
if status not in ['ack', 'assign']:
|
||||
return
|
||||
return
|
||||
|
||||
message = "%s: %s alert for %s - %s" %( alert.environment, alert.severity.capitalize(), ','.join(alert.service), alert.resource)
|
||||
message = '{}: {} alert for {} - {}'.format(
|
||||
alert.environment, alert.severity.capitalize(), ','.join(alert.service), alert.resource)
|
||||
payload = {
|
||||
"source_id": alert.id,
|
||||
"source_status": TriggerEvent._event_type(alert.severity),
|
||||
"description": message,
|
||||
"resource": alert.resource,
|
||||
"source": "alerta",
|
||||
"source_url": '%s/#/alert/%s' % (DASHBOARD_URL, alert.id),
|
||||
"details": alert.get_body(history=False)}
|
||||
'source_id': alert.id,
|
||||
'source_status': TriggerEvent._event_type(alert.severity),
|
||||
'description': message,
|
||||
'resource': alert.resource,
|
||||
'source': 'alerta',
|
||||
'source_url': '{}/#/alert/{}'.format(DASHBOARD_URL, alert.id),
|
||||
'details': alert.get_body(history=False)}
|
||||
|
||||
try:
|
||||
r = requests.post(ALERTOPS_URL, json=payload, timeout=2)
|
||||
except Exception as e:
|
||||
raise RuntimeError("AlertOps connection error: %s" % e)
|
||||
LOG.debug('AlertOps response: %s - %s' % (r.status_code, r.text))
|
||||
|
||||
|
||||
raise RuntimeError('AlertOps connection error: %s' % e)
|
||||
LOG.debug('AlertOps response: {} - {}'.format(r.status_code, r.text))
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '1.0.0.1'
|
||||
|
||||
setup(
|
||||
name="alerta-alertops",
|
||||
name='alerta-alertops',
|
||||
version=version,
|
||||
description='Alerta plugin for AlertOps',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from alerta.plugins import PluginBase
|
||||
from kombu import BrokerConnection, Exchange, Producer
|
||||
from kombu.utils.debug import setup_logging
|
||||
|
||||
|
@ -9,7 +9,6 @@ try:
|
|||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.amqp')
|
||||
|
||||
|
@ -17,8 +16,10 @@ DEFAULT_AMQP_URL = 'mongodb://localhost:27017/kombu'
|
|||
DEFAULT_AMQP_TOPIC = 'notify'
|
||||
DEFAULT_AMQP_SEND_ALERT_HISTORY = True
|
||||
|
||||
AMQP_URL = os.environ.get('REDIS_URL') or os.environ.get('AMQP_URL') or app.config.get('AMQP_URL', DEFAULT_AMQP_URL)
|
||||
AMQP_TOPIC = os.environ.get('AMQP_TOPIC') or app.config.get('AMQP_TOPIC', DEFAULT_AMQP_TOPIC)
|
||||
AMQP_URL = os.environ.get('REDIS_URL') or os.environ.get(
|
||||
'AMQP_URL') or app.config.get('AMQP_URL', DEFAULT_AMQP_URL)
|
||||
AMQP_TOPIC = os.environ.get('AMQP_TOPIC') or app.config.get(
|
||||
'AMQP_TOPIC', DEFAULT_AMQP_TOPIC)
|
||||
|
||||
|
||||
class FanoutPublisher(PluginBase):
|
||||
|
@ -37,10 +38,11 @@ class FanoutPublisher(PluginBase):
|
|||
self.channel = self.connection.channel()
|
||||
self.exchange_name = AMQP_TOPIC
|
||||
|
||||
self.exchange = Exchange(name=self.exchange_name, type='fanout', channel=self.channel)
|
||||
self.exchange = Exchange(
|
||||
name=self.exchange_name, type='fanout', channel=self.channel)
|
||||
self.producer = Producer(exchange=self.exchange, channel=self.channel)
|
||||
|
||||
super(FanoutPublisher, self).__init__(name)
|
||||
super().__init__(name)
|
||||
|
||||
LOG.info('Configured fanout publisher on topic "%s"', AMQP_TOPIC)
|
||||
|
||||
|
@ -48,8 +50,10 @@ class FanoutPublisher(PluginBase):
|
|||
return alert
|
||||
|
||||
def post_receive(self, alert, **kwargs):
|
||||
LOG.info('Sending message %s to AMQP topic "%s"', alert.get_id(), AMQP_TOPIC)
|
||||
body = alert.get_body(history=self.get_config('AMQP_SEND_ALERT_HISTORY', default=DEFAULT_AMQP_SEND_ALERT_HISTORY, type=bool, **kwargs))
|
||||
LOG.info('Sending message %s to AMQP topic "%s"',
|
||||
alert.get_id(), AMQP_TOPIC)
|
||||
body = alert.get_body(history=self.get_config(
|
||||
'AMQP_SEND_ALERT_HISTORY', default=DEFAULT_AMQP_SEND_ALERT_HISTORY, type=bool, **kwargs))
|
||||
LOG.debug('Message: %s', body)
|
||||
self.producer.publish(body, declare=[self.exchange], retry=True)
|
||||
|
||||
|
|
|
@ -29,7 +29,8 @@ class FanoutConsumer(ConsumerMixin):
|
|||
)
|
||||
]
|
||||
return [
|
||||
Consumer(queues=queues, accept=['json'], callbacks=[self.on_message])
|
||||
Consumer(queues=queues, accept=[
|
||||
'json'], callbacks=[self.on_message])
|
||||
]
|
||||
|
||||
def on_message(self, body, message):
|
||||
|
@ -39,6 +40,7 @@ class FanoutConsumer(ConsumerMixin):
|
|||
print(str(e))
|
||||
message.ack()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from kombu.utils.debug import setup_logging
|
||||
setup_logging(loglevel='DEBUG', loggers=[''])
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.4.1'
|
||||
|
||||
setup(
|
||||
name="alerta-amqp",
|
||||
name='alerta-amqp',
|
||||
version=version,
|
||||
description='Alerta plugin for AMQP messaging',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
import cachetclient.cachet as cachet
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
import cachetclient.cachet as cachet
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.cachet')
|
||||
|
||||
CACHET_API_URL = os.environ.get('CACHET_API_URL') or app.config['CACHET_API_URL']
|
||||
CACHET_API_TOKEN = os.environ.get('CACHET_API_TOKEN') or app.config['CACHET_API_TOKEN']
|
||||
CACHET_SSL_VERIFY = True if (os.environ.get('CACHET_SSL_VERIFY') == 'True' or app.config.get('CACHET_SSL_VERIFY', False)) else False
|
||||
CACHET_API_URL = os.environ.get(
|
||||
'CACHET_API_URL') or app.config['CACHET_API_URL']
|
||||
CACHET_API_TOKEN = os.environ.get(
|
||||
'CACHET_API_TOKEN') or app.config['CACHET_API_TOKEN']
|
||||
CACHET_SSL_VERIFY = True if (os.environ.get(
|
||||
'CACHET_SSL_VERIFY') == 'True' or app.config.get('CACHET_SSL_VERIFY', False)) else False
|
||||
|
||||
|
||||
STATUS_MAP = {
|
||||
|
@ -25,13 +28,15 @@ STATUS_MAP = {
|
|||
'closed': 4 # Fixed
|
||||
}
|
||||
|
||||
|
||||
class CachetIncident(PluginBase):
|
||||
|
||||
def __init__(self, name=None):
|
||||
|
||||
self.incidents = cachet.Incidents(endpoint=CACHET_API_URL, api_token=CACHET_API_TOKEN, verify=CACHET_SSL_VERIFY)
|
||||
self.incidents = cachet.Incidents(
|
||||
endpoint=CACHET_API_URL, api_token=CACHET_API_TOKEN, verify=CACHET_SSL_VERIFY)
|
||||
|
||||
super(CachetIncident, self).__init__(name)
|
||||
super().__init__(name)
|
||||
|
||||
def pre_receive(self, alert):
|
||||
return alert
|
||||
|
@ -42,14 +47,16 @@ class CachetIncident(PluginBase):
|
|||
status = STATUS_MAP[alert.status]
|
||||
message = alert.text
|
||||
|
||||
r = json.loads(self.incidents.get(name=name, message=message, status=status))
|
||||
r = json.loads(self.incidents.get(
|
||||
name=name, message=message, status=status))
|
||||
if r['meta']['pagination']['count']:
|
||||
return
|
||||
|
||||
try:
|
||||
r = json.loads(self.incidents.post(name=name, message=message, status=status, visible=True))
|
||||
r = json.loads(self.incidents.post(
|
||||
name=name, message=message, status=status, visible=True))
|
||||
except Exception as e:
|
||||
raise RuntimeError("Cachet: ERROR - %s", e)
|
||||
raise RuntimeError('Cachet: ERROR - %s', e)
|
||||
|
||||
LOG.debug('Cachet: %s', r)
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.0.1'
|
||||
|
||||
setup(
|
||||
name="alerta-cachet",
|
||||
name='alerta-cachet',
|
||||
version=version,
|
||||
description='Alerta plugin for Cachet status page',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import os
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
|
@ -19,12 +19,17 @@ class DebugTracing(PluginBase):
|
|||
DEBUG = self.get_config('DEBUG', default=False, type=bool, **kwargs)
|
||||
LOG.info('DEBUG=%s' % DEBUG)
|
||||
|
||||
BOOL_VAR = self.get_config('BOOL_VAR', default=False, type=bool, **kwargs)
|
||||
BOOL_VAR = self.get_config(
|
||||
'BOOL_VAR', default=False, type=bool, **kwargs)
|
||||
INT_VAR = self.get_config('INT_VAR', default=0, type=int, **kwargs)
|
||||
FLOAT_VAR = self.get_config('FLOAT_VAR', default=0.1, type=float, **kwargs)
|
||||
LIST_VAR = self.get_config('LIST_VAR', default=['default', 'list'], type=list, **kwargs)
|
||||
STR_VAR = self.get_config('STR_VAR', default='default-string', type=str, **kwargs)
|
||||
DICT_VAR = self.get_config('DICT_VAR', default={'default': 'dict'}, type=json.loads, **kwargs)
|
||||
FLOAT_VAR = self.get_config(
|
||||
'FLOAT_VAR', default=0.1, type=float, **kwargs)
|
||||
LIST_VAR = self.get_config(
|
||||
'LIST_VAR', default=['default', 'list'], type=list, **kwargs)
|
||||
STR_VAR = self.get_config(
|
||||
'STR_VAR', default='default-string', type=str, **kwargs)
|
||||
DICT_VAR = self.get_config(
|
||||
'DICT_VAR', default={'default': 'dict'}, type=json.loads, **kwargs)
|
||||
|
||||
LOG.debug('BOOL_VAR=%s' % BOOL_VAR)
|
||||
LOG.debug('INT_VAR=%s' % INT_VAR)
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '7.0.0'
|
||||
|
||||
setup(
|
||||
name="alerta-debug",
|
||||
name='alerta-debug',
|
||||
version=version,
|
||||
description='Alerta plugin for debug & tracing',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -52,6 +52,3 @@ DING_WEBHOOK_URL = 'https://oapi.dingtalk.com/robot/send?access_token=fc89e66e'
|
|||
WEBHOOK_MATCHERS = [ {"regex":"proxy[\\d+]", "webhook":"https://oapi.dingtalk.com/robot/send?access_token=f9216e240af"} ]
|
||||
DASHBOARD_URL = 'https://try.alerta.io'
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,24 +1,22 @@
|
|||
from dingtalkchatbot.chatbot import DingtalkChatbot
|
||||
import time
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
import os
|
||||
|
||||
from alerta.plugins import PluginBase
|
||||
from dingtalkchatbot.chatbot import DingtalkChatbot
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.ding')
|
||||
|
||||
|
||||
DING_WEBHOOK_URL = os.environ.get('DING_WEBHOOK_URL') or app.config.get('DING_WEBHOOK_URL')
|
||||
DASHBOARD_URL = os.environ.get('DASHBOARD_URL') or app.config.get('DASHBOARD_URL', '')
|
||||
|
||||
DING_WEBHOOK_URL = os.environ.get(
|
||||
'DING_WEBHOOK_URL') or app.config.get('DING_WEBHOOK_URL')
|
||||
DASHBOARD_URL = os.environ.get(
|
||||
'DASHBOARD_URL') or app.config.get('DASHBOARD_URL', '')
|
||||
|
||||
|
||||
class ServiceIntegration(PluginBase):
|
||||
|
@ -30,9 +28,8 @@ class ServiceIntegration(PluginBase):
|
|||
def pre_receive(self, alert):
|
||||
return alert
|
||||
|
||||
|
||||
def _prepare_payload(self, alert):
|
||||
return "{}** **{}**\n`{}` ```{}```".format(
|
||||
return '{}** **{}**\n`{}` ```{}```'.format(
|
||||
alert.severity,
|
||||
alert.environment,
|
||||
alert.event,
|
||||
|
@ -40,8 +37,6 @@ class ServiceIntegration(PluginBase):
|
|||
)
|
||||
LOG.debug('DingTalk: %s', alert)
|
||||
|
||||
|
||||
|
||||
def post_receive(self, alert):
|
||||
if alert.repeat:
|
||||
return
|
||||
|
@ -50,11 +45,7 @@ class ServiceIntegration(PluginBase):
|
|||
message = self._prepare_payload(alert)
|
||||
LOG.debug('DingTalk: %s', message)
|
||||
ding.send_text(msg='Received Alert {}'.format(message))
|
||||
#xiaoding.send_text(msg='next alert {}'.format(service_name_str))
|
||||
# xiaoding.send_text(msg='next alert {}'.format(service_name_str))
|
||||
|
||||
|
||||
|
||||
def status_change(self, alert, status, text):
|
||||
return
|
||||
|
||||
|
||||
|
|
|
@ -5,4 +5,4 @@ __version__ = '1.3.0'
|
|||
__author__ = 'devin'
|
||||
__author_email__ = '1324556701@qq.com'
|
||||
__license__ = 'MIT'
|
||||
__cake__ = u'\u2728 \U0001f370 \u2728'
|
||||
__cake__ = '\u2728 \U0001f370 \u2728'
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
#!/usr/bin/env python
|
||||
# _*_ coding:utf-8 _*_
|
||||
# create time: 07/01/2018 11:35
|
||||
__author__ = 'Devin -- http://zhangchuzhao.site'
|
||||
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
import time
|
||||
|
||||
import requests
|
||||
|
||||
try:
|
||||
JSONDecodeError = json.decoder.JSONDecodeError
|
||||
except AttributeError:
|
||||
|
@ -34,16 +35,17 @@ def is_not_null_and_blank_str(content):
|
|||
return False
|
||||
|
||||
|
||||
class DingtalkChatbot(object):
|
||||
class DingtalkChatbot:
|
||||
"""
|
||||
钉钉群自定义机器人(每个机器人每分钟最多发送20条),支持文本(text)、连接(link)、markdown三种消息类型!
|
||||
"""
|
||||
|
||||
def __init__(self, webhook):
|
||||
"""
|
||||
机器人初始化
|
||||
:param webhook: 钉钉群自定义机器人webhook地址
|
||||
"""
|
||||
super(DingtalkChatbot, self).__init__()
|
||||
super().__init__()
|
||||
self.headers = {'Content-Type': 'application/json; charset=utf-8'}
|
||||
self.webhook = webhook
|
||||
self.times = 0
|
||||
|
@ -58,23 +60,23 @@ class DingtalkChatbot(object):
|
|||
:param at_dingtalk_ids: 被@人的dingtalkId(可选)
|
||||
:return: 返回消息发送结果
|
||||
"""
|
||||
data = {"msgtype": "text", "at": {}}
|
||||
data = {'msgtype': 'text', 'at': {}}
|
||||
if is_not_null_and_blank_str(msg):
|
||||
data["text"] = {"content": msg}
|
||||
data['text'] = {'content': msg}
|
||||
else:
|
||||
logging.error("text类型,消息内容不能为空!")
|
||||
raise ValueError("text类型,消息内容不能为空!")
|
||||
logging.error('text类型,消息内容不能为空!')
|
||||
raise ValueError('text类型,消息内容不能为空!')
|
||||
|
||||
if is_at_all:
|
||||
data["at"]["isAtAll"] = is_at_all
|
||||
data['at']['isAtAll'] = is_at_all
|
||||
|
||||
if at_mobiles:
|
||||
at_mobiles = list(map(str, at_mobiles))
|
||||
data["at"]["atMobiles"] = at_mobiles
|
||||
data['at']['atMobiles'] = at_mobiles
|
||||
|
||||
if at_dingtalk_ids:
|
||||
at_dingtalk_ids = list(map(str, at_dingtalk_ids))
|
||||
data["at"]["atDingtalkIds"] = at_dingtalk_ids
|
||||
data['at']['atDingtalkIds'] = at_dingtalk_ids
|
||||
|
||||
logging.debug('text类型:%s' % data)
|
||||
return self.post(data)
|
||||
|
@ -87,16 +89,16 @@ class DingtalkChatbot(object):
|
|||
"""
|
||||
if is_not_null_and_blank_str(pic_url):
|
||||
data = {
|
||||
"msgtype": "image",
|
||||
"image": {
|
||||
"picURL": pic_url
|
||||
'msgtype': 'image',
|
||||
'image': {
|
||||
'picURL': pic_url
|
||||
}
|
||||
}
|
||||
logging.debug('image类型:%s' % data)
|
||||
return self.post(data)
|
||||
else:
|
||||
logging.error("image类型中图片链接不能为空!")
|
||||
raise ValueError("image类型中图片链接不能为空!")
|
||||
logging.error('image类型中图片链接不能为空!')
|
||||
raise ValueError('image类型中图片链接不能为空!')
|
||||
|
||||
def send_link(self, title, text, message_url, pic_url=''):
|
||||
"""
|
||||
|
@ -110,19 +112,19 @@ class DingtalkChatbot(object):
|
|||
"""
|
||||
if is_not_null_and_blank_str(title) and is_not_null_and_blank_str(text) and is_not_null_and_blank_str(message_url):
|
||||
data = {
|
||||
"msgtype": "link",
|
||||
"link": {
|
||||
"text": text,
|
||||
"title": title,
|
||||
"picUrl": pic_url,
|
||||
"messageUrl": message_url
|
||||
}
|
||||
'msgtype': 'link',
|
||||
'link': {
|
||||
'text': text,
|
||||
'title': title,
|
||||
'picUrl': pic_url,
|
||||
'messageUrl': message_url
|
||||
}
|
||||
}
|
||||
logging.debug('link类型:%s' % data)
|
||||
return self.post(data)
|
||||
else:
|
||||
logging.error("link类型中消息标题或内容或链接不能为空!")
|
||||
raise ValueError("link类型中消息标题或内容或链接不能为空!")
|
||||
logging.error('link类型中消息标题或内容或链接不能为空!')
|
||||
raise ValueError('link类型中消息标题或内容或链接不能为空!')
|
||||
|
||||
def send_markdown(self, title, text, is_at_all=False, at_mobiles=[], at_dingtalk_ids=[]):
|
||||
"""
|
||||
|
@ -136,29 +138,29 @@ class DingtalkChatbot(object):
|
|||
"""
|
||||
if is_not_null_and_blank_str(title) and is_not_null_and_blank_str(text):
|
||||
data = {
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"title": title,
|
||||
"text": text
|
||||
'msgtype': 'markdown',
|
||||
'markdown': {
|
||||
'title': title,
|
||||
'text': text
|
||||
},
|
||||
"at": {}
|
||||
'at': {}
|
||||
}
|
||||
if is_at_all:
|
||||
data["at"]["isAtAll"] = is_at_all
|
||||
data['at']['isAtAll'] = is_at_all
|
||||
|
||||
if at_mobiles:
|
||||
at_mobiles = list(map(str, at_mobiles))
|
||||
data["at"]["atMobiles"] = at_mobiles
|
||||
data['at']['atMobiles'] = at_mobiles
|
||||
|
||||
if at_dingtalk_ids:
|
||||
at_dingtalk_ids = list(map(str, at_dingtalk_ids))
|
||||
data["at"]["atDingtalkIds"] = at_dingtalk_ids
|
||||
data['at']['atDingtalkIds'] = at_dingtalk_ids
|
||||
|
||||
logging.debug("markdown类型:%s" % data)
|
||||
logging.debug('markdown类型:%s' % data)
|
||||
return self.post(data)
|
||||
else:
|
||||
logging.error("markdown类型中消息标题或内容不能为空!")
|
||||
raise ValueError("markdown类型中消息标题或内容不能为空!")
|
||||
logging.error('markdown类型中消息标题或内容不能为空!')
|
||||
raise ValueError('markdown类型中消息标题或内容不能为空!')
|
||||
|
||||
def send_action_card(self, action_card):
|
||||
"""
|
||||
|
@ -168,11 +170,11 @@ class DingtalkChatbot(object):
|
|||
"""
|
||||
if isinstance(action_card, ActionCard):
|
||||
data = action_card.get_data()
|
||||
logging.debug("ActionCard类型:%s" % data)
|
||||
logging.debug('ActionCard类型:%s' % data)
|
||||
return self.post(data)
|
||||
else:
|
||||
logging.error("ActionCard类型:传入的实例类型不正确!")
|
||||
raise TypeError("ActionCard类型:传入的实例类型不正确!")
|
||||
logging.error('ActionCard类型:传入的实例类型不正确!')
|
||||
raise TypeError('ActionCard类型:传入的实例类型不正确!')
|
||||
|
||||
def send_feed_card(self, links):
|
||||
"""
|
||||
|
@ -187,8 +189,8 @@ class DingtalkChatbot(object):
|
|||
if link_data_list:
|
||||
# 兼容:1、传入FeedLink或CardItem实例列表;2、传入数据字典列表;
|
||||
links = link_data_list
|
||||
data = {"msgtype": "feedCard", "feedCard": {"links": links}}
|
||||
logging.debug("FeedCard类型:%s" % data)
|
||||
data = {'msgtype': 'feedCard', 'feedCard': {'links': links}}
|
||||
logging.debug('FeedCard类型:%s' % data)
|
||||
return self.post(data)
|
||||
|
||||
def post(self, data):
|
||||
|
@ -206,38 +208,44 @@ class DingtalkChatbot(object):
|
|||
|
||||
post_data = json.dumps(data)
|
||||
try:
|
||||
response = requests.post(self.webhook, headers=self.headers, data=post_data)
|
||||
response = requests.post(
|
||||
self.webhook, headers=self.headers, data=post_data)
|
||||
except requests.exceptions.HTTPError as exc:
|
||||
logging.error("消息发送失败, HTTP error: %d, reason: %s" % (exc.response.status_code, exc.response.reason))
|
||||
logging.error('消息发送失败, HTTP error: %d, reason: %s' %
|
||||
(exc.response.status_code, exc.response.reason))
|
||||
raise
|
||||
except requests.exceptions.ConnectionError:
|
||||
logging.error("消息发送失败,HTTP connection error!")
|
||||
logging.error('消息发送失败,HTTP connection error!')
|
||||
raise
|
||||
except requests.exceptions.Timeout:
|
||||
logging.error("消息发送失败,Timeout error!")
|
||||
logging.error('消息发送失败,Timeout error!')
|
||||
raise
|
||||
except requests.exceptions.RequestException:
|
||||
logging.error("消息发送失败, Request Exception!")
|
||||
logging.error('消息发送失败, Request Exception!')
|
||||
raise
|
||||
else:
|
||||
try:
|
||||
result = response.json()
|
||||
except JSONDecodeError:
|
||||
logging.error("服务器响应异常,状态码:%s,响应内容:%s" % (response.status_code, response.text))
|
||||
logging.error('服务器响应异常,状态码:%s,响应内容:%s' %
|
||||
(response.status_code, response.text))
|
||||
return {'errcode': 500, 'errmsg': '服务器响应异常'}
|
||||
else:
|
||||
logging.debug('发送结果:%s' % result)
|
||||
if result['errcode']:
|
||||
error_data = {"msgtype": "text", "text": {"content": "钉钉机器人消息发送失败,原因:%s" % result['errmsg']}, "at": {"isAtAll": True}}
|
||||
logging.error("消息发送失败,自动通知:%s" % error_data)
|
||||
requests.post(self.webhook, headers=self.headers, data=json.dumps(error_data))
|
||||
error_data = {'msgtype': 'text', 'text': {
|
||||
'content': '钉钉机器人消息发送失败,原因:%s' % result['errmsg']}, 'at': {'isAtAll': True}}
|
||||
logging.error('消息发送失败,自动通知:%s' % error_data)
|
||||
requests.post(self.webhook, headers=self.headers,
|
||||
data=json.dumps(error_data))
|
||||
return result
|
||||
|
||||
|
||||
class ActionCard(object):
|
||||
class ActionCard:
|
||||
"""
|
||||
ActionCard类型消息格式(整体跳转、独立跳转)
|
||||
"""
|
||||
|
||||
def __init__(self, title, text, btns, btn_orientation=0, hide_avatar=0):
|
||||
"""
|
||||
ActionCard初始化
|
||||
|
@ -247,7 +255,7 @@ class ActionCard(object):
|
|||
:param btn_orientation: 0:按钮竖直排列,1:按钮横向排列(可选)
|
||||
:param hide_avatar: 0:正常发消息者头像,1:隐藏发消息者头像(可选)
|
||||
"""
|
||||
super(ActionCard, self).__init__()
|
||||
super().__init__()
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.btn_orientation = btn_orientation
|
||||
|
@ -269,39 +277,40 @@ class ActionCard(object):
|
|||
if len(self.btns) == 1:
|
||||
# 整体跳转ActionCard类型
|
||||
data = {
|
||||
"msgtype": "actionCard",
|
||||
"actionCard": {
|
||||
"title": self.title,
|
||||
"text": self.text,
|
||||
"hideAvatar": self.hide_avatar,
|
||||
"btnOrientation": self.btn_orientation,
|
||||
"singleTitle": self.btns[0]["title"],
|
||||
"singleURL": self.btns[0]["actionURL"]
|
||||
}
|
||||
'msgtype': 'actionCard',
|
||||
'actionCard': {
|
||||
'title': self.title,
|
||||
'text': self.text,
|
||||
'hideAvatar': self.hide_avatar,
|
||||
'btnOrientation': self.btn_orientation,
|
||||
'singleTitle': self.btns[0]['title'],
|
||||
'singleURL': self.btns[0]['actionURL']
|
||||
}
|
||||
}
|
||||
return data
|
||||
else:
|
||||
# 独立跳转ActionCard类型
|
||||
data = {
|
||||
"msgtype": "actionCard",
|
||||
"actionCard": {
|
||||
"title": self.title,
|
||||
"text": self.text,
|
||||
"hideAvatar": self.hide_avatar,
|
||||
"btnOrientation": self.btn_orientation,
|
||||
"btns": self.btns
|
||||
'msgtype': 'actionCard',
|
||||
'actionCard': {
|
||||
'title': self.title,
|
||||
'text': self.text,
|
||||
'hideAvatar': self.hide_avatar,
|
||||
'btnOrientation': self.btn_orientation,
|
||||
'btns': self.btns
|
||||
}
|
||||
}
|
||||
return data
|
||||
else:
|
||||
logging.error("ActionCard类型,消息标题或内容或按钮数量不能为空!")
|
||||
raise ValueError("ActionCard类型,消息标题或内容或按钮数量不能为空!")
|
||||
logging.error('ActionCard类型,消息标题或内容或按钮数量不能为空!')
|
||||
raise ValueError('ActionCard类型,消息标题或内容或按钮数量不能为空!')
|
||||
|
||||
|
||||
class FeedLink(object):
|
||||
class FeedLink:
|
||||
"""
|
||||
FeedCard类型单条消息格式
|
||||
"""
|
||||
|
||||
def __init__(self, title, message_url, pic_url):
|
||||
"""
|
||||
初始化单条消息文本
|
||||
|
@ -309,7 +318,7 @@ class FeedLink(object):
|
|||
:param message_url: 点击单条信息后触发的URL
|
||||
:param pic_url: 点击单条消息后面图片触发的URL
|
||||
"""
|
||||
super(FeedLink, self).__init__()
|
||||
super().__init__()
|
||||
self.title = title
|
||||
self.message_url = message_url
|
||||
self.pic_url = pic_url
|
||||
|
@ -321,17 +330,17 @@ class FeedLink(object):
|
|||
"""
|
||||
if is_not_null_and_blank_str(self.title) and is_not_null_and_blank_str(self.message_url) and is_not_null_and_blank_str(self.pic_url):
|
||||
data = {
|
||||
"title": self.title,
|
||||
"messageURL": self.message_url,
|
||||
"picURL": self.pic_url
|
||||
'title': self.title,
|
||||
'messageURL': self.message_url,
|
||||
'picURL': self.pic_url
|
||||
}
|
||||
return data
|
||||
else:
|
||||
logging.error("FeedCard类型单条消息文本、消息链接、图片链接不能为空!")
|
||||
raise ValueError("FeedCard类型单条消息文本、消息链接、图片链接不能为空!")
|
||||
logging.error('FeedCard类型单条消息文本、消息链接、图片链接不能为空!')
|
||||
raise ValueError('FeedCard类型单条消息文本、消息链接、图片链接不能为空!')
|
||||
|
||||
|
||||
class CardItem(object):
|
||||
class CardItem:
|
||||
"""
|
||||
ActionCard和FeedCard消息类型中的子控件
|
||||
"""
|
||||
|
@ -343,7 +352,7 @@ class CardItem(object):
|
|||
@param url: 点击子控件时触发的URL
|
||||
@param pic_url: FeedCard的图片地址,ActionCard时不需要,故默认为None
|
||||
"""
|
||||
super(CardItem, self).__init__()
|
||||
super().__init__()
|
||||
self.title = title
|
||||
self.url = url
|
||||
self.pic_url = pic_url
|
||||
|
@ -356,24 +365,25 @@ class CardItem(object):
|
|||
if is_not_null_and_blank_str(self.pic_url) and is_not_null_and_blank_str(self.title) and is_not_null_and_blank_str(self.url):
|
||||
# FeedCard类型
|
||||
data = {
|
||||
"title": self.title,
|
||||
"messageURL": self.url,
|
||||
"picURL": self.pic_url
|
||||
'title': self.title,
|
||||
'messageURL': self.url,
|
||||
'picURL': self.pic_url
|
||||
}
|
||||
return data
|
||||
elif is_not_null_and_blank_str(self.title) and is_not_null_and_blank_str(self.url):
|
||||
# ActionCard类型
|
||||
data = {
|
||||
"title": self.title,
|
||||
"actionURL": self.url
|
||||
'title': self.title,
|
||||
'actionURL': self.url
|
||||
}
|
||||
return data
|
||||
else:
|
||||
logging.error("CardItem是ActionCard的子控件时,title、url不能为空;是FeedCard的子控件时,title、url、pic_url不能为空!")
|
||||
raise ValueError("CardItem是ActionCard的子控件时,title、url不能为空;是FeedCard的子控件时,title、url、pic_url不能为空!")
|
||||
logging.error(
|
||||
'CardItem是ActionCard的子控件时,title、url不能为空;是FeedCard的子控件时,title、url、pic_url不能为空!')
|
||||
raise ValueError(
|
||||
'CardItem是ActionCard的子控件时,title、url不能为空;是FeedCard的子控件时,title、url、pic_url不能为空!')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
#!/usr/bin/env python
|
||||
# _*_ coding:utf-8 _*_
|
||||
# create time: 16/01/2018 14:37
|
||||
import unittest
|
||||
|
||||
from dingtalkchatbot.chatbot import DingtalkChatbot, is_not_null_and_blank_str, ActionCard, FeedLink, CardItem
|
||||
from dingtalkchatbot.chatbot import (ActionCard, CardItem, DingtalkChatbot,
|
||||
FeedLink, is_not_null_and_blank_str)
|
||||
|
||||
__author__ = 'Devin -- http://zhangchuzhao.site'
|
||||
|
||||
|
@ -31,26 +31,28 @@ class TestDingtalkChatbot(unittest.TestCase):
|
|||
|
||||
def test_send_image(self):
|
||||
"""测试发送表情图片消息函数"""
|
||||
result = self.xiaoding.send_image(pic_url='http://uc-test-manage-00.umlife.net/jenkins/pic/flake8.png')
|
||||
result = self.xiaoding.send_image(
|
||||
pic_url='http://uc-test-manage-00.umlife.net/jenkins/pic/flake8.png')
|
||||
self.assertEqual(result['errcode'], 0)
|
||||
|
||||
def test_send_link(self):
|
||||
"""测试发送链接消息函数"""
|
||||
result = self.xiaoding.send_link(title='万万没想到,某小璐竟然...', text='故事是这样子的...', message_url='http://www.kwongwah.com.my/?p=454748", pic_url="https://pbs.twimg.com/media/CEwj7EDWgAE5eIF.jpg')
|
||||
result = self.xiaoding.send_link(title='万万没想到,某小璐竟然...', text='故事是这样子的...',
|
||||
message_url='http://www.kwongwah.com.my/?p=454748", pic_url="https://pbs.twimg.com/media/CEwj7EDWgAE5eIF.jpg')
|
||||
self.assertEqual(result['errcode'], 0)
|
||||
|
||||
def test_send_markdown(self):
|
||||
"""测试发送Markdown格式消息函数"""
|
||||
result = self.xiaoding.send_markdown(title='氧气文字', text='#### 广州天气\n'
|
||||
'> 9度,西北风1级,空气良89,相对温度73%\n\n'
|
||||
'> \n'
|
||||
'> ###### 10点20分发布 [天气](http://www.thinkpage.cn/) \n',
|
||||
'> 9度,西北风1级,空气良89,相对温度73%\n\n'
|
||||
'> \n'
|
||||
'> ###### 10点20分发布 [天气](http://www.thinkpage.cn/) \n',
|
||||
is_at_all=True)
|
||||
self.assertEqual(result['errcode'], 0)
|
||||
|
||||
def test_send_actioncard(self):
|
||||
"""测试发送整体跳转ActionCard消息功能(CardItem新API)"""
|
||||
btns1 = [CardItem(title="查看详情", url="https://www.dingtalk.com/")]
|
||||
btns1 = [CardItem(title='查看详情', url='https://www.dingtalk.com/')]
|
||||
actioncard1 = ActionCard(title='万万没想到,竟然...',
|
||||
text=' \n### 故事是这样子的...',
|
||||
btns=btns1,
|
||||
|
@ -60,7 +62,8 @@ class TestDingtalkChatbot(unittest.TestCase):
|
|||
self.assertEqual(result['errcode'], 0)
|
||||
|
||||
"""测试发送单独跳转ActionCard消息功能"""
|
||||
btns2 = [CardItem(title="支持", url="https://www.dingtalk.com/"), CardItem(title="反对", url="http://www.back china.com/news/2018/01/11/537468.html")]
|
||||
btns2 = [CardItem(title='支持', url='https://www.dingtalk.com/'), CardItem(
|
||||
title='反对', url='http://www.back china.com/news/2018/01/11/537468.html')]
|
||||
actioncard2 = ActionCard(title='万万没想到,竟然...',
|
||||
text=' \n### 故事是这样子的...',
|
||||
btns=btns2,
|
||||
|
@ -71,7 +74,7 @@ class TestDingtalkChatbot(unittest.TestCase):
|
|||
|
||||
def test_send_actioncard_old_api(self):
|
||||
"""测试发送整体跳转ActionCard消息功能(数据列表btns旧API)"""
|
||||
btns1 = [{"title": "查看详情", "actionURL": "https://www.dingtalk.com/"}]
|
||||
btns1 = [{'title': '查看详情', 'actionURL': 'https://www.dingtalk.com/'}]
|
||||
actioncard1 = ActionCard(title='万万没想到,竟然...',
|
||||
text=' \n### 故事是这样子的...',
|
||||
btns=btns1,
|
||||
|
@ -81,8 +84,8 @@ class TestDingtalkChatbot(unittest.TestCase):
|
|||
self.assertEqual(result['errcode'], 0)
|
||||
|
||||
"""测试发送单独跳转ActionCard消息功能"""
|
||||
btns2 = [{"title": "支持", "actionURL": "https://www.dingtalk.com/"},
|
||||
{"title": "反对", "actionURL": "http://www.back china.com/news/2018/01/11/537468.html"}]
|
||||
btns2 = [{'title': '支持', 'actionURL': 'https://www.dingtalk.com/'},
|
||||
{'title': '反对', 'actionURL': 'http://www.back china.com/news/2018/01/11/537468.html'}]
|
||||
actioncard2 = ActionCard(title='万万没想到,竟然...',
|
||||
text=' \n### 故事是这样子的...',
|
||||
btns=btns2,
|
||||
|
@ -93,18 +96,24 @@ class TestDingtalkChatbot(unittest.TestCase):
|
|||
|
||||
def test_send_feedcard(self):
|
||||
"""测试发送FeedCard类型消息功能(CardItem新API)"""
|
||||
carditem1 = CardItem(title="氧气美女", url="https://www.dingtalk.com/", pic_url="https://unzippedtv.com/wp-content/uploads/sites/28/2016/02/asian.jpg")
|
||||
carditem2 = CardItem(title="氧眼美女", url="https://www.dingtalk.com/", pic_url="https://unzippedtv.com/wp-content/uploads/sites/28/2016/02/asian.jpg")
|
||||
carditem3 = CardItem(title="氧神美女", url="https://www.dingtalk.com/", pic_url="https://unzippedtv.com/wp-content/uploads/sites/28/2016/02/asian.jpg")
|
||||
carditem1 = CardItem(title='氧气美女', url='https://www.dingtalk.com/',
|
||||
pic_url='https://unzippedtv.com/wp-content/uploads/sites/28/2016/02/asian.jpg')
|
||||
carditem2 = CardItem(title='氧眼美女', url='https://www.dingtalk.com/',
|
||||
pic_url='https://unzippedtv.com/wp-content/uploads/sites/28/2016/02/asian.jpg')
|
||||
carditem3 = CardItem(title='氧神美女', url='https://www.dingtalk.com/',
|
||||
pic_url='https://unzippedtv.com/wp-content/uploads/sites/28/2016/02/asian.jpg')
|
||||
cards = [carditem1, carditem2, carditem3]
|
||||
result = self.xiaoding.send_feed_card(cards)
|
||||
self.assertEqual(result['errcode'], 0)
|
||||
|
||||
def test_send_feedcard_old_api(self):
|
||||
"""测试发送FeedCard类型消息功能(FeedLink旧API)"""
|
||||
feedlink1 = FeedLink(title="氧气美女", message_url="https://www.dingtalk.com/", pic_url="https://unzippedtv.com/wp-content/uploads/sites/28/2016/02/asian.jpg")
|
||||
feedlink2 = FeedLink(title="氧眼美女", message_url="https://www.dingtalk.com/", pic_url="https://unzippedtv.com/wp-content/uploads/sites/28/2016/02/asian.jpg")
|
||||
feedlink3 = FeedLink(title="氧神美女", message_url="https://www.dingtalk.com/", pic_url="https://unzippedtv.com/wp-content/uploads/sites/28/2016/02/asian.jpg")
|
||||
feedlink1 = FeedLink(title='氧气美女', message_url='https://www.dingtalk.com/',
|
||||
pic_url='https://unzippedtv.com/wp-content/uploads/sites/28/2016/02/asian.jpg')
|
||||
feedlink2 = FeedLink(title='氧眼美女', message_url='https://www.dingtalk.com/',
|
||||
pic_url='https://unzippedtv.com/wp-content/uploads/sites/28/2016/02/asian.jpg')
|
||||
feedlink3 = FeedLink(title='氧神美女', message_url='https://www.dingtalk.com/',
|
||||
pic_url='https://unzippedtv.com/wp-content/uploads/sites/28/2016/02/asian.jpg')
|
||||
links = [feedlink1, feedlink2, feedlink3]
|
||||
result = self.xiaoding.send_feed_card(links)
|
||||
self.assertEqual(result['errcode'], 0)
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
#!/usr/bin/env python
|
||||
# _*_ coding:utf-8 _*_
|
||||
# create time: 15/01/2018 17:08
|
||||
__author__ = 'Devin -- http://zhangchuzhao.site'
|
||||
import json
|
||||
import json # noqa: F401
|
||||
import logging
|
||||
import requests
|
||||
from dingtalkchatbot.chatbot import DingtalkChatbot, ActionCard, FeedLink, CardItem
|
||||
|
||||
import requests # noqa: F401
|
||||
from dingtalkchatbot.chatbot import FeedLink # noqa: F401
|
||||
from dingtalkchatbot.chatbot import ActionCard, CardItem, DingtalkChatbot
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
|
@ -13,7 +14,8 @@ if __name__ == '__main__':
|
|||
# *************************************这里填写自己钉钉群自定义机器人的token*****************************************
|
||||
webhook = 'https://oapi.dingtalk.com/robot/send?access_token=52d9034cc78680bc0d4ba6a65748e77fa7b96ee43d57b96116910606f7863d59'
|
||||
# 用户手机号列表
|
||||
at_mobiles = ['*************************这里填写需要提醒的用户的手机号码,字符串或数字都可以****************************']
|
||||
at_mobiles = [
|
||||
'*************************这里填写需要提醒的用户的手机号码,字符串或数字都可以****************************']
|
||||
# 初始化机器人小丁
|
||||
xiaoding = DingtalkChatbot(webhook)
|
||||
# text
|
||||
|
@ -21,10 +23,12 @@ if __name__ == '__main__':
|
|||
xiaoding.send_text(msg='我就是小丁,小丁就是我!', at_mobiles=at_mobiles)
|
||||
|
||||
# image表情
|
||||
xiaoding.send_image(pic_url='http://uc-test-manage-00.umlife.net/jenkins/pic/flake8.png')
|
||||
xiaoding.send_image(
|
||||
pic_url='http://uc-test-manage-00.umlife.net/jenkins/pic/flake8.png')
|
||||
|
||||
# link
|
||||
xiaoding.send_link(title='万万没想到,某小璐竟然...', text='故事是这样子的...', message_url='http://www.kwongwah.com.my/?p=454748", pic_url="https://pbs.twimg.com/media/CEwj7EDWgAE5eIF.jpg')
|
||||
xiaoding.send_link(title='万万没想到,某小璐竟然...', text='故事是这样子的...',
|
||||
message_url='http://www.kwongwah.com.my/?p=454748", pic_url="https://pbs.twimg.com/media/CEwj7EDWgAE5eIF.jpg')
|
||||
|
||||
# markdown
|
||||
# 1、提醒所有人
|
||||
|
@ -41,7 +45,7 @@ if __name__ == '__main__':
|
|||
at_mobiles=at_mobiles)
|
||||
|
||||
# 整体跳转ActionCard
|
||||
btns1 = [CardItem(title="查看详情", url="https://www.dingtalk.com/")]
|
||||
btns1 = [CardItem(title='查看详情', url='https://www.dingtalk.com/')]
|
||||
actioncard1 = ActionCard(title='万万没想到,竟然...',
|
||||
text=' \n### 故事是这样子的...',
|
||||
btns=btns1,
|
||||
|
@ -51,7 +55,8 @@ if __name__ == '__main__':
|
|||
|
||||
# 单独跳转ActionCard
|
||||
# 1、两个按钮选择
|
||||
btns2 = [CardItem(title="支持", url="https://www.dingtalk.com/"), CardItem(title="反对", url="https://www.dingtalk.com/")]
|
||||
btns2 = [CardItem(title='支持', url='https://www.dingtalk.com/'),
|
||||
CardItem(title='反对', url='https://www.dingtalk.com/')]
|
||||
actioncard2 = ActionCard(title='万万没想到,竟然...',
|
||||
text=' \n### 故事是这样子的...',
|
||||
btns=btns2,
|
||||
|
@ -59,7 +64,8 @@ if __name__ == '__main__':
|
|||
hide_avatar=1)
|
||||
xiaoding.send_action_card(actioncard2)
|
||||
# 2、三个按钮选择
|
||||
btns3 = [CardItem(title="支持", url="https://www.dingtalk.com/"), CardItem(title="中立", url="https://www.dingtalk.com/"), CardItem(title="反对", url="https://www.dingtalk.com/")]
|
||||
btns3 = [CardItem(title='支持', url='https://www.dingtalk.com/'), CardItem(title='中立',
|
||||
url='https://www.dingtalk.com/'), CardItem(title='反对', url='https://www.dingtalk.com/')]
|
||||
actioncard3 = ActionCard(title='万万没想到,竟然...',
|
||||
text=' \n### 故事是这样子的...',
|
||||
btns=btns3,
|
||||
|
@ -68,9 +74,12 @@ if __name__ == '__main__':
|
|||
xiaoding.send_action_card(actioncard3)
|
||||
|
||||
# FeedCard类型
|
||||
card1 = CardItem(title="氧气美女", url="https://www.dingtalk.com/", pic_url="https://unzippedtv.com/wp-content/uploads/sites/28/2016/02/asian.jpg")
|
||||
card2 = CardItem(title="氧眼美女", url="https://www.dingtalk.com/", pic_url="https://unzippedtv.com/wp-content/uploads/sites/28/2016/02/asian.jpg")
|
||||
card3 = CardItem(title="氧神美女", url="https://www.dingtalk.com/", pic_url="https://unzippedtv.com/wp-content/uploads/sites/28/2016/02/asian.jpg")
|
||||
card1 = CardItem(title='氧气美女', url='https://www.dingtalk.com/',
|
||||
pic_url='https://unzippedtv.com/wp-content/uploads/sites/28/2016/02/asian.jpg')
|
||||
card2 = CardItem(title='氧眼美女', url='https://www.dingtalk.com/',
|
||||
pic_url='https://unzippedtv.com/wp-content/uploads/sites/28/2016/02/asian.jpg')
|
||||
card3 = CardItem(title='氧神美女', url='https://www.dingtalk.com/',
|
||||
pic_url='https://unzippedtv.com/wp-content/uploads/sites/28/2016/02/asian.jpg')
|
||||
cards = [card1, card2, card3]
|
||||
xiaoding.send_feed_card(cards)
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '0.0.1'
|
||||
|
||||
setup(
|
||||
name="alerta-ding",
|
||||
name='alerta-ding',
|
||||
version=version,
|
||||
description='Example Alerta plugin for test alerts',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
@ -21,4 +21,3 @@ setup(
|
|||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import logging
|
||||
|
||||
from alerta.plugins import PluginBase
|
||||
|
@ -12,7 +11,7 @@ class EnhanceAlert(PluginBase):
|
|||
|
||||
def pre_receive(self, alert):
|
||||
|
||||
LOG.info("Enhancing alert...")
|
||||
LOG.info('Enhancing alert...')
|
||||
|
||||
# Set "isOutOfHours" flag for later use by notification plugins
|
||||
dayOfWeek = alert.create_time.strftime('%a')
|
||||
|
@ -23,7 +22,8 @@ class EnhanceAlert(PluginBase):
|
|||
alert.attributes['isOutOfHours'] = False
|
||||
|
||||
# Add link to Run Book based on event name
|
||||
alert.attributes['runBookUrl'] = '%s/%s' % (RUNBOOK_URL, alert.event.replace(' ', '-'))
|
||||
alert.attributes['runBookUrl'] = '{}/{}'.format(
|
||||
RUNBOOK_URL, alert.event.replace(' ', '-'))
|
||||
|
||||
return alert
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.3.3'
|
||||
|
||||
setup(
|
||||
name="alerta-enhance",
|
||||
name='alerta-enhance',
|
||||
version=version,
|
||||
description='Alerta plugin for alert enhancement',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import os
|
||||
import logging
|
||||
import os
|
||||
|
||||
from alerta.plugins import PluginBase
|
||||
from alertaclient.api import Client
|
||||
|
@ -18,6 +18,7 @@ FORWARD_API_KEY = os.environ.get(
|
|||
FORWARD_MAX_LENGTH = os.environ.get(
|
||||
'FORWARD_MAX_LENGTH') or app.config.get('FORWARD_MAX_LENGTH') or 3
|
||||
|
||||
|
||||
class ForwardAlert(PluginBase):
|
||||
|
||||
def pre_receive(self, alert):
|
||||
|
@ -28,7 +29,7 @@ class ForwardAlert(PluginBase):
|
|||
return
|
||||
client = Client(endpoint=FORWARD_URL, key=FORWARD_API_KEY)
|
||||
fw_count = alert.attributes.get('fw_count') or 0
|
||||
fw_count = fw_count+1
|
||||
fw_count = fw_count + 1
|
||||
if fw_count >= FORWARD_MAX_LENGTH:
|
||||
LOG.debug('alert discarded by cycle overflow')
|
||||
return
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.0.0'
|
||||
|
||||
setup(
|
||||
name="alerta-forward",
|
||||
name='alerta-forward',
|
||||
version=version,
|
||||
description='Alerta plugin for forwarding alerts',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
|
||||
import logging
|
||||
import os
|
||||
|
||||
import requests
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.geoip')
|
||||
|
||||
GEOIP_URL = os.environ.get('GEOIP_URL') or app.config.get('GEOIP_URL', 'http://api.ipstack.com')
|
||||
GEOIP_ACCESS_KEY = os.environ.get('GEOIP_ACCESS_KEY') or app.config.get('GEOIP_ACCESS_KEY', None)
|
||||
GEOIP_URL = os.environ.get('GEOIP_URL') or app.config.get(
|
||||
'GEOIP_URL', 'http://api.ipstack.com')
|
||||
GEOIP_ACCESS_KEY = os.environ.get(
|
||||
'GEOIP_ACCESS_KEY') or app.config.get('GEOIP_ACCESS_KEY', None)
|
||||
|
||||
|
||||
class GeoLocation(PluginBase):
|
||||
|
@ -20,15 +22,18 @@ class GeoLocation(PluginBase):
|
|||
def pre_receive(self, alert):
|
||||
|
||||
ip_addr = alert.attributes['ip'].split(', ')[0]
|
||||
LOG.debug("GeoIP lookup for IP: %s", ip_addr)
|
||||
LOG.debug('GeoIP lookup for IP: %s', ip_addr)
|
||||
|
||||
if 'ip' in alert.attributes:
|
||||
url = '%s/%s?access_key=%s' % (GEOIP_URL, ip_addr, GEOIP_ACCESS_KEY)
|
||||
url = '{}/{}?access_key={}'.format(GEOIP_URL,
|
||||
ip_addr, GEOIP_ACCESS_KEY)
|
||||
else:
|
||||
LOG.warning("IP address must be included as an alert attribute.")
|
||||
raise RuntimeWarning("IP address must be included as an alert attribute.")
|
||||
LOG.warning('IP address must be included as an alert attribute.')
|
||||
raise RuntimeWarning(
|
||||
'IP address must be included as an alert attribute.')
|
||||
|
||||
r = requests.get(url, headers={'Content-type': 'application/json'}, timeout=2)
|
||||
r = requests.get(
|
||||
url, headers={'Content-type': 'application/json'}, timeout=2)
|
||||
try:
|
||||
geoip_lookup = r.json()
|
||||
alert.attributes = {
|
||||
|
@ -36,8 +41,8 @@ class GeoLocation(PluginBase):
|
|||
'country': geoip_lookup['location'].get('country_flag_emoji')
|
||||
}
|
||||
except Exception as e:
|
||||
LOG.error("GeoIP lookup failed: %s" % str(e))
|
||||
raise RuntimeError("GeoIP lookup failed: %s" % str(e))
|
||||
LOG.error('GeoIP lookup failed: %s' % str(e))
|
||||
raise RuntimeError('GeoIP lookup failed: %s' % str(e))
|
||||
|
||||
return alert
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.4.0'
|
||||
|
||||
setup(
|
||||
name="alerta-geoip",
|
||||
name='alerta-geoip',
|
||||
version=version,
|
||||
description='Alerta plugin for GeoIP Lookup',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -1,42 +1,44 @@
|
|||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
import requests
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.goalert')
|
||||
LOG.info('Initializing')
|
||||
|
||||
GOALERT_URL = os.environ.get('GOALERT_URL') or app.config['GOALERT_URL']
|
||||
GOALERT_URL = os.environ.get('GOALERT_URL') or app.config['GOALERT_URL']
|
||||
GOALERT_TOKEN = os.environ.get('GOALERT_TOKEN') or app.config['GOALERT_TOKEN']
|
||||
GOALERT_VERIFY = os.environ.get('GOALERT_VERIFY') or app.config['GOALERT_VERIFY']
|
||||
GOALERT_VERIFY = os.environ.get(
|
||||
'GOALERT_VERIFY') or app.config['GOALERT_VERIFY']
|
||||
DASHBOARD_URL = os.environ.get('DASHBOARD_URL') or app.config['DASHBOARD_URL']
|
||||
|
||||
|
||||
class TriggerEvent(PluginBase):
|
||||
|
||||
def goalerts_endpoint(self):
|
||||
return '%s%s' % (GOALERT_URL, '/api/v2/generic/incoming')
|
||||
return '{}{}'.format(GOALERT_URL, '/api/v2/generic/incoming')
|
||||
|
||||
def goalert_close_alert(self, alert, why):
|
||||
closeUrl = self.goalerts_endpoint()
|
||||
|
||||
json = {
|
||||
"token": GOALERT_TOKEN,
|
||||
"dedup": alert.id,
|
||||
"action": "close",
|
||||
"details": why
|
||||
'token': GOALERT_TOKEN,
|
||||
'dedup': alert.id,
|
||||
'action': 'close',
|
||||
'details': why
|
||||
}
|
||||
LOG.debug('goalert close %s: %s %s' % (why, alert.id, closeUrl))
|
||||
LOG.debug('goalert close {}: {} {}'.format(why, alert.id, closeUrl))
|
||||
|
||||
try:
|
||||
r = requests.post(closeUrl, json, timeout=2, verify=GOALERT_VERIFY)
|
||||
except Exception as e:
|
||||
raise RuntimeError("goalert connection error: %s" % e)
|
||||
raise RuntimeError('goalert connection error: %s' % e)
|
||||
return r
|
||||
|
||||
# def goalert_ack_alert(self, alert, why):
|
||||
|
@ -54,7 +56,8 @@ class TriggerEvent(PluginBase):
|
|||
return alert
|
||||
|
||||
def post_receive(self, alert):
|
||||
LOG.debug('Alert receive %s: %s' % (alert.id, alert.get_body(history=False)))
|
||||
LOG.debug('Alert receive %s: %s' %
|
||||
(alert.id, alert.get_body(history=False)))
|
||||
if alert.repeat:
|
||||
LOG.debug('Alert repeating; ignored')
|
||||
return
|
||||
|
@ -62,28 +65,31 @@ class TriggerEvent(PluginBase):
|
|||
if (alert.severity in ['cleared', 'normal', 'ok']) or (alert.status == 'closed'):
|
||||
r = self.goalert_close_alert(alert, 'CREATE-CLOSE')
|
||||
else:
|
||||
body = alert.get_body(history=False)
|
||||
# body = alert.get_body(history=False)
|
||||
json = {
|
||||
"token": GOALERT_TOKEN,
|
||||
"dedup": alert.id,
|
||||
"summary": alert.resource,
|
||||
"details": "[%s] %s: %s" % (alert.environment, alert.resource, alert.value)
|
||||
'token': GOALERT_TOKEN,
|
||||
'dedup': alert.id,
|
||||
'summary': alert.resource,
|
||||
'details': '[{}] {}: {}'.format(alert.environment, alert.resource, alert.value)
|
||||
}
|
||||
LOG.debug('goalert CREATE payload: %s' % json)
|
||||
endpoint = self.goalerts_endpoint()
|
||||
|
||||
try:
|
||||
r = requests.post(endpoint, json, timeout=2, verify=GOALERT_VERIFY)
|
||||
r = requests.post(endpoint, json, timeout=2,
|
||||
verify=GOALERT_VERIFY)
|
||||
except Exception as e:
|
||||
raise RuntimeError("goalert connection error: %s" % e)
|
||||
raise RuntimeError('goalert connection error: %s' % e)
|
||||
|
||||
LOG.debug('goalert response: %s - %s' % (r.status_code, r.text))
|
||||
LOG.debug('goalert response: {} - {}'.format(r.status_code, r.text))
|
||||
|
||||
def status_change(self, alert, status, text):
|
||||
LOG.debug('Alert change %s to %s: %s' % (alert.id, status, alert.get_body(history=False)))
|
||||
LOG.debug('Alert change %s to %s: %s' %
|
||||
(alert.id, status, alert.get_body(history=False)))
|
||||
|
||||
if status not in ['ack', 'assign', 'closed', 'expired']:
|
||||
LOG.debug('Not sending status change to goalert: %s to %s' % (alert.id, status))
|
||||
LOG.debug('Not sending status change to goalert: %s to %s' %
|
||||
(alert.id, status))
|
||||
return
|
||||
|
||||
if status == 'closed':
|
||||
|
@ -93,4 +99,4 @@ class TriggerEvent(PluginBase):
|
|||
# elif status == 'ack':
|
||||
# r = self.goalert_ack_alert(alert, 'STATUS-ACK')
|
||||
|
||||
LOG.debug('goalert response: %s - %s' % (r.status_code, r.text))
|
||||
LOG.debug('goalert response: {} - {}'.format(r.status_code, r.text))
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.0.3'
|
||||
|
||||
setup(
|
||||
name="alerta-goalert",
|
||||
name='alerta-goalert',
|
||||
version=version,
|
||||
description='Alerta plugin for GoAlert',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -1,27 +1,29 @@
|
|||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from alerta.plugins import PluginBase
|
||||
from influxdb import InfluxDBClient
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
from influxdb import InfluxDBClient
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.influxdb')
|
||||
|
||||
# 'influxdb://username:password@localhost:8086/databasename'
|
||||
DEFAULT_INFLUXDB_DSN = 'influxdb://user:pass@localhost:8086/alerta'
|
||||
|
||||
INFLUXDB_DSN = os.environ.get('INFLUXDB_DSN') or app.config.get('INFLUXDB_DSN', DEFAULT_INFLUXDB_DSN)
|
||||
INFLUXDB_DATABASE = os.environ.get('INFLUXDB_DATABASE') or app.config.get('INFLUXDB_DATABASE', None)
|
||||
INFLUXDB_DSN = os.environ.get('INFLUXDB_DSN') or app.config.get(
|
||||
'INFLUXDB_DSN', DEFAULT_INFLUXDB_DSN)
|
||||
INFLUXDB_DATABASE = os.environ.get(
|
||||
'INFLUXDB_DATABASE') or app.config.get('INFLUXDB_DATABASE', None)
|
||||
|
||||
# Specify the name of a measurement to which all alerts will be logged
|
||||
INFLUXDB_MEASUREMENT = os.environ.get('INFLUXDB_MEASUREMENT') or app.config.get('INFLUXDB_MEASUREMENT', 'event')
|
||||
INFLUXDB_MEASUREMENT = os.environ.get(
|
||||
'INFLUXDB_MEASUREMENT') or app.config.get('INFLUXDB_MEASUREMENT', 'event')
|
||||
|
||||
|
||||
class InfluxDBWrite(PluginBase):
|
||||
|
@ -38,7 +40,7 @@ class InfluxDBWrite(PluginBase):
|
|||
except Exception as e:
|
||||
LOG.error('InfluxDB: ERROR - %s' % e)
|
||||
|
||||
super(InfluxDBWrite, self).__init__(name)
|
||||
super().__init__(name)
|
||||
|
||||
def pre_receive(self, alert):
|
||||
return alert
|
||||
|
@ -90,7 +92,7 @@ class InfluxDBWrite(PluginBase):
|
|||
try:
|
||||
self.client.write_points([point], time_precision='ms')
|
||||
except Exception as e:
|
||||
raise RuntimeError("InfluxDB: ERROR - %s" % e)
|
||||
raise RuntimeError('InfluxDB: ERROR - %s' % e)
|
||||
|
||||
def status_change(self, alert, status, text):
|
||||
if status not in ['ack', 'assign']:
|
||||
|
@ -102,4 +104,4 @@ class InfluxDBWrite(PluginBase):
|
|||
try:
|
||||
self.client.write_points([point], time_precision='ms')
|
||||
except Exception as e:
|
||||
raise RuntimeError("InfluxDB: ERROR - %s" % e)
|
||||
raise RuntimeError('InfluxDB: ERROR - %s' % e)
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.4.3'
|
||||
|
||||
setup(
|
||||
name="alerta-influxdb",
|
||||
name='alerta-influxdb',
|
||||
version=version,
|
||||
description='Alerta plugin for InfluxDB',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Jira Plugin
|
||||
Jira Plugin
|
||||
===========
|
||||
|
||||
Creates a task in Jira and adds the Jira Task attribute in alarm. The created attribute is a link to the Jira task and opens in a new tab.
|
||||
|
@ -29,7 +29,7 @@ server configuration file or as environment variables.
|
|||
```python
|
||||
PLUGINS = ['jira']
|
||||
JIRA_PROJECT = '' #project name in jira
|
||||
JIRA_URL = '' #url access to the jira
|
||||
JIRA_URL = '' #url access to the jira
|
||||
JIRA_USER = '' #defined to the according to the jira access data
|
||||
JIRA_PASS = '' #defined to the according to the jira access data
|
||||
```
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import logging
|
||||
import os
|
||||
import http.client
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from base64 import b64encode
|
||||
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
# set plugin logger
|
||||
LOG = logging.getLogger('alerta.plugins.jira')
|
||||
|
@ -19,40 +20,41 @@ JIRA_PROJECT = app.config.get('JIRA_PROJECT') or os.environ.get('JIRA_PROJECT')
|
|||
JIRA_USER = app.config.get('JIRA_USER') or os.environ.get('JIRA_USER')
|
||||
JIRA_PASS = app.config.get('JIRA_PASS') or os.environ.get('JIRA_PASS')
|
||||
|
||||
|
||||
class JiraCreate(PluginBase):
|
||||
|
||||
def _sendjira(self, host, event, value, chart, text, severity):
|
||||
LOG.info('JIRA: Create task ...')
|
||||
userpass = "%s:%s" %(JIRA_USER, JIRA_PASS)
|
||||
userAndPass = b64encode(bytes(userpass, "utf-8")).decode("ascii")
|
||||
userpass = '{}:{}'.format(JIRA_USER, JIRA_PASS)
|
||||
userAndPass = b64encode(bytes(userpass, 'utf-8')).decode('ascii')
|
||||
LOG.debug('JIRA_URL: {}'.format(JIRA_URL))
|
||||
conn = http.client.HTTPSConnection("%s" %(JIRA_URL))
|
||||
|
||||
conn = http.client.HTTPSConnection('%s' % (JIRA_URL))
|
||||
|
||||
payload_dict = {
|
||||
"fields": {
|
||||
"project":
|
||||
'fields': {
|
||||
'project':
|
||||
{
|
||||
"key": "%s" %(JIRA_PROJECT)
|
||||
'key': '%s' % (JIRA_PROJECT)
|
||||
},
|
||||
"summary": "Server %s: alert %s in chart %s - Severity: %s" %(host.upper(), event.upper(), chart.upper(), severity.upper()),
|
||||
"description": "The chart %s INFO: %s. \nVALUE: %s." %(chart, text, value),
|
||||
"issuetype": {
|
||||
"name": "Bug"
|
||||
'summary': 'Server {}: alert {} in chart {} - Severity: {}'.format(host.upper(), event.upper(), chart.upper(), severity.upper()),
|
||||
'description': 'The chart {} INFO: {}. \nVALUE: {}.'.format(chart, text, value),
|
||||
'issuetype': {
|
||||
'name': 'Bug'
|
||||
},
|
||||
}
|
||||
}
|
||||
payload = json.dumps(payload_dict, indent = 4)
|
||||
payload = json.dumps(payload_dict, indent=4)
|
||||
headers = {
|
||||
'Content-Type': "application/json",
|
||||
'Authorization': "Basic %s" % userAndPass
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Basic %s' % userAndPass
|
||||
}
|
||||
|
||||
conn.request("POST", '/rest/api/2/issue/', payload, headers)
|
||||
conn.request('POST', '/rest/api/2/issue/', payload, headers)
|
||||
res = conn.getresponse()
|
||||
data = res.read()
|
||||
data = res.read()
|
||||
data = json.loads(data)
|
||||
return data["key"]
|
||||
|
||||
return data['key']
|
||||
|
||||
# reject or modify an alert before it hits the database
|
||||
def pre_receive(self, alert):
|
||||
return alert
|
||||
|
@ -62,26 +64,28 @@ class JiraCreate(PluginBase):
|
|||
try:
|
||||
# if the alert is critical and don't duplicate, create task in Jira
|
||||
if alert.status not in ['ack', 'closed', 'shelved'] and alert.duplicate_count == 0:
|
||||
LOG.info("Jira: Received an alert")
|
||||
LOG.debug("Jira: ALERT {}".format(alert))
|
||||
LOG.debug("Jira: ID {}".format(alert.id))
|
||||
LOG.debug("Jira: RESOURCE {}".format(alert.resource))
|
||||
LOG.debug("Jira: EVENT {}".format(alert.event))
|
||||
LOG.debug("Jira: SEVERITY {}".format(alert.severity))
|
||||
LOG.debug("Jira: TEXT {}".format(alert.text))
|
||||
LOG.info('Jira: Received an alert')
|
||||
LOG.debug('Jira: ALERT {}'.format(alert))
|
||||
LOG.debug('Jira: ID {}'.format(alert.id))
|
||||
LOG.debug('Jira: RESOURCE {}'.format(alert.resource))
|
||||
LOG.debug('Jira: EVENT {}'.format(alert.event))
|
||||
LOG.debug('Jira: SEVERITY {}'.format(alert.severity))
|
||||
LOG.debug('Jira: TEXT {}'.format(alert.text))
|
||||
|
||||
# get basic info from alert
|
||||
host = alert.resource.split(':')[0]
|
||||
LOG.debug("JIRA: HOST {}".format(host))
|
||||
chart = ".".join(alert.event.split('.')[:-1])
|
||||
LOG.debug("JIRA: CHART {}".format(chart))
|
||||
LOG.debug('JIRA: HOST {}'.format(host))
|
||||
chart = '.'.join(alert.event.split('.')[:-1])
|
||||
LOG.debug('JIRA: CHART {}'.format(chart))
|
||||
event = alert.event.split('.')[-1]
|
||||
LOG.debug("JIRA: EVENT {}".format(event))
|
||||
LOG.debug('JIRA: EVENT {}'.format(event))
|
||||
|
||||
# call the _sendjira and modify de text (discription)
|
||||
task = self._sendjira(host, event, alert.value, chart, alert.text, alert.severity)
|
||||
task_url = "https://" + JIRA_URL + "/browse/" + task
|
||||
href = '<a href="%s" target="_blank">%s</a>' %(task_url, task)
|
||||
task = self._sendjira(
|
||||
host, event, alert.value, chart, alert.text, alert.severity)
|
||||
task_url = 'https://' + JIRA_URL + '/browse/' + task
|
||||
href = '<a href="{}" target="_blank">{}</a>'.format(
|
||||
task_url, task)
|
||||
alert.attributes = {'Jira Task': href}
|
||||
return alert
|
||||
|
||||
|
@ -91,4 +95,4 @@ class JiraCreate(PluginBase):
|
|||
|
||||
# triggered by external status changes, used by integrations
|
||||
def status_change(self, alert, status, text):
|
||||
return
|
||||
return
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '1.0.0'
|
||||
|
||||
setup(
|
||||
name="alerta-jira",
|
||||
name='alerta-jira',
|
||||
version=version,
|
||||
description='Alerta plugin for create tasks in jira',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.logstash')
|
||||
|
@ -16,14 +16,17 @@ LOG = logging.getLogger('alerta.plugins.logstash')
|
|||
DEFAULT_LOGSTASH_HOST = 'localhost'
|
||||
DEFAULT_LOGSTASH_PORT = 6379
|
||||
|
||||
LOGSTASH_HOST = os.environ.get('LOGSTASH_HOST') or app.config.get('LOGSTASH_HOST', DEFAULT_LOGSTASH_HOST)
|
||||
LOGSTASH_PORT = os.environ.get('LOGSTASH_PORT') or app.config.get('LOGSTASH_PORT', DEFAULT_LOGSTASH_PORT)
|
||||
LOGSTASH_HOST = os.environ.get('LOGSTASH_HOST') or app.config.get(
|
||||
'LOGSTASH_HOST', DEFAULT_LOGSTASH_HOST)
|
||||
LOGSTASH_PORT = os.environ.get('LOGSTASH_PORT') or app.config.get(
|
||||
'LOGSTASH_PORT', DEFAULT_LOGSTASH_PORT)
|
||||
|
||||
|
||||
class LogStashOutput(PluginBase):
|
||||
|
||||
def __init__(self, name=None):
|
||||
self.sock = None
|
||||
super(LogStashOutput, self).__init__(name)
|
||||
super().__init__(name)
|
||||
|
||||
def pre_receive(self, alert):
|
||||
return alert
|
||||
|
@ -39,13 +42,14 @@ class LogStashOutput(PluginBase):
|
|||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.connect((LOGSTASH_HOST, logstash_port))
|
||||
except Exception as e:
|
||||
raise RuntimeError("Logstash TCP connection error: %s" % str(e))
|
||||
raise RuntimeError('Logstash TCP connection error: %s' % str(e))
|
||||
|
||||
try:
|
||||
self.sock.send(b"%s\r\n" % json.dumps(alert.get_body(history=False)).encode('utf-8'))
|
||||
self.sock.send(b'%s\r\n' % json.dumps(
|
||||
alert.get_body(history=False)).encode('utf-8'))
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
raise RuntimeError("logstash exception")
|
||||
raise RuntimeError('logstash exception')
|
||||
|
||||
self.sock.close()
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.3.3'
|
||||
|
||||
setup(
|
||||
name="alerta-logstash",
|
||||
name='alerta-logstash',
|
||||
version=version,
|
||||
description='Alerta plugin for ELK logstash',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -1,37 +1,42 @@
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import requests
|
||||
import urllib
|
||||
import json
|
||||
|
||||
import requests
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
|
||||
LOG = logging.getLogger("alerta.plugins.matrix")
|
||||
LOG = logging.getLogger('alerta.plugins.matrix')
|
||||
|
||||
MATRIX_HOMESERVER_URL = [
|
||||
os.environ.get("MATRIX_HOMESERVER") or app.config["MATRIX_HOMESERVER"],
|
||||
"/_matrix/client/r0/rooms/",
|
||||
urllib.parse.quote(os.environ.get("MATRIX_ROOM") or app.config["MATRIX_ROOM"], ":"),
|
||||
"/send/m.room.message"
|
||||
os.environ.get('MATRIX_HOMESERVER') or app.config['MATRIX_HOMESERVER'],
|
||||
'/_matrix/client/r0/rooms/',
|
||||
urllib.parse.quote(os.environ.get('MATRIX_ROOM')
|
||||
or app.config['MATRIX_ROOM'], ':'), # noqa: W503
|
||||
'/send/m.room.message'
|
||||
]
|
||||
MATRIX_ACCESS_TOKEN = os.environ.get("MATRIX_ACCESS_TOKEN") or app.config["MATRIX_ACCESS_TOKEN"]
|
||||
MATRIX_MESSAGE_TYPE = os.environ.get("MATRIX_MESSAGE_TYPE") or app.config.get("MATRIX_MESSAGE_TYPE", "notice")
|
||||
MATRIX_ACCESS_TOKEN = os.environ.get(
|
||||
'MATRIX_ACCESS_TOKEN') or app.config['MATRIX_ACCESS_TOKEN']
|
||||
MATRIX_MESSAGE_TYPE = os.environ.get(
|
||||
'MATRIX_MESSAGE_TYPE') or app.config.get('MATRIX_MESSAGE_TYPE', 'notice')
|
||||
MATRIX_MESSAGE_TYPES = {
|
||||
"notice": "m.notice",
|
||||
"text": "m.text"
|
||||
'notice': 'm.notice',
|
||||
'text': 'm.text'
|
||||
}
|
||||
DASHBOARD_URL = os.environ.get("DASHBOARD_URL") or app.config.get("DASHBOARD_URL", "")
|
||||
DASHBOARD_URL = os.environ.get(
|
||||
'DASHBOARD_URL') or app.config.get('DASHBOARD_URL', '')
|
||||
SEVERITY_ICON = {
|
||||
"critical": "🔴 ",
|
||||
"warning": "⚠️ ",
|
||||
"ok": "✅ ",
|
||||
"cleared": "✅ ",
|
||||
"normal": "✅ ",
|
||||
'critical': '🔴 ',
|
||||
'warning': '⚠️ ',
|
||||
'ok': '✅ ',
|
||||
'cleared': '✅ ',
|
||||
'normal': '✅ ',
|
||||
}
|
||||
|
||||
|
||||
|
@ -44,13 +49,13 @@ class SendMessage(PluginBase):
|
|||
if alert.repeat:
|
||||
return
|
||||
|
||||
severity = SEVERITY_ICON.get(alert.severity, "")
|
||||
severity = SEVERITY_ICON.get(alert.severity, '')
|
||||
|
||||
body = "{}{}: {} alert for {} \n{} - {} - {} \n{} \nDate: {}".format(
|
||||
body = '{}{}: {} alert for {} \n{} - {} - {} \n{} \nDate: {}'.format(
|
||||
severity,
|
||||
alert.environment,
|
||||
alert.severity.capitalize(),
|
||||
",".join(alert.service),
|
||||
','.join(alert.service),
|
||||
alert.resource,
|
||||
alert.event,
|
||||
alert.value,
|
||||
|
@ -62,7 +67,7 @@ class SendMessage(PluginBase):
|
|||
severity,
|
||||
alert.environment,
|
||||
alert.severity.capitalize(),
|
||||
",".join(alert.service),
|
||||
','.join(alert.service),
|
||||
alert.resource,
|
||||
alert.event,
|
||||
alert.value,
|
||||
|
@ -73,25 +78,25 @@ class SendMessage(PluginBase):
|
|||
)
|
||||
|
||||
payload = {
|
||||
"msgtype": MATRIX_MESSAGE_TYPES.get(MATRIX_MESSAGE_TYPE, "m.notice"),
|
||||
"format": "org.matrix.custom.html",
|
||||
"body": body,
|
||||
"formatted_body": formatted_body,
|
||||
'msgtype': MATRIX_MESSAGE_TYPES.get(MATRIX_MESSAGE_TYPE, 'm.notice'),
|
||||
'format': 'org.matrix.custom.html',
|
||||
'body': body,
|
||||
'formatted_body': formatted_body,
|
||||
}
|
||||
|
||||
LOG.debug("Matrix: %s", payload)
|
||||
LOG.debug('Matrix: %s', payload)
|
||||
|
||||
try:
|
||||
r = requests.post(
|
||||
"".join(MATRIX_HOMESERVER_URL),
|
||||
headers={"Authorization": "Bearer " + MATRIX_ACCESS_TOKEN},
|
||||
data=json.dumps(payload).encode("utf-8"),
|
||||
''.join(MATRIX_HOMESERVER_URL),
|
||||
headers={'Authorization': 'Bearer ' + MATRIX_ACCESS_TOKEN},
|
||||
data=json.dumps(payload).encode('utf-8'),
|
||||
timeout=2,
|
||||
)
|
||||
except Exception as e:
|
||||
raise RuntimeError("Matrix: ERROR - %s" % e)
|
||||
raise RuntimeError('Matrix: ERROR - %s' % e)
|
||||
|
||||
LOG.debug("Matrix: %s - %s", r.status_code, r.text)
|
||||
LOG.debug('Matrix: %s - %s', r.status_code, r.text)
|
||||
|
||||
def status_change(self, alert, status, text):
|
||||
return
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = "0.1.0"
|
||||
version = '0.1.0'
|
||||
|
||||
setup(
|
||||
name="alerta-matrix",
|
||||
name='alerta-matrix',
|
||||
version=version,
|
||||
description="Alerta plugin for Matrix",
|
||||
url="https://github.com/alerta/alerta-contrib",
|
||||
license="MIT",
|
||||
author="Magnus Walbeck",
|
||||
author_email="mw@mwalbeck.org",
|
||||
description='Alerta plugin for Matrix',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
license='MIT',
|
||||
author='Magnus Walbeck',
|
||||
author_email='mw@mwalbeck.org',
|
||||
packages=find_packages(),
|
||||
py_modules=["alerta_matrix"],
|
||||
install_requires=["requests"],
|
||||
py_modules=['alerta_matrix'],
|
||||
install_requires=['requests'],
|
||||
include_package_data=True,
|
||||
zip_safe=True,
|
||||
entry_points={"alerta.plugins": ["matrix = alerta_matrix:SendMessage"]},
|
||||
entry_points={'alerta.plugins': ['matrix = alerta_matrix:SendMessage']},
|
||||
)
|
||||
|
|
|
@ -35,7 +35,7 @@ MATTERMOST_USERNAME = # '' default="alerta"
|
|||
Create a new incoming webhook in your Mattermost installation.
|
||||
See https://docs.mattermost.com/developer/webhooks-incoming.html
|
||||
|
||||
Templating
|
||||
Templating
|
||||
----------
|
||||
|
||||
No templating support at this time. Use hardcoded reasonable one.
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import logging
|
||||
import os
|
||||
|
||||
from alerta.plugins import PluginBase
|
||||
from matterhook import Webhook
|
||||
|
||||
try:
|
||||
|
@ -7,7 +9,6 @@ try:
|
|||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.mattermost')
|
||||
|
||||
|
@ -42,7 +43,7 @@ class ServiceIntegration(PluginBase):
|
|||
|
||||
def _prepare_payload(self, alert):
|
||||
LOG.debug('Mattermost: %s', alert)
|
||||
return "{} **{}** **{}**\n`{}` ```{}```".format(
|
||||
return '{} **{}** **{}**\n`{}` ```{}```'.format(
|
||||
self.get_icon(alert.severity),
|
||||
alert.severity,
|
||||
alert.environment,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '1.1.3'
|
||||
|
||||
setup(
|
||||
name="alerta-mattermost",
|
||||
name='alerta-mattermost',
|
||||
version=version,
|
||||
description='Alerta plugin for Mattermost',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -1,31 +1,33 @@
|
|||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import requests
|
||||
|
||||
import pymsteams
|
||||
import requests
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.msteams')
|
||||
|
||||
try:
|
||||
from jinja2 import Template
|
||||
except Exception as e:
|
||||
LOG.error('MS Teams: ERROR - Jinja template error: %s, template functionality will be unavailable', e)
|
||||
LOG.error(
|
||||
'MS Teams: ERROR - Jinja template error: %s, template functionality will be unavailable', e)
|
||||
|
||||
MS_TEAMS_COLORS_MAP = app.config.get('MS_TEAMS_COLORS_MAP', {})
|
||||
MS_TEAMS_DEFAULT_COLORS_MAP = {'security': '000000',
|
||||
'critical': 'D8122A',
|
||||
'major': 'EA680F',
|
||||
'minor': 'FFBE1E',
|
||||
'warning': '1E90FF'}
|
||||
'critical': 'D8122A',
|
||||
'major': 'EA680F',
|
||||
'minor': 'FFBE1E',
|
||||
'warning': '1E90FF'}
|
||||
MS_TEAMS_DEFAULT_COLOR = '00AA5A'
|
||||
MS_TEAMS_DEFAULT_TIMEOUT = 7 # pymsteams http_timeout
|
||||
MS_TEAMS_DEFAULT_TIMEOUT = 7 # pymsteams http_timeout
|
||||
|
||||
|
||||
class SendConnectorCardMessage(PluginBase):
|
||||
|
||||
|
@ -34,12 +36,12 @@ class SendConnectorCardMessage(PluginBase):
|
|||
self._colors = MS_TEAMS_DEFAULT_COLORS_MAP
|
||||
self._colors.update(MS_TEAMS_COLORS_MAP)
|
||||
|
||||
super(SendConnectorCardMessage, self).__init__(name)
|
||||
super().__init__(name)
|
||||
|
||||
def _load_template(self, templateFmt):
|
||||
try:
|
||||
if os.path.exists(templateFmt):
|
||||
with open(templateFmt, 'r') as f:
|
||||
with open(templateFmt) as f:
|
||||
template = Template(f.read())
|
||||
else:
|
||||
template = Template(templateFmt)
|
||||
|
@ -52,19 +54,28 @@ class SendConnectorCardMessage(PluginBase):
|
|||
return alert
|
||||
|
||||
def post_receive(self, alert, **kwargs):
|
||||
MS_TEAMS_WEBHOOK_URL = self.get_config('MS_TEAMS_WEBHOOK_URL', default='', type=str, **kwargs)
|
||||
MS_TEAMS_SUMMARY_FMT = self.get_config('MS_TEAMS_SUMMARY_FMT', default=None, type=str, **kwargs) # Message summary(title) format
|
||||
MS_TEAMS_TEXT_FMT = self.get_config('MS_TEAMS_TEXT_FMT', default=None, type=str, **kwargs) # Message text format
|
||||
MS_TEAMS_PAYLOAD = self.get_config('MS_TEAMS_PAYLOAD', default=None, type=str, **kwargs) # json/Jinja2 MS teams messagecard payload
|
||||
MS_TEAMS_INBOUNDWEBHOOK_URL = self.get_config('MS_TEAMS_INBOUNDWEBHOOK_URL', default=None, type=str, **kwargs) # webhook url for connectorcard actions
|
||||
MS_TEAMS_APIKEY = self.get_config('MS_TEAMS_APIKEY', default=None, type=str, **kwargs) # X-API-Key (needs webhook.write permission)
|
||||
DASHBOARD_URL = self.get_config('DASHBOARD_URL', default='', type=str, **kwargs)
|
||||
MS_TEAMS_WEBHOOK_URL = self.get_config(
|
||||
'MS_TEAMS_WEBHOOK_URL', default='', type=str, **kwargs)
|
||||
MS_TEAMS_SUMMARY_FMT = self.get_config(
|
||||
'MS_TEAMS_SUMMARY_FMT', default=None, type=str, **kwargs) # Message summary(title) format
|
||||
MS_TEAMS_TEXT_FMT = self.get_config(
|
||||
'MS_TEAMS_TEXT_FMT', default=None, type=str, **kwargs) # Message text format
|
||||
# json/Jinja2 MS teams messagecard payload
|
||||
MS_TEAMS_PAYLOAD = self.get_config(
|
||||
'MS_TEAMS_PAYLOAD', default=None, type=str, **kwargs)
|
||||
MS_TEAMS_INBOUNDWEBHOOK_URL = self.get_config(
|
||||
'MS_TEAMS_INBOUNDWEBHOOK_URL', default=None, type=str, **kwargs) # webhook url for connectorcard actions
|
||||
# X-API-Key (needs webhook.write permission)
|
||||
MS_TEAMS_APIKEY = self.get_config(
|
||||
'MS_TEAMS_APIKEY', default=None, type=str, **kwargs)
|
||||
DASHBOARD_URL = self.get_config(
|
||||
'DASHBOARD_URL', default='', type=str, **kwargs)
|
||||
|
||||
if alert.repeat:
|
||||
return
|
||||
|
||||
color = self._colors.get(alert.severity, MS_TEAMS_DEFAULT_COLOR)
|
||||
url = "%s/#/alert/%s" % (DASHBOARD_URL, alert.id)
|
||||
url = '{}/#/alert/{}'.format(DASHBOARD_URL, alert.id)
|
||||
|
||||
template_vars = {
|
||||
'alert': alert,
|
||||
|
@ -75,7 +86,8 @@ class SendConnectorCardMessage(PluginBase):
|
|||
|
||||
if MS_TEAMS_INBOUNDWEBHOOK_URL and MS_TEAMS_APIKEY:
|
||||
# Add X-API-Key header for teams(webhook) HttpPOST actions
|
||||
template_vars['headers'] = '[ {{ "name": "X-API-Key", "value": "{}" }} ]'.format(MS_TEAMS_APIKEY)
|
||||
template_vars['headers'] = '[ {{ "name": "X-API-Key", "value": "{}" }} ]'.format(
|
||||
MS_TEAMS_APIKEY)
|
||||
template_vars['webhook_url'] = MS_TEAMS_INBOUNDWEBHOOK_URL
|
||||
|
||||
if MS_TEAMS_PAYLOAD:
|
||||
|
@ -87,7 +99,8 @@ class SendConnectorCardMessage(PluginBase):
|
|||
# Try to check that we've valid json
|
||||
json.loads(card_json)
|
||||
except Exception as e:
|
||||
LOG.error('MS Teams: ERROR - Template(PAYLOAD) render failed: %s', e)
|
||||
LOG.error(
|
||||
'MS Teams: ERROR - Template(PAYLOAD) render failed: %s', e)
|
||||
return
|
||||
else:
|
||||
# Use pymsteams to format/construct message card
|
||||
|
@ -113,7 +126,8 @@ class SendConnectorCardMessage(PluginBase):
|
|||
try:
|
||||
text = txt_template.render(**template_vars)
|
||||
except Exception as e:
|
||||
LOG.error('MS Teams: ERROR - Template(TEXT_FMT) render failed: %s', e)
|
||||
LOG.error(
|
||||
'MS Teams: ERROR - Template(TEXT_FMT) render failed: %s', e)
|
||||
return
|
||||
else:
|
||||
text = alert.text
|
||||
|
@ -123,19 +137,23 @@ class SendConnectorCardMessage(PluginBase):
|
|||
try:
|
||||
if MS_TEAMS_PAYLOAD:
|
||||
# Use requests.post to send raw json message card
|
||||
LOG.debug("MS Teams sending(json payload) POST to %s", MS_TEAMS_WEBHOOK_URL)
|
||||
r = requests.post(MS_TEAMS_WEBHOOK_URL, data=card_json, timeout=MS_TEAMS_DEFAULT_TIMEOUT)
|
||||
LOG.debug('MS Teams response: %s / %s' % (r.status_code, r.text))
|
||||
LOG.debug('MS Teams sending(json payload) POST to %s',
|
||||
MS_TEAMS_WEBHOOK_URL)
|
||||
r = requests.post(
|
||||
MS_TEAMS_WEBHOOK_URL, data=card_json, timeout=MS_TEAMS_DEFAULT_TIMEOUT)
|
||||
LOG.debug('MS Teams response: %s / %s' %
|
||||
(r.status_code, r.text))
|
||||
else:
|
||||
# Use pymsteams to send card
|
||||
msTeamsMessage = pymsteams.connectorcard(hookurl=MS_TEAMS_WEBHOOK_URL, http_timeout=MS_TEAMS_DEFAULT_TIMEOUT)
|
||||
msTeamsMessage = pymsteams.connectorcard(
|
||||
hookurl=MS_TEAMS_WEBHOOK_URL, http_timeout=MS_TEAMS_DEFAULT_TIMEOUT)
|
||||
msTeamsMessage.title(summary)
|
||||
msTeamsMessage.text(text)
|
||||
msTeamsMessage.addLinkButton("Open in Alerta", url)
|
||||
msTeamsMessage.addLinkButton('Open in Alerta', url)
|
||||
msTeamsMessage.color(color)
|
||||
msTeamsMessage.send()
|
||||
except Exception as e:
|
||||
raise RuntimeError("MS Teams: ERROR - %s", e)
|
||||
raise RuntimeError('MS Teams: ERROR - %s', e)
|
||||
|
||||
def status_change(self, alert, status, text, **kwargs):
|
||||
return
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.2.1'
|
||||
|
||||
setup(
|
||||
name="alerta-msteams",
|
||||
name='alerta-msteams',
|
||||
version=version,
|
||||
description='Alerta plugin for Microsoft Teams',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import logging
|
||||
|
||||
from alerta.plugins import PluginBase
|
||||
|
@ -10,10 +9,10 @@ class NormaliseAlert(PluginBase):
|
|||
|
||||
def pre_receive(self, alert):
|
||||
|
||||
LOG.info("Normalising alert...")
|
||||
LOG.info('Normalising alert...')
|
||||
|
||||
# prepend severity to alert text
|
||||
alert.text = '%s: %s' % (alert.severity.upper(), alert.text)
|
||||
alert.text = '{}: {}'.format(alert.severity.upper(), alert.text)
|
||||
|
||||
# supply different default values if missing
|
||||
if not alert.group or alert.group == 'Misc':
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.3.1'
|
||||
|
||||
setup(
|
||||
name="alerta-normalise",
|
||||
name='alerta-normalise',
|
||||
version=version,
|
||||
description='Alerta plugin for alert normalisation',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -53,4 +53,4 @@ References
|
|||
License
|
||||
-------
|
||||
|
||||
Copyright (c) 2017 Anton Delitsch. Available under the MIT License.
|
||||
Copyright (c) 2017 Anton Delitsch. Available under the MIT License.
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
|
||||
import datetime
|
||||
import logging
|
||||
import os
|
||||
|
||||
from alerta.plugins import PluginBase
|
||||
from op5 import OP5
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.app import db
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.op5')
|
||||
|
||||
DEFAULT_OP5_API_URL = 'https://demo.op5.com/api'
|
||||
|
||||
OP5_API_URL = os.environ.get('OP5_API_URL') or app.config.get('OP5_API_URL', None)
|
||||
OP5_API_USERNAME = os.environ.get('OP5_API_USERNAME') or app.config.get('OP5_API_USERNAME', '')
|
||||
OP5_API_PASSWORD = os.environ.get('OP5_API_PASSWORD') or app.config.get('OP5_API_PASSWORD', '')
|
||||
OP5_API_URL = os.environ.get(
|
||||
'OP5_API_URL') or app.config.get('OP5_API_URL', None)
|
||||
OP5_API_USERNAME = os.environ.get(
|
||||
'OP5_API_USERNAME') or app.config.get('OP5_API_USERNAME', '')
|
||||
OP5_API_PASSWORD = os.environ.get(
|
||||
'OP5_API_PASSWORD') or app.config.get('OP5_API_PASSWORD', '')
|
||||
|
||||
|
||||
class OP5Acknowledge(PluginBase):
|
||||
|
@ -37,14 +38,16 @@ class OP5Acknowledge(PluginBase):
|
|||
return
|
||||
|
||||
if status == 'ack':
|
||||
json_data = {"sticky":"1","notify":"0","persistent":"1","comment": text}
|
||||
json_data = {'sticky': '1', 'notify': '0',
|
||||
'persistent': '1', 'comment': text}
|
||||
if alert.event_type == 'op5ServiceAlert':
|
||||
command_type = 'ACKNOWLEDGE_SVC_PROBLEM'
|
||||
json_data["host_name"] = alert.resource
|
||||
json_data["service_description"] = alert.event
|
||||
json_data['host_name'] = alert.resource
|
||||
json_data['service_description'] = alert.event
|
||||
if alert.event_type == 'op5HostAlert':
|
||||
command_type = 'ACKNOWLEDGE_HOST_PROBLEM'
|
||||
json_data["host_name"] = alert.resource
|
||||
|
||||
op5 = OP5(OP5_API_URL, OP5_API_USERNAME, OP5_API_PASSWORD, dryrun=False, debug=False, logtofile=False, interactive=False)
|
||||
json_data['host_name'] = alert.resource
|
||||
|
||||
op5 = OP5(OP5_API_URL, OP5_API_USERNAME, OP5_API_PASSWORD,
|
||||
dryrun=False, debug=False, logtofile=False, interactive=False)
|
||||
op5.command(command_type, json_data)
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.0.2'
|
||||
|
||||
setup(
|
||||
name="alerta-op5",
|
||||
name='alerta-op5',
|
||||
version=version,
|
||||
description='Alerta plugin for OP5 Monitor',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
@ -14,7 +13,7 @@ setup(
|
|||
packages=find_packages(),
|
||||
py_modules=['alerta_op5'],
|
||||
install_requires=[
|
||||
"op5lib==1.0"
|
||||
'op5lib==1.0'
|
||||
],
|
||||
include_package_data=True,
|
||||
zip_safe=True,
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
import requests
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.opsgenie')
|
||||
LOG.info('Initializing')
|
||||
|
@ -15,66 +16,78 @@ LOG.info('Initializing')
|
|||
OPSGENIE_EVENTS_CREATE_URL = 'https://api.opsgenie.com/v2/alerts'
|
||||
OPSGENIE_EVENTS_CLOSE_URL = 'https://api.opsgenie.com/v2/alerts/%s/close?identifierType=alias'
|
||||
OPSGENIE_EVENTS_ACK_URL = 'https://api.opsgenie.com/v2/alerts/%s/acknowledge?identifierType=alias'
|
||||
OPSGENIE_SERVICE_KEY = os.environ.get('OPSGENIE_SERVICE_KEY') or app.config['OPSGENIE_SERVICE_KEY']
|
||||
OPSGENIE_TEAMS = os.environ.get('OPSGENIE_TEAMS', '') # comma separated list of teams
|
||||
OPSGENIE_SEND_WARN = os.environ.get('OPSGENIE_SEND_WARN') or app.config.get('OPSGENIE_SEND_WARN', False)
|
||||
SERVICE_KEY_MATCHERS = os.environ.get('SERVICE_KEY_MATCHERS') or app.config['SERVICE_KEY_MATCHERS']
|
||||
DASHBOARD_URL = os.environ.get('DASHBOARD_URL') or app.config.get('DASHBOARD_URL', '')
|
||||
LOG.info('Initialized: %s key, %s matchers' % (OPSGENIE_SERVICE_KEY, SERVICE_KEY_MATCHERS))
|
||||
OPSGENIE_SERVICE_KEY = os.environ.get(
|
||||
'OPSGENIE_SERVICE_KEY') or app.config['OPSGENIE_SERVICE_KEY']
|
||||
# comma separated list of teams
|
||||
OPSGENIE_TEAMS = os.environ.get('OPSGENIE_TEAMS', '')
|
||||
OPSGENIE_SEND_WARN = os.environ.get(
|
||||
'OPSGENIE_SEND_WARN') or app.config.get('OPSGENIE_SEND_WARN', False)
|
||||
SERVICE_KEY_MATCHERS = os.environ.get(
|
||||
'SERVICE_KEY_MATCHERS') or app.config['SERVICE_KEY_MATCHERS']
|
||||
DASHBOARD_URL = os.environ.get(
|
||||
'DASHBOARD_URL') or app.config.get('DASHBOARD_URL', '')
|
||||
LOG.info('Initialized: %s key, %s matchers' %
|
||||
(OPSGENIE_SERVICE_KEY, SERVICE_KEY_MATCHERS))
|
||||
|
||||
# when using with OpsGenie Edge connector setting a known source is useful
|
||||
OPSGENIE_ALERT_SOURCE = os.environ.get('OPSGENIE_ALERT_SOURCE') or app.config.get('OPSGENIE_ALERT_SOURCE', 'Alerta')
|
||||
OPSGENIE_ALERT_SOURCE = os.environ.get(
|
||||
'OPSGENIE_ALERT_SOURCE') or app.config.get('OPSGENIE_ALERT_SOURCE', 'Alerta')
|
||||
|
||||
|
||||
class TriggerEvent(PluginBase):
|
||||
|
||||
def opsgenie_service_key(self, resource):
|
||||
if not SERVICE_KEY_MATCHERS:
|
||||
LOG.debug('No matchers defined! Default service key: %s' % (OPSGENIE_SERVICE_KEY))
|
||||
LOG.debug('No matchers defined! Default service key: %s' %
|
||||
(OPSGENIE_SERVICE_KEY))
|
||||
return OPSGENIE_SERVICE_KEY
|
||||
|
||||
for mapping in SERVICE_KEY_MATCHERS:
|
||||
if re.match(mapping['regex'], resource):
|
||||
LOG.debug('Matched regex: %s, service key: %s' % (mapping['regex'], mapping['api_key']))
|
||||
LOG.debug('Matched regex: %s, service key: %s' %
|
||||
(mapping['regex'], mapping['api_key']))
|
||||
return mapping['api_key']
|
||||
|
||||
LOG.debug('No regex match! Default service key: %s' % (OPSGENIE_SERVICE_KEY))
|
||||
LOG.debug('No regex match! Default service key: %s' %
|
||||
(OPSGENIE_SERVICE_KEY))
|
||||
return OPSGENIE_SERVICE_KEY
|
||||
|
||||
def opsgenie_close_alert(self, alert, why):
|
||||
|
||||
headers = {
|
||||
"Authorization": 'GenieKey ' + self.opsgenie_service_key(alert.resource)
|
||||
'Authorization': 'GenieKey ' + self.opsgenie_service_key(alert.resource)
|
||||
}
|
||||
|
||||
closeUrl = OPSGENIE_EVENTS_CLOSE_URL % alert.id
|
||||
LOG.debug('OpsGenie close %s: %s %s' % (why, alert.id, closeUrl))
|
||||
LOG.debug('OpsGenie close {}: {} {}'.format(why, alert.id, closeUrl))
|
||||
|
||||
try:
|
||||
r = requests.post(closeUrl, json={}, headers=headers, timeout=2)
|
||||
except Exception as e:
|
||||
raise RuntimeError("OpsGenie connection error: %s" % e)
|
||||
raise RuntimeError('OpsGenie connection error: %s' % e)
|
||||
return r
|
||||
|
||||
def opsgenie_ack_alert(self, alert, why):
|
||||
|
||||
headers = {
|
||||
"Authorization": 'GenieKey ' + self.opsgenie_service_key(alert.resource)
|
||||
'Authorization': 'GenieKey ' + self.opsgenie_service_key(alert.resource)
|
||||
}
|
||||
|
||||
ackUrl = OPSGENIE_EVENTS_ACK_URL % alert.id
|
||||
LOG.debug('OpsGenie ack %s: %s %s' % (why, alert.id, ackUrl))
|
||||
LOG.debug('OpsGenie ack {}: {} {}'.format(why, alert.id, ackUrl))
|
||||
|
||||
try:
|
||||
r = requests.post(ackUrl, json={}, headers=headers, timeout=2)
|
||||
except Exception as e:
|
||||
raise RuntimeError("OpsGenie connection error: %s" % e)
|
||||
raise RuntimeError('OpsGenie connection error: %s' % e)
|
||||
return r
|
||||
|
||||
def pre_receive(self, alert):
|
||||
return alert
|
||||
|
||||
def post_receive(self, alert):
|
||||
LOG.debug('Alert receive %s: %s' % (alert.id, alert.get_body(history=False)))
|
||||
LOG.debug('Alert receive %s: %s' %
|
||||
(alert.id, alert.get_body(history=False)))
|
||||
if alert.repeat:
|
||||
LOG.debug('Alert repeating; ignored')
|
||||
return
|
||||
|
@ -86,13 +99,14 @@ class TriggerEvent(PluginBase):
|
|||
LOG.info('Just informational or warning not sending to OpsGenie')
|
||||
else:
|
||||
headers = {
|
||||
"Authorization": 'GenieKey ' + self.opsgenie_service_key(alert.resource)
|
||||
'Authorization': 'GenieKey ' + self.opsgenie_service_key(alert.resource)
|
||||
}
|
||||
|
||||
# Send all alert data as details to opsgenie
|
||||
body = alert.get_body(history=False)
|
||||
details = {}
|
||||
details['web_url'] = '%s/#/alert/%s' % (DASHBOARD_URL, alert.id)
|
||||
details['web_url'] = '{}/#/alert/{}'.format(
|
||||
DASHBOARD_URL, alert.id)
|
||||
details['service'] = alert.service[0]
|
||||
details['origin'] = body['origin']
|
||||
details['event'] = body['event']
|
||||
|
@ -103,37 +117,40 @@ class TriggerEvent(PluginBase):
|
|||
details['duplicateCount'] = body['duplicateCount']
|
||||
|
||||
payload = {
|
||||
"alias": alert.id,
|
||||
"message": "[ %s ]: %s: %s" % (alert.environment, alert.severity, alert.text),
|
||||
"entity": alert.environment,
|
||||
"responders" : self.get_opsgenie_teams(),
|
||||
"tags": [alert.environment, alert.resource, alert.service[0], alert.event],
|
||||
"source": "{}".format(OPSGENIE_ALERT_SOURCE),
|
||||
"details": details
|
||||
'alias': alert.id,
|
||||
'message': '[ {} ]: {}: {}'.format(alert.environment, alert.severity, alert.text),
|
||||
'entity': alert.environment,
|
||||
'responders': self.get_opsgenie_teams(),
|
||||
'tags': [alert.environment, alert.resource, alert.service[0], alert.event],
|
||||
'source': '{}'.format(OPSGENIE_ALERT_SOURCE),
|
||||
'details': details
|
||||
}
|
||||
|
||||
LOG.debug('OpsGenie CREATE payload: %s' % payload)
|
||||
|
||||
try:
|
||||
r = requests.post(OPSGENIE_EVENTS_CREATE_URL, json=payload, headers=headers, timeout=2)
|
||||
r = requests.post(OPSGENIE_EVENTS_CREATE_URL,
|
||||
json=payload, headers=headers, timeout=2)
|
||||
except Exception as e:
|
||||
raise RuntimeError("OpsGenie connection error: %s" % e)
|
||||
raise RuntimeError('OpsGenie connection error: %s' % e)
|
||||
|
||||
LOG.debug('OpsGenie response: %s - %s' % (r.status_code, r.text))
|
||||
LOG.debug('OpsGenie response: {} - {}'.format(r.status_code, r.text))
|
||||
|
||||
# generate list of responders from OPSGENIE_TEAMS env var
|
||||
def get_opsgenie_teams(self):
|
||||
teams = OPSGENIE_TEAMS.replace(' ', '') # remove whitespace
|
||||
teams = OPSGENIE_TEAMS.replace(' ', '') # remove whitespace
|
||||
if len(teams) == 0:
|
||||
return [] # no teams specified
|
||||
return [] # no teams specified
|
||||
teams = teams.split(',')
|
||||
return [{"name": team, "type": "team"} for team in teams]
|
||||
return [{'name': team, 'type': 'team'} for team in teams]
|
||||
|
||||
def status_change(self, alert, status, text):
|
||||
LOG.debug('Alert change %s to %s: %s' % (alert.id, status, alert.get_body(history=False)))
|
||||
LOG.debug('Alert change %s to %s: %s' %
|
||||
(alert.id, status, alert.get_body(history=False)))
|
||||
|
||||
if status not in ['ack', 'assign', 'closed']:
|
||||
LOG.debug('Not sending status change to opsgenie: %s to %s' % (alert.id, status))
|
||||
LOG.debug('Not sending status change to opsgenie: %s to %s' %
|
||||
(alert.id, status))
|
||||
return
|
||||
|
||||
if status == 'closed':
|
||||
|
@ -141,4 +158,4 @@ class TriggerEvent(PluginBase):
|
|||
elif status == 'ack':
|
||||
r = self.opsgenie_ack_alert(alert, 'STATUS-ACK')
|
||||
|
||||
LOG.debug('OpsGenie response: %s - %s' % (r.status_code, r.text))
|
||||
LOG.debug('OpsGenie response: {} - {}'.format(r.status_code, r.text))
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.0.3'
|
||||
|
||||
setup(
|
||||
name="alerta-opsgenie",
|
||||
name='alerta-opsgenie',
|
||||
version=version,
|
||||
description='Alerta plugin for OpsGenie',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -1,36 +1,42 @@
|
|||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
import requests
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.pagerduty')
|
||||
|
||||
PAGERDUTY_EVENTS_URL = 'https://events.pagerduty.com/generic/2010-04-15/create_event.json'
|
||||
PAGERDUTY_SERVICE_KEY = os.environ.get('PAGERDUTY_SERVICE_KEY') or app.config['PAGERDUTY_SERVICE_KEY']
|
||||
SERVICE_KEY_MATCHERS = os.environ.get('SERVICE_KEY_MATCHERS') or app.config['SERVICE_KEY_MATCHERS']
|
||||
DASHBOARD_URL = os.environ.get('DASHBOARD_URL') or app.config.get('DASHBOARD_URL', '')
|
||||
PAGERDUTY_SERVICE_KEY = os.environ.get(
|
||||
'PAGERDUTY_SERVICE_KEY') or app.config['PAGERDUTY_SERVICE_KEY']
|
||||
SERVICE_KEY_MATCHERS = os.environ.get(
|
||||
'SERVICE_KEY_MATCHERS') or app.config['SERVICE_KEY_MATCHERS']
|
||||
DASHBOARD_URL = os.environ.get(
|
||||
'DASHBOARD_URL') or app.config.get('DASHBOARD_URL', '')
|
||||
|
||||
|
||||
class TriggerEvent(PluginBase):
|
||||
|
||||
def pagerduty_service_key(self, resource):
|
||||
if not SERVICE_KEY_MATCHERS:
|
||||
LOG.debug('No matchers defined! Default service key: %s' % (PAGERDUTY_SERVICE_KEY))
|
||||
LOG.debug('No matchers defined! Default service key: %s' %
|
||||
(PAGERDUTY_SERVICE_KEY))
|
||||
return PAGERDUTY_SERVICE_KEY
|
||||
|
||||
for mapping in SERVICE_KEY_MATCHERS:
|
||||
if re.match(mapping['regex'], resource):
|
||||
LOG.debug('Matched regex: %s, service key: %s' % (mapping['regex'], mapping['api_key']))
|
||||
LOG.debug('Matched regex: %s, service key: %s' %
|
||||
(mapping['regex'], mapping['api_key']))
|
||||
return mapping['api_key']
|
||||
|
||||
LOG.debug('No regex match! Default service key: %s' % (PAGERDUTY_SERVICE_KEY))
|
||||
LOG.debug('No regex match! Default service key: %s' %
|
||||
(PAGERDUTY_SERVICE_KEY))
|
||||
return PAGERDUTY_SERVICE_KEY
|
||||
|
||||
def pre_receive(self, alert):
|
||||
|
@ -41,24 +47,24 @@ class TriggerEvent(PluginBase):
|
|||
if alert.repeat:
|
||||
return
|
||||
|
||||
message = "%s: %s alert for %s - %s is %s" % (
|
||||
message = '{}: {} alert for {} - {} is {}'.format(
|
||||
alert.environment, alert.severity.capitalize(),
|
||||
','.join(alert.service), alert.resource, alert.event
|
||||
)
|
||||
|
||||
if alert.severity in ['cleared', 'normal', 'ok']:
|
||||
event_type = "resolve"
|
||||
event_type = 'resolve'
|
||||
else:
|
||||
event_type = "trigger"
|
||||
event_type = 'trigger'
|
||||
|
||||
payload = {
|
||||
"service_key": self.pagerduty_service_key(alert.resource),
|
||||
"incident_key": alert.id,
|
||||
"event_type": event_type,
|
||||
"description": message,
|
||||
"client": "alerta",
|
||||
"client_url": '%s/#/alert/%s' % (DASHBOARD_URL, alert.id),
|
||||
"details": alert.get_body(history=False)
|
||||
'service_key': self.pagerduty_service_key(alert.resource),
|
||||
'incident_key': alert.id,
|
||||
'event_type': event_type,
|
||||
'description': message,
|
||||
'client': 'alerta',
|
||||
'client_url': '{}/#/alert/{}'.format(DASHBOARD_URL, alert.id),
|
||||
'details': alert.get_body(history=False)
|
||||
}
|
||||
|
||||
LOG.debug('PagerDuty payload: %s', payload)
|
||||
|
@ -66,7 +72,7 @@ class TriggerEvent(PluginBase):
|
|||
try:
|
||||
r = requests.post(PAGERDUTY_EVENTS_URL, json=payload, timeout=2)
|
||||
except Exception as e:
|
||||
raise RuntimeError("PagerDuty connection error: %s" % e)
|
||||
raise RuntimeError('PagerDuty connection error: %s' % e)
|
||||
|
||||
LOG.debug('PagerDuty response: %s - %s', r.status_code, r.text)
|
||||
|
||||
|
@ -76,11 +82,11 @@ class TriggerEvent(PluginBase):
|
|||
return
|
||||
|
||||
payload = {
|
||||
"service_key": self.pagerduty_service_key(alert.resource),
|
||||
"incident_key": alert.id,
|
||||
"event_type": "acknowledge",
|
||||
"description": text,
|
||||
"details": alert.get_body(history=False)
|
||||
'service_key': self.pagerduty_service_key(alert.resource),
|
||||
'incident_key': alert.id,
|
||||
'event_type': 'acknowledge',
|
||||
'description': text,
|
||||
'details': alert.get_body(history=False)
|
||||
}
|
||||
|
||||
LOG.debug('PagerDuty payload: %s', payload)
|
||||
|
@ -88,6 +94,6 @@ class TriggerEvent(PluginBase):
|
|||
try:
|
||||
r = requests.post(PAGERDUTY_EVENTS_URL, json=payload, timeout=2)
|
||||
except Exception as e:
|
||||
raise RuntimeError("PagerDuty connection error: %s" % e)
|
||||
raise RuntimeError('PagerDuty connection error: %s' % e)
|
||||
|
||||
LOG.debug('PagerDuty response: %s - %s', r.status_code, r.text)
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.3.1'
|
||||
|
||||
setup(
|
||||
name="alerta-pagerduty",
|
||||
name='alerta-pagerduty',
|
||||
version=version,
|
||||
description='Alerta plugin for PagerDuty',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -48,13 +48,13 @@ ALERTMANAGER_SILENCE_FROM_ACK = True
|
|||
```
|
||||
|
||||
|
||||
Prometheus docs specify that prometheus should send all alerts to all alertmanagers. If you have configured your
|
||||
ALERTMANAGER_API_URL to be a load balanced endpoint that mirrors requests to a set of alertmanagers then the following setting
|
||||
Prometheus docs specify that prometheus should send all alerts to all alertmanagers. If you have configured your
|
||||
ALERTMANAGER_API_URL to be a load balanced endpoint that mirrors requests to a set of alertmanagers then the following setting
|
||||
will create/remove silences if alertmanager has set the externalUrl, the following will configure alerta to use that for silences
|
||||
instead of the Alertmanager API URL.
|
||||
instead of the Alertmanager API URL.
|
||||
|
||||
Alertmanager syncs silences across all alertmanagers so only sendng it to one AM is appropriate. Using a load-balanced API that mirrors
|
||||
requests will create one unique silenceId per alertmanager instance and sync them across all alertmanagers, which is not necessary.
|
||||
Alertmanager syncs silences across all alertmanagers so only sendng it to one AM is appropriate. Using a load-balanced API that mirrors
|
||||
requests will create one unique silenceId per alertmanager instance and sync them across all alertmanagers, which is not necessary.
|
||||
```python
|
||||
ALERTMANAGER_USE_EXTERNALURL_FOR_SILENCES = True
|
||||
```
|
||||
|
|
|
@ -1,37 +1,45 @@
|
|||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
from alerta.models.alert import Alert
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
|
||||
from alerta.models.alert import Alert
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.prometheus')
|
||||
|
||||
DEFAULT_ALERTMANAGER_API_URL = 'http://localhost:9093'
|
||||
|
||||
ALERTMANAGER_API_URL = os.environ.get('ALERTMANAGER_API_URL') or app.config.get('ALERTMANAGER_API_URL', None)
|
||||
ALERTMANAGER_USERNAME = os.environ.get('ALERTMANAGER_USERNAME') or app.config.get('ALERTMANAGER_USERNAME', None)
|
||||
ALERTMANAGER_PASSWORD = os.environ.get('ALERTMANAGER_PASSWORD') or app.config.get('ALERTMANAGER_PASSWORD', None)
|
||||
ALERTMANAGER_SILENCE_DAYS = os.environ.get('ALERTMANAGER_SILENCE_DAYS') or app.config.get('ALERTMANAGER_SILENCE_DAYS', 1)
|
||||
ALERTMANAGER_SILENCE_FROM_ACK = os.environ.get('ALERTMANAGER_SILENCE_FROM_ACK') or app.config.get('ALERTMANAGER_SILENCE_FROM_ACK', False)
|
||||
ALERTMANAGER_USE_EXTERNALURL_FOR_SILENCES = os.environ.get('ALERTMANAGER_USE_EXTERNALURL_FOR_SILENCES') or app.config.get('ALERTMANAGER_USE_EXTERNALURL_FOR_SILENCES',False)
|
||||
ALERTMANAGER_API_URL = os.environ.get(
|
||||
'ALERTMANAGER_API_URL') or app.config.get('ALERTMANAGER_API_URL', None)
|
||||
ALERTMANAGER_USERNAME = os.environ.get(
|
||||
'ALERTMANAGER_USERNAME') or app.config.get('ALERTMANAGER_USERNAME', None)
|
||||
ALERTMANAGER_PASSWORD = os.environ.get(
|
||||
'ALERTMANAGER_PASSWORD') or app.config.get('ALERTMANAGER_PASSWORD', None)
|
||||
ALERTMANAGER_SILENCE_DAYS = os.environ.get(
|
||||
'ALERTMANAGER_SILENCE_DAYS') or app.config.get('ALERTMANAGER_SILENCE_DAYS', 1)
|
||||
ALERTMANAGER_SILENCE_FROM_ACK = os.environ.get(
|
||||
'ALERTMANAGER_SILENCE_FROM_ACK') or app.config.get('ALERTMANAGER_SILENCE_FROM_ACK', False)
|
||||
ALERTMANAGER_USE_EXTERNALURL_FOR_SILENCES = os.environ.get(
|
||||
'ALERTMANAGER_USE_EXTERNALURL_FOR_SILENCES') or app.config.get('ALERTMANAGER_USE_EXTERNALURL_FOR_SILENCES', False)
|
||||
|
||||
|
||||
class AlertmanagerSilence(PluginBase):
|
||||
|
||||
def __init__(self, name=None):
|
||||
|
||||
self.auth = (ALERTMANAGER_USERNAME, ALERTMANAGER_PASSWORD) if ALERTMANAGER_USERNAME else None
|
||||
self.auth = (ALERTMANAGER_USERNAME,
|
||||
ALERTMANAGER_PASSWORD) if ALERTMANAGER_USERNAME else None
|
||||
|
||||
super(AlertmanagerSilence, self).__init__(name)
|
||||
super().__init__(name)
|
||||
|
||||
def pre_receive(self, alert):
|
||||
return alert
|
||||
|
@ -47,22 +55,25 @@ class AlertmanagerSilence(PluginBase):
|
|||
|
||||
silenceId = alert.attributes.get('silenceId', None)
|
||||
if silenceId:
|
||||
LOG.debug('Alertmanager: Remove silence for alertname=%s instance=%s', alert.event, alert.resource)
|
||||
base_url = ALERTMANAGER_API_URL or alert.attributes.get('externalUrl', DEFAULT_ALERTMANAGER_API_URL)
|
||||
LOG.debug('Alertmanager: Remove silence for alertname=%s instance=%s',
|
||||
alert.event, alert.resource)
|
||||
base_url = ALERTMANAGER_API_URL or alert.attributes.get(
|
||||
'externalUrl', DEFAULT_ALERTMANAGER_API_URL)
|
||||
url = base_url + '/api/v1/silence/%s' % silenceId
|
||||
try:
|
||||
r = requests.delete(url, auth=self.auth, timeout=2)
|
||||
except Exception as e:
|
||||
raise RuntimeError("Alertmanager: ERROR - %s" % e)
|
||||
raise RuntimeError('Alertmanager: ERROR - %s' % e)
|
||||
LOG.debug('Alertmanager: %s - %s', r.status_code, r.text)
|
||||
|
||||
try:
|
||||
alert.attributes['silenceId'] = None
|
||||
except Exception as e:
|
||||
raise RuntimeError("Alertmanager: ERROR - %s" % e)
|
||||
LOG.debug('Alertmanager: Removed silenceId %s from attributes', silenceId)
|
||||
raise RuntimeError('Alertmanager: ERROR - %s' % e)
|
||||
LOG.debug(
|
||||
'Alertmanager: Removed silenceId %s from attributes', silenceId)
|
||||
if status == 'closed':
|
||||
LOG.warning("Status is now closed")
|
||||
LOG.warning('Status is now closed')
|
||||
|
||||
return alert
|
||||
|
||||
|
@ -74,22 +85,26 @@ class AlertmanagerSilence(PluginBase):
|
|||
if alert.event_type != 'prometheusAlert':
|
||||
return alert
|
||||
|
||||
|
||||
base_url = ALERTMANAGER_API_URL or alert.attributes.get('externalUrl', DEFAULT_ALERTMANAGER_API_URL)
|
||||
base_url = ALERTMANAGER_API_URL or alert.attributes.get(
|
||||
'externalUrl', DEFAULT_ALERTMANAGER_API_URL)
|
||||
if action == 'close':
|
||||
LOG.warning("Got a close action so trying to close this in alertmanager too")
|
||||
LOG.warning(
|
||||
'Got a close action so trying to close this in alertmanager too')
|
||||
url = base_url + '/api/v1/alerts'
|
||||
raw_data_string = alert.raw_data
|
||||
raw_data = json.loads(raw_data_string)
|
||||
# set the endsAt to now so alertmanager will consider it expired or whatever
|
||||
raw_data["endsAt"] = (datetime.datetime.utcnow() - datetime.timedelta(minutes=5)).replace(microsecond=0).isoformat() + ".000Z"
|
||||
LOG.debug("Raw data type: {}, Raw data contents: {}".format(type(raw_data),raw_data))
|
||||
data = [ raw_data ]
|
||||
raw_data['endsAt'] = (datetime.datetime.utcnow(
|
||||
) - datetime.timedelta(minutes=5)).replace(microsecond=0).isoformat() + '.000Z'
|
||||
LOG.debug('Raw data type: {}, Raw data contents: {}'.format(
|
||||
type(raw_data), raw_data))
|
||||
data = [raw_data]
|
||||
try:
|
||||
r = requests.post(url, json=data, auth=self.auth, timeout=2)
|
||||
except Exception as e:
|
||||
raise RuntimeError("Alertmanager: ERROR - %s" % e)
|
||||
LOG.debug('Alertmanager response was: %s - %s', r.status_code, r.text)
|
||||
raise RuntimeError('Alertmanager: ERROR - %s' % e)
|
||||
LOG.debug('Alertmanager response was: %s - %s',
|
||||
r.status_code, r.text)
|
||||
|
||||
elif action == 'ack' and ALERTMANAGER_SILENCE_FROM_ACK:
|
||||
|
||||
|
@ -108,37 +123,38 @@ class AlertmanagerSilence(PluginBase):
|
|||
LOG.debug('Alertmanager: Add silence for alertname=%s instance=%s timeout=%s',
|
||||
alert.event, alert.resource, str(silence_seconds))
|
||||
data = {
|
||||
"matchers": [
|
||||
'matchers': [
|
||||
{
|
||||
"name": "alertname",
|
||||
"value": alert.event
|
||||
'name': 'alertname',
|
||||
'value': alert.event
|
||||
},
|
||||
{
|
||||
"name": "instance",
|
||||
"value": alert.resource
|
||||
'name': 'instance',
|
||||
'value': alert.resource
|
||||
}
|
||||
],
|
||||
"startsAt": datetime.datetime.utcnow().replace(microsecond=0).isoformat() + ".000Z",
|
||||
"endsAt": (datetime.datetime.utcnow() + datetime.timedelta(seconds=silence_seconds))
|
||||
.replace(microsecond=0).isoformat() + ".000Z",
|
||||
"createdBy": "alerta",
|
||||
"comment": text if text != '' else "silenced by alerta"
|
||||
'startsAt': datetime.datetime.utcnow().replace(microsecond=0).isoformat() + '.000Z',
|
||||
'endsAt': (datetime.datetime.utcnow() + datetime.timedelta(seconds=silence_seconds))
|
||||
.replace(microsecond=0).isoformat() + '.000Z',
|
||||
'createdBy': 'alerta',
|
||||
'comment': text if text != '' else 'silenced by alerta'
|
||||
}
|
||||
|
||||
# if alertmanager is clustered behind a load balancer that mirrors requests we should prefer to create one silence
|
||||
# rather than many
|
||||
# if alertmanager is clustered behind a load balancer that mirrors requests we should prefer to create one silence
|
||||
# rather than many
|
||||
if ALERTMANAGER_USE_EXTERNALURL_FOR_SILENCES:
|
||||
base_url = alert.attributes.get('externalUrl', DEFAULT_ALERTMANAGER_API_URL) or ALERTMANAGER_API_URL
|
||||
else:
|
||||
base_url = ALERTMANAGER_API_URL or alert.attributes.get('externalUrl', DEFAULT_ALERTMANAGER_API_URL)
|
||||
base_url = alert.attributes.get(
|
||||
'externalUrl', DEFAULT_ALERTMANAGER_API_URL) or ALERTMANAGER_API_URL
|
||||
else:
|
||||
base_url = ALERTMANAGER_API_URL or alert.attributes.get(
|
||||
'externalUrl', DEFAULT_ALERTMANAGER_API_URL)
|
||||
|
||||
url = base_url + '/api/v1/silences'
|
||||
|
||||
|
||||
try:
|
||||
r = requests.post(url, json=data, auth=self.auth, timeout=2)
|
||||
except Exception as e:
|
||||
raise RuntimeError("Alertmanager: ERROR - %s" % e)
|
||||
raise RuntimeError('Alertmanager: ERROR - %s' % e)
|
||||
LOG.debug('Alertmanager: %s - %s', r.status_code, r.text)
|
||||
|
||||
# example r={"status":"success","data":{"silenceId":8}}
|
||||
|
@ -148,29 +164,32 @@ class AlertmanagerSilence(PluginBase):
|
|||
silenceId = data['silenceId']
|
||||
alert.attributes['silenceId'] = silenceId
|
||||
else:
|
||||
silenceId = alert.attributes.get('silenceId', "unknown")
|
||||
silenceId = alert.attributes.get('silenceId', 'unknown')
|
||||
text = text + ' (silenced in Alertmanager)'
|
||||
except Exception as e:
|
||||
raise RuntimeError("Alertmanager: ERROR - %s" % e)
|
||||
raise RuntimeError('Alertmanager: ERROR - %s' % e)
|
||||
LOG.debug('Alertmanager: Added silenceId %s to attributes', silenceId)
|
||||
|
||||
elif action == 'unack':
|
||||
LOG.debug('Alertmanager: Remove silence for alertname=%s instance=%s', alert.event, alert.resource)
|
||||
LOG.debug('Alertmanager: Remove silence for alertname=%s instance=%s',
|
||||
alert.event, alert.resource)
|
||||
|
||||
silenceId = alert.attributes.get('silenceId', None)
|
||||
if silenceId:
|
||||
base_url = ALERTMANAGER_API_URL or alert.attributes.get('externalUrl', DEFAULT_ALERTMANAGER_API_URL)
|
||||
base_url = ALERTMANAGER_API_URL or alert.attributes.get(
|
||||
'externalUrl', DEFAULT_ALERTMANAGER_API_URL)
|
||||
url = base_url + '/api/v1/silence/%s' % silenceId
|
||||
try:
|
||||
r = requests.delete(url, auth=self.auth, timeout=2)
|
||||
except Exception as e:
|
||||
raise RuntimeError("Alertmanager: ERROR - %s" % e)
|
||||
raise RuntimeError('Alertmanager: ERROR - %s' % e)
|
||||
LOG.debug('Alertmanager: %s - %s', r.status_code, r.text)
|
||||
|
||||
try:
|
||||
alert.attributes['silenceId'] = None
|
||||
except Exception as e:
|
||||
raise RuntimeError("Alertmanager: ERROR - %s" % e)
|
||||
LOG.debug('Alertmanager: Removed silenceId %s from attributes', silenceId)
|
||||
raise RuntimeError('Alertmanager: ERROR - %s' % e)
|
||||
LOG.debug(
|
||||
'Alertmanager: Removed silenceId %s from attributes', silenceId)
|
||||
|
||||
return alert
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.4.0'
|
||||
|
||||
setup(
|
||||
name="alerta-prometheus",
|
||||
name='alerta-prometheus',
|
||||
version=version,
|
||||
description='Alerta plugin for Prometheus Alertmanager',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -14,7 +14,7 @@ You need to install following python packages:
|
|||
$ sudo pip install --upgrade oauth2client
|
||||
$ sudo pip install grpcio==1.30.0
|
||||
|
||||
There are issues running never versions of grpcio (Confirmed stable and working with grpcio 1.30.0).
|
||||
There are issues running never versions of grpcio (Confirmed stable and working with grpcio 1.30.0).
|
||||
|
||||
Follow this to configure [authentication](https://googlecloudplatform.github.io/google-cloud-python/stable/pubsub-usage.html#authentication-configuration)
|
||||
|
||||
|
|
|
@ -1,27 +1,28 @@
|
|||
import os
|
||||
import logging
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
from alerta.plugins import PluginBase
|
||||
from google.cloud import pubsub_v1
|
||||
from google.oauth2 import service_account
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
from google.cloud import pubsub_v1
|
||||
from google.oauth2 import service_account
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.pubsub')
|
||||
|
||||
PROJECT_ID = os.environ.get('PROJECT_ID') or app.config.get('PROJECT_ID')
|
||||
TOPIC_NAME = os.environ.get('TOPIC_NAME') or app.config.get('TOPIC_NAME')
|
||||
|
||||
SERVICE_ACCOUNT_JSON = os.environ.get('SERVICE_ACCOUNT_JSON') or app.config.get('SERVICE_ACCOUNT_JSON', None)
|
||||
SERVICE_ACCOUNT_JSON = os.environ.get(
|
||||
'SERVICE_ACCOUNT_JSON') or app.config.get('SERVICE_ACCOUNT_JSON', None)
|
||||
|
||||
PUBSUB_SCOPES = ['https://www.googleapis.com/auth/pubsub']
|
||||
|
||||
PUBSUB_SCOPES = ["https://www.googleapis.com/auth/pubsub"]
|
||||
|
||||
class SendToPubsub(PluginBase):
|
||||
|
||||
|
@ -29,13 +30,14 @@ class SendToPubsub(PluginBase):
|
|||
LOG.info('creating pubsub client')
|
||||
self.publisher = self.get_client()
|
||||
self.topic = 'projects/{}/topics/{}'.format(PROJECT_ID, TOPIC_NAME)
|
||||
super(SendToPubsub, self).__init__(name)
|
||||
super().__init__(name)
|
||||
|
||||
def get_client(self):
|
||||
if SERVICE_ACCOUNT_JSON:
|
||||
json_dict = json.loads(SERVICE_ACCOUNT_JSON)
|
||||
LOG.info('using service account JSON : %s', json_dict)
|
||||
credential = service_account.Credentials.from_service_account_info(json_dict)
|
||||
credential = service_account.Credentials.from_service_account_info(
|
||||
json_dict)
|
||||
scoped_credential = credential.with_scopes(PUBSUB_SCOPES)
|
||||
return pubsub_v1.PublisherClient(credentials=scoped_credential)
|
||||
|
||||
|
@ -47,31 +49,32 @@ class SendToPubsub(PluginBase):
|
|||
|
||||
def post_receive(self, alert):
|
||||
body = alert.get_body(history=False)
|
||||
if not alert.severity in ['cleared', 'normal', 'ok']:
|
||||
if alert.severity not in ['cleared', 'normal', 'ok']:
|
||||
try:
|
||||
encoded_message = json.dumps(body)
|
||||
encoded_message = encoded_message.encode("utf-8")
|
||||
LOG.info("post receive")
|
||||
encoded_message = encoded_message.encode('utf-8')
|
||||
LOG.info('post receive')
|
||||
LOG.info(encoded_message)
|
||||
future = self.publisher.publish(self.topic, encoded_message)
|
||||
future.result()
|
||||
except Exception as excp:
|
||||
LOG.exception(excp)
|
||||
raise RuntimeError("pubsub exception: %s - %s" % (excp, body))
|
||||
raise RuntimeError(
|
||||
'pubsub exception: {} - {}'.format(excp, body))
|
||||
|
||||
def status_change(self, alert, status, text):
|
||||
body = alert.get_body(history=False)
|
||||
if not alert.severity in ['normal','ok']:
|
||||
if alert.severity not in ['normal', 'ok']:
|
||||
try:
|
||||
body['status'] = status
|
||||
body['updateTime'] = datetime.utcnow().isoformat()
|
||||
encoded_message = json.dumps(body)
|
||||
encoded_message = encoded_message.encode("utf-8")
|
||||
LOG.info("status change: %s", status)
|
||||
encoded_message = encoded_message.encode('utf-8')
|
||||
LOG.info('status change: %s', status)
|
||||
LOG.info(encoded_message)
|
||||
future = self.publisher.publish(self.topic, encoded_message)
|
||||
future.result()
|
||||
except Exception as excp:
|
||||
LOG.exception(excp)
|
||||
raise RuntimeError("pubsub exception: %s - %s" % (excp, body))
|
||||
|
||||
raise RuntimeError(
|
||||
'pubsub exception: {} - {}'.format(excp, body))
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.2.2'
|
||||
|
||||
setup(
|
||||
name="alerta-pubsub",
|
||||
name='alerta-pubsub',
|
||||
version=version,
|
||||
description='Alerta plugin for sending alerts to pubsub',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
|
||||
import logging
|
||||
import os
|
||||
|
||||
import requests
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.pushover')
|
||||
|
||||
PUSHOVER_URL = 'https://api.pushover.net/1/messages.json'
|
||||
|
||||
PUSHOVER_TOKEN = os.environ.get('PUSHOVER_TOKEN') or app.config['PUSHOVER_TOKEN']
|
||||
PUSHOVER_TOKEN = os.environ.get(
|
||||
'PUSHOVER_TOKEN') or app.config['PUSHOVER_TOKEN']
|
||||
PUSHOVER_USER = os.environ.get('PUSHOVER_USER') or app.config['PUSHOVER_USER']
|
||||
DASHBOARD_URL = os.environ.get('DASHBOARD_URL') or app.config.get('DASHBOARD_URL', '')
|
||||
DASHBOARD_URL = os.environ.get(
|
||||
'DASHBOARD_URL') or app.config.get('DASHBOARD_URL', '')
|
||||
|
||||
PUSHOVER_EMERG = 2 # requires user ack
|
||||
PUSHOVER_HIGH = 1
|
||||
|
@ -26,9 +28,9 @@ PUSHOVER_BADGE = -2 # no notification
|
|||
# See https://pushover.net/api#priority
|
||||
PRIORITY_MAP = {
|
||||
'critical': PUSHOVER_EMERG,
|
||||
'major': PUSHOVER_HIGH,
|
||||
'minor': PUSHOVER_NORMAL,
|
||||
'warning': PUSHOVER_LOW
|
||||
'major': PUSHOVER_HIGH,
|
||||
'minor': PUSHOVER_NORMAL,
|
||||
'warning': PUSHOVER_LOW
|
||||
}
|
||||
|
||||
|
||||
|
@ -42,7 +44,7 @@ class PushMessage(PluginBase):
|
|||
if alert.repeat:
|
||||
return
|
||||
|
||||
title = "%s: %s alert for %s - %s is %s" % (
|
||||
title = '{}: {} alert for {} - {} is {}'.format(
|
||||
alert.environment, alert.severity.capitalize(),
|
||||
','.join(alert.service), alert.resource, alert.event
|
||||
)
|
||||
|
@ -50,15 +52,15 @@ class PushMessage(PluginBase):
|
|||
priority = PRIORITY_MAP.get(alert.severity, PUSHOVER_BADGE)
|
||||
|
||||
payload = {
|
||||
"token": PUSHOVER_TOKEN,
|
||||
"user": PUSHOVER_USER,
|
||||
"title": title,
|
||||
"message": alert.text,
|
||||
"url": '%s/#/alert/%s' % (DASHBOARD_URL, alert.id),
|
||||
"url_title": "View alert",
|
||||
"priority": priority,
|
||||
"timestamp": alert.create_time,
|
||||
"sound": "tugboat"
|
||||
'token': PUSHOVER_TOKEN,
|
||||
'user': PUSHOVER_USER,
|
||||
'title': title,
|
||||
'message': alert.text,
|
||||
'url': '{}/#/alert/{}'.format(DASHBOARD_URL, alert.id),
|
||||
'url_title': 'View alert',
|
||||
'priority': priority,
|
||||
'timestamp': alert.create_time,
|
||||
'sound': 'tugboat'
|
||||
}
|
||||
|
||||
if priority == PUSHOVER_EMERG:
|
||||
|
@ -70,7 +72,7 @@ class PushMessage(PluginBase):
|
|||
try:
|
||||
r = requests.post(PUSHOVER_URL, data=payload, timeout=2)
|
||||
except Exception as e:
|
||||
raise RuntimeError("Pushover.net: ERROR - %s" % e)
|
||||
raise RuntimeError('Pushover.net: ERROR - %s' % e)
|
||||
|
||||
LOG.debug('Pushover.net: %s - %s', r.status_code, r.text)
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.3.2'
|
||||
|
||||
setup(
|
||||
name="alerta-pushover",
|
||||
name='alerta-pushover',
|
||||
version=version,
|
||||
description='Alerta plugin for Pushover',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -1,29 +1,34 @@
|
|||
|
||||
import logging
|
||||
import os
|
||||
|
||||
import requests
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.rocketchat')
|
||||
|
||||
ROCKETCHAT_WEBHOOK_URL = os.environ.get('ROCKETCHAT_WEBHOOK_URL') or app.config['ROCKETCHAT_WEBHOOK_URL']
|
||||
ROCKETCHAT_CHANNEL = os.environ.get('ROCKETCHAT_CHANNEL') or app.config.get('ROCKETCHAT_CHANNEL', '')
|
||||
ALERTA_USERNAME = os.environ.get('ALERTA_USERNAME') or app.config.get('ALERTA_USERNAME', 'alerta')
|
||||
ICON_EMOJI = os.environ.get('ICON_EMOJI') or app.config.get('ICON_EMOJI', ':rocket:')
|
||||
DASHBOARD_URL = os.environ.get('DASHBOARD_URL') or app.config.get('DASHBOARD_URL', '')
|
||||
ROCKETCHAT_WEBHOOK_URL = os.environ.get(
|
||||
'ROCKETCHAT_WEBHOOK_URL') or app.config['ROCKETCHAT_WEBHOOK_URL']
|
||||
ROCKETCHAT_CHANNEL = os.environ.get(
|
||||
'ROCKETCHAT_CHANNEL') or app.config.get('ROCKETCHAT_CHANNEL', '')
|
||||
ALERTA_USERNAME = os.environ.get(
|
||||
'ALERTA_USERNAME') or app.config.get('ALERTA_USERNAME', 'alerta')
|
||||
ICON_EMOJI = os.environ.get('ICON_EMOJI') or app.config.get(
|
||||
'ICON_EMOJI', ':rocket:')
|
||||
DASHBOARD_URL = os.environ.get(
|
||||
'DASHBOARD_URL') or app.config.get('DASHBOARD_URL', '')
|
||||
|
||||
DEFAULT_SEVERITY_MAP = {
|
||||
'security': '#000000', # black
|
||||
'critical': '#FF0000', # red
|
||||
'major': '#FFA500', # orange
|
||||
'minor': '#FFFF00', # yellow
|
||||
'warning': '#1E90FF', #blue
|
||||
'informational': '#808080', #gray
|
||||
'warning': '#1E90FF', # blue
|
||||
'informational': '#808080', # gray
|
||||
'debug': '#808080', # gray
|
||||
'trace': '#808080', # gray
|
||||
'ok': '#00CC00' # green
|
||||
|
@ -50,27 +55,29 @@ class PostMessage(PluginBase):
|
|||
title = '[{status}] {environment}: {event} on {resource}'.format(
|
||||
status=(status if status else alert.status).capitalize(),
|
||||
environment=alert.environment,
|
||||
severity=alert.severity,
|
||||
# severity=alert.severity,
|
||||
event=alert.event,
|
||||
resource=alert.resource
|
||||
)
|
||||
|
||||
return {
|
||||
"channel": ROCKETCHAT_CHANNEL,
|
||||
"text": text,
|
||||
"alias": ALERTA_USERNAME,
|
||||
"emoji": ICON_EMOJI,
|
||||
"attachments": [{
|
||||
"title": title,
|
||||
"title_link": '%s/#/alert/%s' % (DASHBOARD_URL, alert.id),
|
||||
"text": alert.text,
|
||||
"color": DEFAULT_SEVERITY_MAP.get(alert.severity, DEFAULT_SEVERITY_MAP['ok']),
|
||||
"fields": [
|
||||
{"title": "Status", "value": (status if status else alert.status).capitalize(), "short": True},
|
||||
{"title": "Environment", "value": alert.environment, "short": True},
|
||||
{"title": "Resource", "value": alert.resource, "short": True},
|
||||
{"title": "Services", "value": ", ".join(alert.service), "short": True}
|
||||
]
|
||||
'channel': ROCKETCHAT_CHANNEL,
|
||||
'text': text,
|
||||
'alias': ALERTA_USERNAME,
|
||||
'emoji': ICON_EMOJI,
|
||||
'attachments': [{
|
||||
'title': title,
|
||||
'title_link': '{}/#/alert/{}'.format(DASHBOARD_URL, alert.id),
|
||||
'text': alert.text,
|
||||
'color': DEFAULT_SEVERITY_MAP.get(alert.severity, DEFAULT_SEVERITY_MAP['ok']),
|
||||
'fields': [
|
||||
{'title': 'Status', 'value': (
|
||||
status if status else alert.status).capitalize(), 'short': True},
|
||||
{'title': 'Environment', 'value': alert.environment, 'short': True},
|
||||
{'title': 'Resource', 'value': alert.resource, 'short': True},
|
||||
{'title': 'Services', 'value': ', '.join(
|
||||
alert.service), 'short': True}
|
||||
]
|
||||
}]
|
||||
}
|
||||
|
||||
|
@ -81,6 +88,6 @@ class PostMessage(PluginBase):
|
|||
try:
|
||||
r = requests.post(ROCKETCHAT_WEBHOOK_URL, json=payload, timeout=2)
|
||||
except Exception as e:
|
||||
raise RuntimeError("Rocket.Chat: ERROR - %s" % e)
|
||||
raise RuntimeError('Rocket.Chat: ERROR - %s' % e)
|
||||
|
||||
LOG.debug('Rocket.Chat: %s - %s', r.status_code, r.text)
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.0.0'
|
||||
|
||||
setup(
|
||||
name="alerta-rocketchat",
|
||||
name='alerta-rocketchat',
|
||||
version=version,
|
||||
description='Alerta plugin for Rocket.Chat',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -115,8 +115,8 @@ SLACK_PAYLOAD = {
|
|||
|
||||
Slack Apps API
|
||||
--------------
|
||||
To use the Slack "Apps" API instead of an Incoming Webhook, create an application and
|
||||
obtain its OAuth token. Use that to set ```SLACK_TOKEN``` and specify the
|
||||
To use the Slack "Apps" API instead of an Incoming Webhook, create an application and
|
||||
obtain its OAuth token. Use that to set ```SLACK_TOKEN``` and specify the
|
||||
URL endpoint to the new API entrypoint this way:
|
||||
|
||||
```python
|
||||
|
|
|
@ -1,24 +1,25 @@
|
|||
|
||||
import ast
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import requests
|
||||
import traceback
|
||||
import ast
|
||||
|
||||
import requests
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.slack')
|
||||
|
||||
try:
|
||||
from jinja2 import Template
|
||||
except Exception as e:
|
||||
LOG.error('SLACK: ERROR - Jinja template error: %s, template functionality will be unavailable', e)
|
||||
LOG.error(
|
||||
'SLACK: ERROR - Jinja template error: %s, template functionality will be unavailable', e)
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.slack')
|
||||
|
||||
|
@ -27,46 +28,47 @@ SLACK_ATTACHMENTS = True if os.environ.get(
|
|||
try:
|
||||
SLACK_CHANNEL_ENV_MAP = json.loads(
|
||||
os.environ.get('SLACK_CHANNEL_ENV_MAP'))
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
SLACK_CHANNEL_ENV_MAP = app.config.get('SLACK_CHANNEL_ENV_MAP', dict())
|
||||
|
||||
try:
|
||||
SLACK_CHANNEL_EVENT_MAP = json.loads(
|
||||
os.environ.get('SLACK_CHANNEL_EVENT_MAP'))
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
SLACK_CHANNEL_EVENT_MAP = app.config.get('SLACK_CHANNEL_EVENT_MAP', dict())
|
||||
|
||||
try:
|
||||
SLACK_CHANNEL_SEVERITY_MAP = json.loads(
|
||||
os.environ.get('SLACK_CHANNEL_SEVERITY_MAP'))
|
||||
except Exception as e:
|
||||
SLACK_CHANNEL_SEVERITY_MAP = app.config.get('SLACK_CHANNEL_SEVERITY_MAP', dict())
|
||||
except Exception:
|
||||
SLACK_CHANNEL_SEVERITY_MAP = app.config.get(
|
||||
'SLACK_CHANNEL_SEVERITY_MAP', dict())
|
||||
|
||||
try:
|
||||
SLACK_CHANNEL_MAP = json.loads(
|
||||
os.environ.get('SLACK_CHANNEL_MAP'))
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
SLACK_CHANNEL_MAP = app.config.get('SLACK_CHANNEL_MAP', dict())
|
||||
|
||||
try:
|
||||
SLACK_SEVERITY_FILTER = ast.literal_eval(
|
||||
os.environ.get('SLACK_SEVERITY_FILTER'))
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
SLACK_SEVERITY_FILTER = app.config.get('SLACK_SEVERITY_FILTER', list())
|
||||
|
||||
SLACK_SEND_ON_ACK = os.environ.get(
|
||||
'SLACK_SEND_ON_ACK') or app.config.get('SLACK_SEND_ON_ACK', False)
|
||||
SLACK_SEVERITY_MAP = app.config.get('SLACK_SEVERITY_MAP', {})
|
||||
SLACK_DEFAULT_SEVERITY_MAP = {'security': '#000000', # black
|
||||
'critical': '#FF0000', # red
|
||||
'major': '#FFA500', # orange
|
||||
'minor': '#FFFF00', # yellow
|
||||
'warning': '#1E90FF', #blue
|
||||
'informational': '#808080', #gray
|
||||
'debug': '#808080', # gray
|
||||
'trace': '#808080', # gray
|
||||
'ok': '#00CC00'} # green
|
||||
SLACK_DEFAULT_SUMMARY_FMT='*[{status}] {environment} {service} {severity}* - _{event} on {resource}_ <{dashboard}/#/alert/{alert_id}|{short_id}>'
|
||||
SLACK_DEFAULT_SEVERITY_MAP = {'security': '#000000', # black
|
||||
'critical': '#FF0000', # red
|
||||
'major': '#FFA500', # orange
|
||||
'minor': '#FFFF00', # yellow
|
||||
'warning': '#1E90FF', # blue
|
||||
'informational': '#808080', # gray
|
||||
'debug': '#808080', # gray
|
||||
'trace': '#808080', # gray
|
||||
'ok': '#00CC00'} # green
|
||||
SLACK_DEFAULT_SUMMARY_FMT = '*[{status}] {environment} {service} {severity}* - _{event} on {resource}_ <{dashboard}/#/alert/{alert_id}|{short_id}>'
|
||||
SLACK_HEADERS = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
@ -79,7 +81,7 @@ class ServiceIntegration(PluginBase):
|
|||
self._severities = SLACK_DEFAULT_SEVERITY_MAP
|
||||
self._severities.update(SLACK_SEVERITY_MAP)
|
||||
|
||||
super(ServiceIntegration, self).__init__(name)
|
||||
super().__init__(name)
|
||||
|
||||
def pre_receive(self, alert):
|
||||
return alert
|
||||
|
@ -101,12 +103,17 @@ class ServiceIntegration(PluginBase):
|
|||
return
|
||||
|
||||
def _slack_prepare_payload(self, alert, status=None, text=None, **kwargs):
|
||||
SLACK_CHANNEL = self.get_config('SLACK_CHANNEL', default='', type=str, **kwargs)
|
||||
SLACK_SUMMARY_FMT = self.get_config('SLACK_SUMMARY_FMT', type=str, **kwargs) # Message summary format
|
||||
SLACK_PAYLOAD = self.get_config('SLACK_PAYLOAD', type=str, **kwargs) # Full API control
|
||||
SLACK_CHANNEL = self.get_config(
|
||||
'SLACK_CHANNEL', default='', type=str, **kwargs)
|
||||
SLACK_SUMMARY_FMT = self.get_config(
|
||||
'SLACK_SUMMARY_FMT', type=str, **kwargs) # Message summary format
|
||||
SLACK_PAYLOAD = self.get_config(
|
||||
'SLACK_PAYLOAD', type=str, **kwargs) # Full API control
|
||||
ICON_EMOJI = self.get_config('ICON_EMOJI', type=str, **kwargs)
|
||||
ALERTA_USERNAME = self.get_config('ALERTA_USERNAME', default='alerta', type=str, **kwargs)
|
||||
DASHBOARD_URL = self.get_config('DASHBOARD_URL', default='', type=str, **kwargs)
|
||||
ALERTA_USERNAME = self.get_config(
|
||||
'ALERTA_USERNAME', default='alerta', type=str, **kwargs)
|
||||
DASHBOARD_URL = self.get_config(
|
||||
'DASHBOARD_URL', default='', type=str, **kwargs)
|
||||
SLACK_TOKEN = self.get_config('SLACK_TOKEN', type=str, **kwargs)
|
||||
if SLACK_TOKEN:
|
||||
SLACK_HEADERS['Authorization'] = 'Bearer ' + SLACK_TOKEN
|
||||
|
@ -117,24 +124,25 @@ class ServiceIntegration(PluginBase):
|
|||
color = '#00CC00' # green
|
||||
channel = SLACK_CHANNEL_SEVERITY_MAP.get(alert.severity, SLACK_CHANNEL)
|
||||
if SLACK_CHANNEL_SEVERITY_MAP.get(alert.severity):
|
||||
LOG.debug("Found severity mapping. Channel: %s" % channel)
|
||||
LOG.debug('Found severity mapping. Channel: %s' % channel)
|
||||
else:
|
||||
LOG.debug("No severity mapping. Channel: %s" % channel)
|
||||
LOG.debug('No severity mapping. Channel: %s' % channel)
|
||||
channel = SLACK_CHANNEL_ENV_MAP.get(alert.environment, channel)
|
||||
if SLACK_CHANNEL_ENV_MAP.get(alert.environment):
|
||||
LOG.debug("Found env mapping. Channel: %s" % channel)
|
||||
LOG.debug('Found env mapping. Channel: %s' % channel)
|
||||
else:
|
||||
LOG.debug("No env mapping. Channel: %s" % channel)
|
||||
LOG.debug('No env mapping. Channel: %s' % channel)
|
||||
channel = SLACK_CHANNEL_EVENT_MAP.get(alert.event, channel)
|
||||
if SLACK_CHANNEL_EVENT_MAP.get(alert.event):
|
||||
LOG.debug("Found event mapping. Channel: %s" % channel)
|
||||
LOG.debug('Found event mapping. Channel: %s' % channel)
|
||||
else:
|
||||
LOG.debug("No event mapping. Channel: %s" % channel)
|
||||
channel = SLACK_CHANNEL_MAP.get(alert.environment, dict()).get(alert.severity, channel)
|
||||
LOG.debug('No event mapping. Channel: %s' % channel)
|
||||
channel = SLACK_CHANNEL_MAP.get(
|
||||
alert.environment, dict()).get(alert.severity, channel)
|
||||
if SLACK_CHANNEL_MAP.get(alert.environment, dict()).get(alert.severity, channel):
|
||||
LOG.debug("Found env-severity mapping. Channel: %s" % channel)
|
||||
LOG.debug('Found env-severity mapping. Channel: %s' % channel)
|
||||
else:
|
||||
LOG.debug("No env-severity mapping. Channel: %s" % channel)
|
||||
LOG.debug('No env-severity mapping. Channel: %s' % channel)
|
||||
|
||||
templateVars = {
|
||||
'alert': alert,
|
||||
|
@ -146,13 +154,15 @@ class ServiceIntegration(PluginBase):
|
|||
}
|
||||
|
||||
if SLACK_PAYLOAD:
|
||||
LOG.debug("Formatting with slack payload template")
|
||||
formattedPayload = self._format_template(json.dumps(SLACK_PAYLOAD), templateVars).replace('\n', '\\n')
|
||||
LOG.debug("Formatted slack payload:\n%s" % formattedPayload)
|
||||
LOG.debug('Formatting with slack payload template')
|
||||
formattedPayload = self._format_template(json.dumps(
|
||||
SLACK_PAYLOAD), templateVars).replace('\n', '\\n')
|
||||
LOG.debug('Formatted slack payload:\n%s' % formattedPayload)
|
||||
payload = json.loads(formattedPayload)
|
||||
else:
|
||||
if type(SLACK_SUMMARY_FMT) is str:
|
||||
summary = self._format_template(SLACK_SUMMARY_FMT, templateVars)
|
||||
summary = self._format_template(
|
||||
SLACK_SUMMARY_FMT, templateVars)
|
||||
else:
|
||||
summary = SLACK_DEFAULT_SUMMARY_FMT.format(
|
||||
status=alert.status.capitalize(),
|
||||
|
@ -173,68 +183,75 @@ class ServiceIntegration(PluginBase):
|
|||
payload['icon_emoji'] = ICON_EMOJI
|
||||
if SLACK_ATTACHMENTS:
|
||||
payload['attachments'] = [{
|
||||
"fallback": summary,
|
||||
"color": color,
|
||||
"fields": [
|
||||
{"title": "Status", "value": (status if status else alert.status).capitalize(),
|
||||
"short": True},
|
||||
{"title": "Environment",
|
||||
"value": alert.environment, "short": True},
|
||||
{"title": "Resource", "value": alert.resource, "short": True},
|
||||
{"title": "Services", "value": ", ".join(
|
||||
alert.service), "short": True}
|
||||
'fallback': summary,
|
||||
'color': color,
|
||||
'fields': [
|
||||
{'title': 'Status', 'value': (status if status else alert.status).capitalize(),
|
||||
'short': True},
|
||||
{'title': 'Environment',
|
||||
'value': alert.environment, 'short': True},
|
||||
{'title': 'Resource', 'value': alert.resource, 'short': True},
|
||||
{'title': 'Services', 'value': ', '.join(
|
||||
alert.service), 'short': True}
|
||||
]
|
||||
}]
|
||||
|
||||
return payload
|
||||
|
||||
def post_receive(self, alert, **kwargs):
|
||||
SLACK_WEBHOOK_URL = self.get_config('SLACK_WEBHOOK_URL', type=str, **kwargs)
|
||||
SLACK_WEBHOOK_URL = self.get_config(
|
||||
'SLACK_WEBHOOK_URL', type=str, **kwargs)
|
||||
|
||||
if alert.repeat:
|
||||
return
|
||||
|
||||
if alert.severity in SLACK_SEVERITY_FILTER:
|
||||
LOG.debug("Alert severity %s is included in SLACK_SEVERITY_FILTER list, thus it will not be forwarded to Slack." % alert.severity)
|
||||
LOG.debug(
|
||||
'Alert severity %s is included in SLACK_SEVERITY_FILTER list, thus it will not be forwarded to Slack.' % alert.severity)
|
||||
return
|
||||
|
||||
if alert.severity in ['ok', 'normal', 'cleared', app.config.get('DEFAULT_NORMAL_SEVERITY')] and alert.previous_severity in SLACK_SEVERITY_FILTER:
|
||||
LOG.debug("Alert severity is %s but previous_severity was %s (included in SLACK_SEVERITY_FILTER list), thus it will not be forwarded to Slack." % (alert.severity, alert.previous_severity))
|
||||
LOG.debug('Alert severity is {} but previous_severity was {} (included in SLACK_SEVERITY_FILTER list), thus it will not be forwarded to Slack.'.format(
|
||||
alert.severity, alert.previous_severity))
|
||||
return
|
||||
|
||||
try:
|
||||
payload = self._slack_prepare_payload(alert, **kwargs)
|
||||
LOG.debug('Slack payload: %s', payload)
|
||||
except Exception as e:
|
||||
LOG.error('Exception formatting payload: %s\n%s' % (e, traceback.format_exc()))
|
||||
LOG.error('Exception formatting payload: %s\n%s' %
|
||||
(e, traceback.format_exc()))
|
||||
return
|
||||
|
||||
try:
|
||||
r = requests.post(SLACK_WEBHOOK_URL,
|
||||
data=json.dumps(payload), headers=SLACK_HEADERS, timeout=2)
|
||||
except Exception as e:
|
||||
raise RuntimeError("Slack connection error: %s", e)
|
||||
raise RuntimeError('Slack connection error: %s', e)
|
||||
|
||||
LOG.debug('Slack response: %s\n%s' % (r.status_code, r.text))
|
||||
LOG.debug('Slack response: {}\n{}'.format(r.status_code, r.text))
|
||||
|
||||
def status_change(self, alert, status, text, **kwargs):
|
||||
SLACK_WEBHOOK_URL = self.get_config('SLACK_WEBHOOK_URL', type=str, **kwargs)
|
||||
SLACK_WEBHOOK_URL = self.get_config(
|
||||
'SLACK_WEBHOOK_URL', type=str, **kwargs)
|
||||
|
||||
if SLACK_SEND_ON_ACK == False or status not in ['ack', 'assign']:
|
||||
if not SLACK_SEND_ON_ACK or status not in ['ack', 'assign']:
|
||||
return
|
||||
|
||||
try:
|
||||
payload = self._slack_prepare_payload(alert, status, text, **kwargs)
|
||||
payload = self._slack_prepare_payload(
|
||||
alert, status, text, **kwargs)
|
||||
|
||||
LOG.debug('Slack payload: %s', payload)
|
||||
except Exception as e:
|
||||
LOG.error('Exception formatting payload: %s\n%s' % (e, traceback.format_exc()))
|
||||
LOG.error('Exception formatting payload: %s\n%s' %
|
||||
(e, traceback.format_exc()))
|
||||
return
|
||||
|
||||
try:
|
||||
r = requests.post(SLACK_WEBHOOK_URL,
|
||||
data=json.dumps(payload), headers=SLACK_HEADERS, timeout=2)
|
||||
except Exception as e:
|
||||
raise RuntimeError("Slack connection error: %s", e)
|
||||
raise RuntimeError('Slack connection error: %s', e)
|
||||
|
||||
LOG.debug('Slack response: %s\n%s' % (r.status_code, r.text))
|
||||
LOG.debug('Slack response: {}\n{}'.format(r.status_code, r.text))
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.5.2'
|
||||
|
||||
setup(
|
||||
name="alerta-slack",
|
||||
name='alerta-slack',
|
||||
version=version,
|
||||
description='Alerta plugin for Slack',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -70,8 +70,10 @@ class SlackPluginTestCase(unittest.TestCase):
|
|||
'tags': []
|
||||
}
|
||||
|
||||
response = self.client.post('/alert', data=json.dumps(self.alert), headers={'Content-type': 'application/json'})
|
||||
response = self.client.post(
|
||||
'/alert', data=json.dumps(self.alert), headers={'Content-type': 'application/json'})
|
||||
self.assertEqual(response.status_code, 201)
|
||||
data = json.loads(response.data.decode('utf-8'))
|
||||
self.assertEqual(data['status'], 'ok')
|
||||
self.assertRegex(data['id'], '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
|
||||
self.assertRegex(
|
||||
data['id'], '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
|
||||
|
|
|
@ -1,24 +1,28 @@
|
|||
import logging
|
||||
import os
|
||||
|
||||
import boto.exception
|
||||
import boto.sns
|
||||
import logging
|
||||
import os
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.sns')
|
||||
|
||||
DEFAULT_AWS_REGION = 'eu-west-1'
|
||||
DEFAULT_AWS_SNS_TOPIC = 'notify'
|
||||
|
||||
AWS_REGION = os.environ.get('AWS_REGION') or app.config.get('AWS_REGION', DEFAULT_AWS_REGION)
|
||||
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID') or app.config.get('AWS_ACCESS_KEY_ID')
|
||||
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY') or app.config.get('AWS_SECRET_ACCESS_KEY')
|
||||
AWS_SNS_TOPIC = os.environ.get('AWS_SNS_TOPIC') or app.config.get('AWS_SNS_TOPIC', DEFAULT_AWS_SNS_TOPIC)
|
||||
AWS_REGION = os.environ.get('AWS_REGION') or app.config.get(
|
||||
'AWS_REGION', DEFAULT_AWS_REGION)
|
||||
AWS_ACCESS_KEY_ID = os.environ.get(
|
||||
'AWS_ACCESS_KEY_ID') or app.config.get('AWS_ACCESS_KEY_ID')
|
||||
AWS_SECRET_ACCESS_KEY = os.environ.get(
|
||||
'AWS_SECRET_ACCESS_KEY') or app.config.get('AWS_SECRET_ACCESS_KEY')
|
||||
AWS_SNS_TOPIC = os.environ.get('AWS_SNS_TOPIC') or app.config.get(
|
||||
'AWS_SNS_TOPIC', DEFAULT_AWS_SNS_TOPIC)
|
||||
|
||||
|
||||
class SnsTopicPublisher(PluginBase):
|
||||
|
@ -35,7 +39,8 @@ class SnsTopicPublisher(PluginBase):
|
|||
raise RuntimeError
|
||||
|
||||
if not self.connection:
|
||||
LOG.error('Failed to connect to SNS topic %s - check AWS credentials and region', AWS_SNS_TOPIC)
|
||||
LOG.error(
|
||||
'Failed to connect to SNS topic %s - check AWS credentials and region', AWS_SNS_TOPIC)
|
||||
raise RuntimeError
|
||||
|
||||
try:
|
||||
|
@ -50,7 +55,7 @@ class SnsTopicPublisher(PluginBase):
|
|||
LOG.error('Failed to get SNS TopicArn for %s', AWS_SNS_TOPIC)
|
||||
raise RuntimeError
|
||||
|
||||
super(SnsTopicPublisher, self).__init__(name)
|
||||
super().__init__(name)
|
||||
|
||||
LOG.info('Configured SNS publisher on topic "%s"', self.topic_arn)
|
||||
|
||||
|
@ -59,10 +64,12 @@ class SnsTopicPublisher(PluginBase):
|
|||
|
||||
def post_receive(self, alert):
|
||||
|
||||
LOG.info('Sending message %s to SNS topic "%s"', alert.get_id(), self.topic_arn)
|
||||
LOG.info('Sending message %s to SNS topic "%s"',
|
||||
alert.get_id(), self.topic_arn)
|
||||
LOG.debug('Message: %s', alert.get_body())
|
||||
|
||||
response = self.connection.publish(topic=self.topic_arn, message=alert.get_body())
|
||||
response = self.connection.publish(
|
||||
topic=self.topic_arn, message=alert.get_body())
|
||||
LOG.debug('Response: %s', response)
|
||||
|
||||
def status_change(self, alert, status, text):
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.3.1'
|
||||
|
||||
setup(
|
||||
name="alerta-sns",
|
||||
name='alerta-sns',
|
||||
version=version,
|
||||
description='Alerta plugin for AWS SNS',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from logging.handlers import SysLogHandler
|
||||
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.logger')
|
||||
|
||||
|
@ -16,9 +16,12 @@ DEFAULT_SYSLOG_FORMAT = '%(name)s[%(process)d]: %(levelname)s - %(message)s'
|
|||
DEFAULT_SYSLOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
|
||||
DEFAULT_SYSLOG_FACILITY = 'local7'
|
||||
|
||||
LOGGER_SYSLOG_FORMAT = os.environ.get('LOGGER_SYSLOG_FORMAT') or app.config.get('SYSLOG_FORMAT', DEFAULT_SYSLOG_FORMAT)
|
||||
LOGGER_SYSLOG_DATE_FORMAT = os.environ.get('LOGGER_SYSLOG_DATE_FORMAT') or app.config.get('SYSLOG_DATE_FORMAT', DEFAULT_SYSLOG_DATE_FORMAT)
|
||||
LOGGER_SYSLOG_FACILITY = os.environ.get('LOGGER_SYSLOG_FACILITY') or app.config.get('SYSLOG_FACILITY', DEFAULT_SYSLOG_FACILITY)
|
||||
LOGGER_SYSLOG_FORMAT = os.environ.get('LOGGER_SYSLOG_FORMAT') or app.config.get(
|
||||
'SYSLOG_FORMAT', DEFAULT_SYSLOG_FORMAT)
|
||||
LOGGER_SYSLOG_DATE_FORMAT = os.environ.get('LOGGER_SYSLOG_DATE_FORMAT') or app.config.get(
|
||||
'SYSLOG_DATE_FORMAT', DEFAULT_SYSLOG_DATE_FORMAT)
|
||||
LOGGER_SYSLOG_FACILITY = os.environ.get('LOGGER_SYSLOG_FACILITY') or app.config.get(
|
||||
'SYSLOG_FACILITY', DEFAULT_SYSLOG_FACILITY)
|
||||
|
||||
|
||||
class Syslog(PluginBase):
|
||||
|
@ -27,17 +30,18 @@ class Syslog(PluginBase):
|
|||
|
||||
self.logger = logging.getLogger(name)
|
||||
|
||||
if sys.platform == "darwin":
|
||||
if sys.platform == 'darwin':
|
||||
socket = '/var/run/syslog'
|
||||
else:
|
||||
socket = '/dev/log'
|
||||
facility = LOGGER_SYSLOG_FACILITY
|
||||
|
||||
syslog = SysLogHandler(address=socket, facility=facility)
|
||||
syslog.setFormatter(logging.Formatter(fmt=LOGGER_SYSLOG_FORMAT, datefmt=LOGGER_SYSLOG_DATE_FORMAT))
|
||||
syslog.setFormatter(logging.Formatter(
|
||||
fmt=LOGGER_SYSLOG_FORMAT, datefmt=LOGGER_SYSLOG_DATE_FORMAT))
|
||||
self.logger.addHandler(syslog)
|
||||
|
||||
super(Syslog, self).__init__(name)
|
||||
super().__init__(name)
|
||||
|
||||
def pre_receive(self, alert):
|
||||
return alert
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.3.2'
|
||||
|
||||
setup(
|
||||
name="alerta-logger",
|
||||
name='alerta-logger',
|
||||
version=version,
|
||||
description='Alerta plugin for syslog logging',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import logging
|
||||
import os
|
||||
|
||||
import telepot
|
||||
from alerta.plugins import PluginBase
|
||||
from jinja2 import Template, UndefinedError
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
import telepot
|
||||
from jinja2 import Template, UndefinedError
|
||||
|
||||
DEFAULT_TMPL = """
|
||||
DEFAULT_TMPL = r"""
|
||||
{% if customer %}Customer: `{{customer}}` {% endif %}
|
||||
|
||||
*[{{ status.capitalize() }}] {{ environment }} {{ severity.capitalize() }}*
|
||||
|
@ -24,26 +25,26 @@ DEFAULT_TMPL = """
|
|||
LOG = logging.getLogger('alerta.plugins.telegram')
|
||||
|
||||
TELEGRAM_TOKEN = app.config.get('TELEGRAM_TOKEN') \
|
||||
or os.environ.get('TELEGRAM_TOKEN')
|
||||
or os.environ.get('TELEGRAM_TOKEN')
|
||||
TELEGRAM_CHAT_ID = app.config.get('TELEGRAM_CHAT_ID') \
|
||||
or os.environ.get('TELEGRAM_CHAT_ID')
|
||||
or os.environ.get('TELEGRAM_CHAT_ID')
|
||||
TELEGRAM_WEBHOOK_URL = app.config.get('TELEGRAM_WEBHOOK_URL', None) \
|
||||
or os.environ.get('TELEGRAM_WEBHOOK_URL')
|
||||
or os.environ.get('TELEGRAM_WEBHOOK_URL')
|
||||
TELEGRAM_TEMPLATE = app.config.get('TELEGRAM_TEMPLATE') \
|
||||
or os.environ.get('TELEGRAM_TEMPLATE')
|
||||
or os.environ.get('TELEGRAM_TEMPLATE')
|
||||
TELEGRAM_PROXY = app.config.get('TELEGRAM_PROXY') \
|
||||
or os.environ.get('TELEGRAM_PROXY')
|
||||
or os.environ.get('TELEGRAM_PROXY')
|
||||
TELEGRAM_PROXY_USERNAME = app.config.get('TELEGRAM_PROXY_USERNAME') \
|
||||
or os.environ.get('TELEGRAM_PROXY_USERNAME')
|
||||
or os.environ.get('TELEGRAM_PROXY_USERNAME')
|
||||
TELEGRAM_PROXY_PASSWORD = app.config.get('TELEGRAM_PROXY_PASSWORD') \
|
||||
or os.environ.get('TELEGRAM_PROXY_PASSWORD')
|
||||
or os.environ.get('TELEGRAM_PROXY_PASSWORD')
|
||||
TELEGRAM_SOUND_NOTIFICATION_SEVERITY = app.config.get('TELEGRAM_SOUND_NOTIFICATION_SEVERITY') \
|
||||
or os.environ.get('TELEGRAM_SOUND_NOTIFICATION_SEVERITY')
|
||||
or os.environ.get('TELEGRAM_SOUND_NOTIFICATION_SEVERITY')
|
||||
TELEGRAM_DISABLE_NOTIFICATION_SEVERITY = app.config.get('TELEGRAM_DISABLE_NOTIFICATION_SEVERITY') \
|
||||
or os.environ.get('TELEGRAM_DISABLE_NOTIFICATION_SEVERITY')
|
||||
or os.environ.get('TELEGRAM_DISABLE_NOTIFICATION_SEVERITY')
|
||||
|
||||
DASHBOARD_URL = app.config.get('DASHBOARD_URL', '') \
|
||||
or os.environ.get('DASHBOARD_URL')
|
||||
or os.environ.get('DASHBOARD_URL')
|
||||
|
||||
# use all the same, but telepot.aio.api.set_proxy for async telepot
|
||||
if all([TELEGRAM_PROXY, TELEGRAM_PROXY_USERNAME, TELEGRAM_PROXY_PASSWORD]):
|
||||
|
@ -54,6 +55,7 @@ elif TELEGRAM_PROXY is not None:
|
|||
telepot.api.set_proxy(TELEGRAM_PROXY)
|
||||
LOG.debug('Telegram: using proxy %s', TELEGRAM_PROXY)
|
||||
|
||||
|
||||
class TelegramBot(PluginBase):
|
||||
def __init__(self, name=None):
|
||||
|
||||
|
@ -61,14 +63,14 @@ class TelegramBot(PluginBase):
|
|||
LOG.debug('Telegram: %s', self.bot.getMe())
|
||||
|
||||
if TELEGRAM_WEBHOOK_URL and \
|
||||
TELEGRAM_WEBHOOK_URL != self.bot.getWebhookInfo()['url']:
|
||||
TELEGRAM_WEBHOOK_URL != self.bot.getWebhookInfo()['url']:
|
||||
self.bot.setWebhook(TELEGRAM_WEBHOOK_URL)
|
||||
LOG.debug('Telegram: %s', self.bot.getWebhookInfo())
|
||||
|
||||
super(TelegramBot, self).__init__(name)
|
||||
super().__init__(name)
|
||||
if TELEGRAM_TEMPLATE:
|
||||
if os.path.exists(TELEGRAM_TEMPLATE):
|
||||
with open(TELEGRAM_TEMPLATE, 'r') as f:
|
||||
with open(TELEGRAM_TEMPLATE) as f:
|
||||
self.template = Template(f.read())
|
||||
else:
|
||||
self.template = Template(TELEGRAM_TEMPLATE)
|
||||
|
@ -86,7 +88,7 @@ class TelegramBot(PluginBase):
|
|||
try:
|
||||
text = self.template.render(alert.__dict__)
|
||||
except UndefinedError:
|
||||
text = "Something bad has happened but also we " \
|
||||
text = 'Something bad has happened but also we ' \
|
||||
"can't handle your telegram template message."
|
||||
|
||||
LOG.debug('Telegram: message=%s', text)
|
||||
|
@ -117,12 +119,14 @@ class TelegramBot(PluginBase):
|
|||
return
|
||||
|
||||
if alert.severity in ['ok', 'normal', 'cleared', app.config.get('DEFAULT_NORMAL_SEVERITY')] and alert.previous_severity in TELEGRAM_DISABLE_NOTIFICATION_SEVERITY:
|
||||
LOG.debug("Alert severity is %s but previous_severity was %s (included in DEFAULT_NORMAL_SEVERITY list), thus it will not be forwarded to Telegram." % (alert.severity, alert.previous_severity))
|
||||
LOG.debug('Alert severity is {} but previous_severity was {} (included in DEFAULT_NORMAL_SEVERITY list), thus it will not be forwarded to Telegram.'.format(
|
||||
alert.severity, alert.previous_severity))
|
||||
return
|
||||
|
||||
LOG.debug('Telegram: post_receive sendMessage disable_notification=%s', str(disable_notification))
|
||||
LOG.debug('Telegram: post_receive sendMessage disable_notification=%s', str(
|
||||
disable_notification))
|
||||
|
||||
chat_ids = TELEGRAM_CHAT_ID.split(",")
|
||||
chat_ids = TELEGRAM_CHAT_ID.split(',')
|
||||
for chat_id in chat_ids:
|
||||
try:
|
||||
response = self.bot.sendMessage(chat_id,
|
||||
|
@ -131,13 +135,13 @@ class TelegramBot(PluginBase):
|
|||
disable_notification=disable_notification,
|
||||
reply_markup=keyboard)
|
||||
except telepot.exception.TelegramError as e:
|
||||
raise RuntimeError("Telegram (ChatId: %s): ERROR - %s, description= %s, json=%s",
|
||||
chat_id,
|
||||
e.error_code,
|
||||
e.description,
|
||||
e.json)
|
||||
raise RuntimeError('Telegram (ChatId: %s): ERROR - %s, description= %s, json=%s',
|
||||
chat_id,
|
||||
e.error_code,
|
||||
e.description,
|
||||
e.json)
|
||||
except Exception as e:
|
||||
raise RuntimeError("Telegram: ERROR - %s", e)
|
||||
raise RuntimeError('Telegram: ERROR - %s', e)
|
||||
|
||||
LOG.debug('Telegram (ChatId: %s): %s', chat_id, response)
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = '5.1.3'
|
||||
|
||||
setup(
|
||||
name="alerta-telegram",
|
||||
name='alerta-telegram',
|
||||
version=version,
|
||||
description='Alerta plugin for Telegram',
|
||||
url='https://github.com/alerta/alerta-contrib',
|
||||
|
|
|
@ -16,7 +16,7 @@ setting and the default global timeout value may not be too large.
|
|||
|
||||
|
||||
Timeout actions:
|
||||
|
||||
|
||||
* The alert 'timeout' attribute is (re)set for each alert to the value specified
|
||||
|
||||
|
||||
|
@ -57,4 +57,3 @@ Restart Alerta API and confirm that the plugin has been loaded and enabled.
|
|||
|
||||
Set `DEBUG=True` in the `alertad.conf` configuration file and look for log
|
||||
entries referencing `Setting timeout for alert to 2400`
|
||||
|
||||
|
|
|
@ -1,22 +1,24 @@
|
|||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
try:
|
||||
from alerta.plugins import app # alerta >= 5.0
|
||||
except ImportError:
|
||||
from alerta.app import app # alerta < 5.0
|
||||
from alerta.plugins import PluginBase
|
||||
|
||||
LOG = logging.getLogger('alerta.plugins.timeout')
|
||||
|
||||
TIMEOUT = os.environ.get('ALERT_TIMEOUT') or app.config.get('ALERT_TIMEOUT', '2600')
|
||||
TIMEOUT = os.environ.get('ALERT_TIMEOUT') or app.config.get(
|
||||
'ALERT_TIMEOUT', '2600')
|
||||
|
||||
|
||||
class Timeout(PluginBase):
|
||||
|
||||
def pre_receive(self, alert):
|
||||
|
||||
LOG.debug("Setting timeout for alert to %s ",TIMEOUT)
|
||||
LOG.debug('Setting timeout for alert to %s ', TIMEOUT)
|
||||
alert.timeout = TIMEOUT
|
||||
|
||||
return alert
|
||||
|
@ -25,4 +27,4 @@ class Timeout(PluginBase):
|
|||
return
|
||||
|
||||
def status_change(self, alert, status, text):
|
||||
return
|
||||
return
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue