Compare commits

..

No commits in common. "4f854bca8b79a9227408a964c28ef4b97e4bf1dc" and "cbf5132b69e45b2453b8a24f5554017cb058f4d5" have entirely different histories.

9 changed files with 45 additions and 154 deletions

View file

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

View file

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

View file

@ -15,25 +15,19 @@ Clone the repository:
git clone https://github.com/jouir/notify-by-telegram.git /opt/notify-by-telegram git clone https://github.com/jouir/notify-by-telegram.git /opt/notify-by-telegram
``` ```
Install dependencies using pip: Install dependencies using the package manager:
``` ```
pip install -r requirements.txt sudo apt install python3-jinja2 python3-requests
```
Or via the package manager:
```
sudo apt install python3-jinja2 python3-requests python3-jsonschema
``` ```
## Configuration ## Configuration
Copy and update the configuration file example: Copy and update the configuration file example:
``` ```
cp -p config.example.json telegram.json cp -p config.json.example telegram.json
vim telegram.json vim telegram.json
sudo mv telegram.json /etc/nagios4/telegram.json sudo mv telegram.json /etc/nagios4/telegram.json
sudo chown root:nagios /etc/nagios4/telegram.json sudo chmod 640 root:nagios /etc/nagios4/telegram.json
sudo chmod 640 /etc/nagios4/telegram.json
``` ```
Ensure Nagios reads the configuration file: Ensure Nagios reads the configuration file:
@ -46,16 +40,7 @@ Then reload service:
systemctl reload nagios4 systemctl reload nagios4
``` ```
## Configuration file ## Logs
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. Errors logs can be set with the `--logfile` argument.
@ -85,37 +70,3 @@ They can be overriden in the configuration file:
``` ```
Both options are optional. 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
```

4
TODO.txt Normal file
View file

@ -0,0 +1,4 @@
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

View file

@ -1,21 +0,0 @@
{
"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 { define command {
command_name notify-host-by-telegram 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$" --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$" --service-desc "$SERVICEDESC$" --host-name "$HOSTNAME$" --host-state "$HOSTSTATE$" --host-address "$HOSTADDRESS$" --host-output "$HOSTOUTPUT$" --long-date-time "$LONGDATETIME$"
} }
define command { define command {

View file

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

View file

@ -1,21 +0,0 @@
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