diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a14542e..503fbbb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,8 +11,6 @@ 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: diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 3629d9a..0000000 --- a/Dockerfile +++ /dev/null @@ -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 diff --git a/README.md b/README.md index b1b2c6b..e799bf6 100644 --- a/README.md +++ b/README.md @@ -15,25 +15,19 @@ Clone the repository: 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 -``` - -Or via the package manager: -``` -sudo apt install python3-jinja2 python3-requests python3-jsonschema +sudo apt install python3-jinja2 python3-requests ``` ## Configuration Copy and update the configuration file example: ``` -cp -p config.example.json telegram.json +cp -p config.json.example telegram.json vim telegram.json sudo mv telegram.json /etc/nagios4/telegram.json -sudo chown root:nagios /etc/nagios4/telegram.json -sudo chmod 640 /etc/nagios4/telegram.json +sudo chmod 640 root:nagios /etc/nagios4/telegram.json ``` Ensure Nagios reads the configuration file: @@ -46,16 +40,7 @@ Then reload service: systemctl reload nagios4 ``` -## 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 +## Logs 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. - -### 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 -``` diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 0000000..12a5bc7 --- /dev/null +++ b/TODO.txt @@ -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 diff --git a/config.example.json b/config.json.example similarity index 100% rename from config.example.json rename to config.json.example diff --git a/config.schema.json b/config.schema.json deleted file mode 100644 index f817acd..0000000 --- a/config.schema.json +++ /dev/null @@ -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" - ] -} diff --git a/nagios.cfg b/nagios.cfg index bdddcff..2b0ee46 100644 --- a/nagios.cfg +++ b/nagios.cfg @@ -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$" --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 { diff --git a/notify-by-telegram.py b/notify-by-telegram.py index 9333308..2b5ebe6 100755 --- a/notify-by-telegram.py +++ b/notify-by-telegram.py @@ -6,10 +6,8 @@ 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): @@ -30,6 +28,7 @@ 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) @@ -61,9 +60,9 @@ def read_config(filename=None): def validate_config(config): if config is None: raise InvalidConfigException('config is not a dict') - with open(os.path.join(absolute_path, 'config.schema.json'), 'r') as fd: - schema = json.loads(fd.read()) - validate(instance=config, schema=schema) + for key in ['chat_id', 'auth_key']: + if key not in config: + raise InvalidConfigException(f'missing "{key}" key in config') def setup_logging(args): @@ -72,56 +71,48 @@ 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_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'} - - # define jinja template name if not template_file_name: - template_name = 'host.md.j2' if message_type == 'host' else 'service.md.j2' - template_file_name = os.path.join(absolute_path, 'templates', template_name) + absolute_path = os.path.split(os.path.abspath(__file__))[0] + template_file_name = os.path.join(absolute_path, 'templates', 'host.md.j2') 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) - - # 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) + 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)) 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 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 send_message(auth_key, payload): @@ -143,23 +134,17 @@ def main(): logger.info('generating payload') if args.command == 'host': - 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, + payload = generate_host_payload(chat_id=config['chat_id'], args=args, template_file_name=config.get('host_template')) elif args.command == 'service': - 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')) + payload = generate_service_payload(chat_id=config['chat_id'], args=args, + 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: + except Exception as err: logger.exception('cannot execute program') diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 973fadd..0000000 --- a/requirements.txt +++ /dev/null @@ -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