diff --git a/README.md b/README.md index a576bca..e799bf6 100644 --- a/README.md +++ b/README.md @@ -50,3 +50,23 @@ tail -f /var/log/nagios4/telegram.log ``` Log level can be raised using `--verbose` or even more with `--debug` arguments. + + +## Message format + +`notify-by-telegram` script uses the `MarkdownV2` format to generate Telegram messages. + +[Jinja](https://jinja.palletsprojects.com) is used for templating (eg. replace `{{host_name}}` placeholders by the value submitted by Nagios). + +Default **host** and **service** templates can be found in the [templates](templates) directory. + +They can be overriden in the configuration file: + +```json +{ + "host_template": "/etc/nagios4/host.md.j2", + "service_template": "/etc/nagios4/service.md.j2" +} +``` + +Both options are optional. diff --git a/TODO.txt b/TODO.txt index 216ec9e..12a5bc7 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,4 +1,3 @@ -Add host and service templates to configuration file (+doc) Factorize the template rendering functions Use JSON schema to validate configuration file Add pre-commit Dockerfile and a doc to easily lint the code diff --git a/notify-by-telegram.py b/notify-by-telegram.py index 8410ac2..2b5ebe6 100755 --- a/notify-by-telegram.py +++ b/notify-by-telegram.py @@ -18,32 +18,32 @@ def parse_arguments(): parser = argparse.ArgumentParser() # base arguments parser.add_argument('-v', '--verbose', dest='loglevel', action='store_const', const=logging.INFO, - help='Print more output') + help='print more output') parser.add_argument('-d', '--debug', dest='loglevel', action='store_const', const=logging.DEBUG, - default=logging.WARNING, help='Print even more output') - parser.add_argument('-c', '--config', help='Configuration file location', required=True) - parser.add_argument('-o', '--logfile', help='Logging file location') - subparsers = parser.add_subparsers(title='commands', dest='command', help='Nagios notification type') + default=logging.WARNING, help='print even more output') + parser.add_argument('-c', '--config', help='configuration file location', required=True) + parser.add_argument('-o', '--logfile', help='logging file location') + subparsers = parser.add_subparsers(title='commands', dest='command', help='nagios notification type') # 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) - host_parser.add_argument('--host-output', help='Nagios $HOSTOUTPUT$', required=True) - host_parser.add_argument('--long-date-time', help='Nagios $LONGDATETIME$', 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-state', help='nagios $HOSTSTATE$', required=True) + host_parser.add_argument('--host-address', help='nagios $HOSTADDRESS$', required=True) + host_parser.add_argument('--host-output', help='nagios $HOSTOUTPUT$', required=True) + host_parser.add_argument('--long-date-time', help='nagios $LONGDATETIME$', required=True) # service notifications service_parser = subparsers.add_parser('service') - service_parser.add_argument('--notification-type', help='Nagios $NOTIFICATIONTYPE$', required=True) - service_parser.add_argument('--service-desc', help='Nagios $SERVICEDESC$', required=True) - service_parser.add_argument('--host-alias', help='Nagios $HOSTALIAS$', required=True) - service_parser.add_argument('--host-address', help='Nagios $HOSTADDRESS$', required=True) - service_parser.add_argument('--service-state', help='Nagios $SERVICESTATE$', required=True) - service_parser.add_argument('--long-date-time', help='Nagios $LONGDATETIME$', required=True) - service_parser.add_argument('--service-output', help='Nagios $SERVICEOUTPUT$', required=True) + service_parser.add_argument('--notification-type', help='nagios $NOTIFICATIONTYPE$', required=True) + service_parser.add_argument('--service-desc', help='nagios $SERVICEDESC$', required=True) + service_parser.add_argument('--host-alias', help='nagios $HOSTALIAS$', required=True) + service_parser.add_argument('--host-address', help='nagios $HOSTADDRESS$', required=True) + service_parser.add_argument('--service-state', help='nagios $SERVICESTATE$', required=True) + service_parser.add_argument('--long-date-time', help='nagios $LONGDATETIME$', required=True) + service_parser.add_argument('--service-output', help='nagios $SERVICEOUTPUT$', required=True) args = parser.parse_args() return args @@ -59,10 +59,10 @@ def read_config(filename=None): def validate_config(config): if config is None: - raise InvalidConfigException('Config is not a dict') + 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') + raise InvalidConfigException(f'missing "{key}" key in config') def setup_logging(args): @@ -76,12 +76,16 @@ def markdown_escape(text): return text -def generate_host_payload(chat_id, args): +def generate_host_payload(chat_id, args, template_file_name=None): payload = {'chat_id': chat_id, 'parse_mode': 'MarkdownV2'} - absolute_path = os.path.split(os.path.abspath(__file__))[0] - loader = FileSystemLoader(os.path.join(absolute_path, 'templates')) + 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_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('host.md.j2') + 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), @@ -91,12 +95,16 @@ def generate_host_payload(chat_id, args): return payload -def generate_service_payload(chat_id, args): +def generate_service_payload(chat_id, args, template_file_name=None): payload = {'chat_id': chat_id, 'parse_mode': 'MarkdownV2'} - absolute_path = os.path.split(os.path.abspath(__file__))[0] - loader = FileSystemLoader(os.path.join(absolute_path, 'templates')) + 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('service.md.j2') + 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), @@ -116,23 +124,28 @@ def send_message(auth_key, payload): def main(): - args = parse_arguments() - setup_logging(args) - logger.info(f'reading configuration file {args.config}') - config = read_config(args.config) - logger.info('validating configuration') - validate_config(config) + try: + args = parse_arguments() + setup_logging(args) + logger.info(f'reading configuration file {args.config}') + config = read_config(args.config) + logger.info('validating configuration') + validate_config(config) - logger.info('generating payload') - if args.command == 'host': - payload = generate_host_payload(chat_id=config['chat_id'], args=args) - elif args.command == 'service': - payload = generate_service_payload(chat_id=config['chat_id'], args=args) - else: - raise NotImplementedError(f'Command {args.command} not supported') + logger.info('generating payload') + if args.command == 'host': + 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'], 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) + logger.info('sending message to telegram api') + send_message(auth_key=config['auth_key'], payload=payload) + except Exception as err: + logger.exception('cannot execute program') if __name__ == '__main__':