Compare commits

...

10 commits

Author SHA1 Message Date
4f854bca8b
fix: Remove $SERVICEDESC$ from host notification
Signed-off-by: Julien Riou <julien@riou.xyz>
2022-05-09 09:57:37 +02:00
df5296af42
Escape '=' to produce valid markdown (#1)
Signed-off-by: Julien Riou <julien@riou.xyz>
2021-03-25 10:39:11 +01:00
0cd101954c
Remove --service-desc argument for hosts
Signed-off-by: Julien Riou <julien@riou.xyz>
2020-11-26 15:35:59 +01:00
7cdc46954e
Add requirements.txt file
Signed-off-by: Julien Riou <julien@riou.xyz>
2020-11-26 15:32:42 +01:00
ca3adb00ce
Add a contribution/lint guide
Signed-off-by: Julien Riou <julien@riou.xyz>
2020-11-26 15:25:09 +01:00
7fa647c953
Update README with template variables
Signed-off-by: Julien Riou <julien@riou.xyz>
2020-11-26 15:09:28 +01:00
2b5a263d48
Add configuration file to README
Signed-off-by: Julien Riou <julien@riou.xyz>
2020-11-26 14:58:40 +01:00
17f0948aeb
Add configuration validation with jsonschema
Signed-off-by: Julien Riou <julien@riou.xyz>
2020-11-26 14:50:59 +01:00
a7c9f1455e
Factorize payload generation
Signed-off-by: Julien Riou <julien@riou.xyz>
2020-11-26 14:19:54 +01:00
a0507d546a
Update typo in README
Signed-off-by: Julien Riou <julien@riou.xyz>
2020-11-26 13:02:08 +01:00
9 changed files with 154 additions and 45 deletions

View file

@ -11,6 +11,8 @@ repos:
args: ['--remove']
- id: requirements-txt-fixer
- id: trailing-whitespace
- id: check-json
- id: check-yaml
- repo: https://gitlab.com/pycqa/flake8
rev: master
hooks:

5
Dockerfile Normal file
View file

@ -0,0 +1,5 @@
FROM python:3.8-slim
RUN apt-get update \
&& apt-get install -y git \
&& apt-get autoclean \
&& pip install pre-commit

View file

@ -15,19 +15,25 @@ Clone the repository:
git clone https://github.com/jouir/notify-by-telegram.git /opt/notify-by-telegram
```
Install dependencies using the package manager:
Install dependencies using pip:
```
sudo apt install python3-jinja2 python3-requests
pip install -r requirements.txt
```
Or via the package manager:
```
sudo apt install python3-jinja2 python3-requests python3-jsonschema
```
## Configuration
Copy and update the configuration file example:
```
cp -p config.json.example telegram.json
cp -p config.example.json telegram.json
vim telegram.json
sudo mv telegram.json /etc/nagios4/telegram.json
sudo chmod 640 root:nagios /etc/nagios4/telegram.json
sudo chown root:nagios /etc/nagios4/telegram.json
sudo chmod 640 /etc/nagios4/telegram.json
```
Ensure Nagios reads the configuration file:
@ -40,7 +46,16 @@ Then reload service:
systemctl reload nagios4
```
## Logs
## Configuration file
Format used is JSON with the following keys:
* `chat_id`: where to send message on Telegram
* `auth_key`: key used to authenticate on Telegram
* `host_template` (optional): path to Markdown template file used for sending host notifications
* `service_template` (optional): path to Markdown template file used for sending service notifications
## Logging
Errors logs can be set with the `--logfile` argument.
@ -70,3 +85,37 @@ They can be overriden in the configuration file:
```
Both options are optional.
### Host variables
Variables replaced in the host template:
* `notification_type` (= `$NOTIFICATIONTYPE$`)
* `host_name` (= `$HOSTNAME$`)
* `host_state` (= `$HOSTSTATE$`)
* `host_address` (= `$HOSTADDRESS$`)
* `host_output` (= `$HOSTOUTPUT$`)
* `long_date_time` (= `$LONGDATETIME$`)
### Service variables
Variables replaced in the service template:
* `notification_type` (= `$NOTIFICATIONTYPE$`)
* `service_desc` (= `$SERVICEDESC$`)
* `host_alias` (= `$HOSTALIAS$`)
* `host_address` (= `$HOSTADDRESS$`)
* `service_state` (= `$SERVICESTATE$`)
* `long_date_time` (= `$LONGDATETIME$`)
* `service_output` (= `$SERVICEOUTPUT$`)
## How to contribute
Contributions are welcomed! Feel free to update the code and create a pull-request.
Be sure to lint the code before:
```
docker build -t pre-commit .
docker run -it -v $(pwd):/mnt/ --rm pre-commit bash
# cd /mnt/
# pre-commit run --all-files
# exit
```

View file

@ -1,4 +0,0 @@
Factorize the template rendering functions
Use JSON schema to validate configuration file
Add pre-commit Dockerfile and a doc to easily lint the code
Add requirements.txt file

21
config.schema.json Normal file
View file

@ -0,0 +1,21 @@
{
"type": "object",
"properties": {
"chat_id": {
"type": "number"
},
"auth_key": {
"type": "string"
},
"host_template": {
"type": "string"
},
"service_template": {
"type": "string"
}
},
"required": [
"chat_id",
"auth_key"
]
}

View file

@ -1,6 +1,6 @@
define command {
command_name notify-host-by-telegram
command_line /opt/notify-by-telegram/notify-by-telegram.py -c /etc/nagios4/telegram.json --logfile /var/log/nagios4/telegram.log host --notification-type "$NOTIFICATIONTYPE$" --service-desc "$SERVICEDESC$" --host-name "$HOSTNAME$" --host-state "$HOSTSTATE$" --host-address "$HOSTADDRESS$" --host-output "$HOSTOUTPUT$" --long-date-time "$LONGDATETIME$"
command_line /opt/notify-by-telegram/notify-by-telegram.py -c /etc/nagios4/telegram.json --logfile /var/log/nagios4/telegram.log host --notification-type "$NOTIFICATIONTYPE$" --host-name "$HOSTNAME$" --host-state "$HOSTSTATE$" --host-address "$HOSTADDRESS$" --host-output "$HOSTOUTPUT$" --long-date-time "$LONGDATETIME$"
}
define command {

View file

@ -6,8 +6,10 @@ import os
import requests
from jinja2 import Environment, FileSystemLoader
from jsonschema import validate
logger = logging.getLogger(__name__)
absolute_path = os.path.split(os.path.abspath(__file__))[0]
class InvalidConfigException(Exception):
@ -28,7 +30,6 @@ def parse_arguments():
# host notifications
host_parser = subparsers.add_parser('host')
host_parser.add_argument('--notification-type', help='nagios $NOTIFICATIONTYPE$', required=True)
host_parser.add_argument('--service-desc', help='nagios $SERVICEDESC$', required=True)
host_parser.add_argument('--host-name', help='nagios $HOSTNAME$', required=True)
host_parser.add_argument('--host-state', help='nagios $HOSTSTATE$', required=True)
host_parser.add_argument('--host-address', help='nagios $HOSTADDRESS$', required=True)
@ -60,9 +61,9 @@ def read_config(filename=None):
def validate_config(config):
if config is None:
raise InvalidConfigException('config is not a dict')
for key in ['chat_id', 'auth_key']:
if key not in config:
raise InvalidConfigException(f'missing "{key}" key in config')
with open(os.path.join(absolute_path, 'config.schema.json'), 'r') as fd:
schema = json.loads(fd.read())
validate(instance=config, schema=schema)
def setup_logging(args):
@ -71,48 +72,56 @@ def setup_logging(args):
def markdown_escape(text):
for special_char in ['\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '#', '+', '-', '.', '!']:
for special_char in ['\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '#', '+', '-', '.', '!', '=']:
text = text.replace(special_char, fr'\{special_char}')
return text
def generate_host_payload(chat_id, args, template_file_name=None):
def generate_payload(chat_id, message_type, message_variables, template_file_name=None):
payload = {'chat_id': chat_id, 'parse_mode': 'MarkdownV2'}
# define jinja template name
if not template_file_name:
absolute_path = os.path.split(os.path.abspath(__file__))[0]
template_file_name = os.path.join(absolute_path, 'templates', 'host.md.j2')
template_name = 'host.md.j2' if message_type == 'host' else 'service.md.j2'
template_file_name = os.path.join(absolute_path, 'templates', template_name)
template_path = os.path.dirname(os.path.abspath(template_file_name))
template_name = os.path.basename(os.path.abspath(template_file_name))
# create a jinja file template
loader = FileSystemLoader(template_path)
env = Environment(loader=loader)
template = env.get_template(template_name)
text = template.render(notification_type=markdown_escape(args.notification_type),
host_name=markdown_escape(args.host_name), host_state=markdown_escape(args.host_state),
host_address=markdown_escape(args.host_address),
host_output=markdown_escape(args.host_output),
long_date_time=markdown_escape(args.long_date_time))
# escape message variables
template_variables = {}
for key, value in message_variables.items():
template_variables[key] = markdown_escape(value)
# render template and update payload
text = template.render(**template_variables)
payload['text'] = text
return payload
def generate_service_payload(chat_id, args, template_file_name=None):
payload = {'chat_id': chat_id, 'parse_mode': 'MarkdownV2'}
if not template_file_name:
absolute_path = os.path.split(os.path.abspath(__file__))[0]
template_file_name = os.path.join(absolute_path, 'templates', 'service.md.j2')
template_path = os.path.dirname(os.path.abspath(template_file_name))
template_name = os.path.basename(os.path.abspath(template_file_name))
loader = FileSystemLoader(template_path)
env = Environment(loader=loader)
template = env.get_template(template_name)
text = template.render(notification_type=markdown_escape(args.notification_type),
service_desc=markdown_escape(args.service_desc), host_alias=markdown_escape(args.host_alias),
host_address=markdown_escape(args.host_address),
service_state=markdown_escape(args.service_state),
long_date_time=markdown_escape(args.long_date_time),
service_output=markdown_escape(args.service_output))
payload['text'] = text
return payload
def generate_host_payload(chat_id, notification_type, host_name, host_state, host_address, host_output, long_date_time,
template_file_name=None):
message_variables = {
'notification_type': notification_type, 'host_name': host_name, 'host_state': host_state,
'host_address': host_address, 'host_output': host_output, 'long_date_time': long_date_time
}
return generate_payload(chat_id=chat_id, message_type='host', message_variables=message_variables,
template_file_name=template_file_name)
def generate_service_payload(chat_id, notification_type, service_desc, host_alias, host_address, service_state,
long_date_time, service_output, template_file_name=None):
message_variables = {
'notification_type': notification_type, 'service_desc': service_desc, 'host_alias': host_alias,
'host_address': host_address, 'service_state': service_state, 'long_date_time': long_date_time,
'service_output': service_output
}
return generate_payload(chat_id=chat_id, message_type='service', message_variables=message_variables,
template_file_name=template_file_name)
def send_message(auth_key, payload):
@ -134,17 +143,23 @@ def main():
logger.info('generating payload')
if args.command == 'host':
payload = generate_host_payload(chat_id=config['chat_id'], args=args,
payload = generate_host_payload(chat_id=config['chat_id'], notification_type=args.notification_type,
host_name=args.host_name, host_state=args.host_state,
host_address=args.host_address, host_output=args.host_output,
long_date_time=args.long_date_time,
template_file_name=config.get('host_template'))
elif args.command == 'service':
payload = generate_service_payload(chat_id=config['chat_id'], args=args,
payload = generate_service_payload(chat_id=config['chat_id'], notification_type=args.notification_type,
service_desc=args.service_desc, host_alias=args.host_alias,
host_address=args.host_address, service_state=args.service_state,
long_date_time=args.long_date_time, service_output=args.service_output,
template_file_name=config.get('service_template'))
else:
raise NotImplementedError(f'command {args.command} not supported')
logger.info('sending message to telegram api')
send_message(auth_key=config['auth_key'], payload=payload)
except Exception as err:
except Exception:
logger.exception('cannot execute program')

21
requirements.txt Normal file
View file

@ -0,0 +1,21 @@
appdirs==1.4.4
attrs==20.3.0
certifi==2020.11.8
cfgv==3.2.0
chardet==3.0.4
distlib==0.3.1
filelock==3.0.12
identify==1.5.10
idna==2.10
Jinja2==2.11.2
jsonschema==3.2.0
MarkupSafe==1.1.1
nodeenv==1.5.0
pre-commit==2.9.2
pyrsistent==0.17.3
PyYAML==5.3.1
requests==2.25.0
six==1.15.0
toml==0.10.2
urllib3==1.26.2
virtualenv==20.2.1