Initial code
Signed-off-by: Julien Riou <julien@riou.xyz>
This commit is contained in:
		
					parent
					
						
							
								fe478845f3
							
						
					
				
			
			
				commit
				
					
						d38907dc35
					
				
			
		
					 6 changed files with 251 additions and 1 deletions
				
			
		
							
								
								
									
										29
									
								
								.pre-commit-config.yaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								.pre-commit-config.yaml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| --- | ||||
| repos: | ||||
|   - repo: https://github.com/pre-commit/pre-commit-hooks | ||||
|     rev: master | ||||
|     hooks: | ||||
|       - id: check-executables-have-shebangs | ||||
|       - id: check-merge-conflict | ||||
|       - id: double-quote-string-fixer | ||||
|       - id: end-of-file-fixer | ||||
|       - id: fix-encoding-pragma | ||||
|         args: ['--remove'] | ||||
|       - id: requirements-txt-fixer | ||||
|       - id: trailing-whitespace | ||||
|   - repo: https://gitlab.com/pycqa/flake8 | ||||
|     rev: master | ||||
|     hooks: | ||||
|       - id: flake8 | ||||
|         args: ['--max-line-length=120'] | ||||
|   - repo: https://github.com/FalconSocial/pre-commit-python-sorter | ||||
|     rev: master | ||||
|     hooks: | ||||
|       - id: python-import-sorter | ||||
|         args: ['--silent-overwrite'] | ||||
|   - repo: https://github.com/chewse/pre-commit-mirrors-pydocstyle | ||||
|     rev: master | ||||
|     hooks: | ||||
|       - id: pydocstyle | ||||
|         args: ['--config=.pydocstyle', '--match="(?!test_).*\.py"'] | ||||
|   - repo: https://github.com/jumanjihouse/pre-commit-hooks | ||||
							
								
								
									
										2
									
								
								.pydocstyle
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.pydocstyle
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| [pydocstyle] | ||||
| ignore = D100,D104,D400,D203,D204,D101,D213,D202 | ||||
							
								
								
									
										58
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										58
									
								
								README.md
									
										
									
									
									
								
							|  | @ -1,2 +1,58 @@ | |||
| # notify-by-telegram | ||||
| Send Nagios notifications to a Telegram channel | ||||
| Send Nagios notifications to a Telegram channel. | ||||
| 
 | ||||
| 
 | ||||
| ## Telegram bot | ||||
| 
 | ||||
| This [tutorial](https://takersplace.de/2019/12/19/telegram-notifications-with-nagios/) explains how to create a Telegram bot. You'll need the *chat_id* and *auth_key* for the next section. | ||||
| 
 | ||||
| ## Installation | ||||
| 
 | ||||
| _This guide has been written for [Debian](https://www.debian.org/). Some commands might slightly change depending on your distribution._ | ||||
| 
 | ||||
| Clone the repository: | ||||
| ``` | ||||
| git clone https://github.com/jouir/notify-by-telegram.git | ||||
| sudo cp -p notify-by-telegram.py /usr/lib/nagios/plugins/notify-by-telegram.py | ||||
| sudo chmod 755 /usr/lib/nagios/plugins/notify-by-telegram.py | ||||
| sudo chown root:root /usr/lib/nagios/plugins/notify-by-telegram.py | ||||
| ``` | ||||
| 
 | ||||
| Install dependencies using the package manager: | ||||
| ``` | ||||
| sudo apt install python3-jinja2 python3-requests | ||||
| ``` | ||||
| 
 | ||||
| ## Configuration | ||||
| 
 | ||||
| Copy and update the configuration file example: | ||||
| ``` | ||||
| sudo cp -p config.json.example /etc/nagios/telegram.json | ||||
| sudo chmod 640 root:nagios /etc/nagios/telegram.json | ||||
| ``` | ||||
| 
 | ||||
| Add Nagios configurations: | ||||
| ``` | ||||
| sudo cp -p nagios.cfg /etc/nagios4/conf.d/telegram.cfg | ||||
| ``` | ||||
| 
 | ||||
| Ensure Nagios reads the configuration file: | ||||
| ``` | ||||
| grep cfg_dir=/etc/nagios4/conf.d /etc/nagios4/nagios.cfg | ||||
| ``` | ||||
| 
 | ||||
| Then reload service: | ||||
| ``` | ||||
| systemctl reload nagios4 | ||||
| ``` | ||||
| 
 | ||||
| ## Logs | ||||
| 
 | ||||
| Errors logs can be set with the `--logfile` argument. | ||||
| 
 | ||||
| Example: | ||||
| ``` | ||||
| tail -f /var/log/nagios4/telegram.log | ||||
| ``` | ||||
| 
 | ||||
| Log level can be raised using `--verbose` or even more with `--debug` arguments. | ||||
|  |  | |||
							
								
								
									
										4
									
								
								config.json.example
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								config.json.example
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| { | ||||
|   "chat_id": 99, | ||||
|   "auth_key": "hash" | ||||
| } | ||||
							
								
								
									
										9
									
								
								nagios.cfg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								nagios.cfg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| define command { | ||||
|     command_name    notify-host-by-telegram | ||||
|     command_line    /usr/lib/nagios/plugins/notify-by-telegram.py -c /etc/nagios/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 { | ||||
|     command_name    notify-service-by-telegram | ||||
|     command_line    /usr/lib/nagios/plugins/notify-by-telegram.py -c /etc/nagios/telegram.json --logfile /var/log/nagios4/telegram.log service --notification-type "$NOTIFICATIONTYPE$" --service-desc "$SERVICEDESC$" --host-alias "$HOSTALIAS$" --host-address "$HOSTADDRESS$" --service-state "$SERVICESTATE$" --long-date-time "$LONGDATETIME$" --service-output "$SERVICEOUTPUT$" | ||||
| } | ||||
							
								
								
									
										150
									
								
								notify-by-telegram.py
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										150
									
								
								notify-by-telegram.py
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,150 @@ | |||
| #!/usr/bin/env python3 | ||||
| import argparse | ||||
| import json | ||||
| import logging | ||||
| import os | ||||
| 
 | ||||
| import requests | ||||
| from jinja2 import Template | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| class InvalidConfigException(Exception): | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| 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') | ||||
|     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') | ||||
| 
 | ||||
|     # 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) | ||||
| 
 | ||||
|     # 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) | ||||
| 
 | ||||
|     args = parser.parse_args() | ||||
|     return args | ||||
| 
 | ||||
| 
 | ||||
| def read_config(filename=None): | ||||
|     if filename and os.path.isfile(filename): | ||||
|         with open(filename, 'r') as fd: | ||||
|             return json.load(fd) | ||||
|     else: | ||||
|         return {} | ||||
| 
 | ||||
| 
 | ||||
| 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') | ||||
| 
 | ||||
| 
 | ||||
| def setup_logging(args): | ||||
|     logging.basicConfig(format='%(levelname)s: %(message)s', level=args.loglevel, filename=args.logfile) | ||||
| 
 | ||||
| 
 | ||||
| def markdown_escape(text): | ||||
|     for special_char in ['\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '#', '+', '-', '.', '!']: | ||||
|         text = text.replace(special_char, fr'\{special_char}') | ||||
|     return text | ||||
| 
 | ||||
| 
 | ||||
| def generate_host_payload(chat_id, args): | ||||
|     payload = {'chat_id': chat_id, 'parse_mode': 'MarkdownV2'} | ||||
|     text = r"""\*\*\*\*\* Nagios \*\*\*\*\* | ||||
| *Notification Type:* {{notification_type}} | ||||
| *Host:* {{host_name}} | ||||
| *State:* {{host_state}} | ||||
| *Address:* {{host_address}} | ||||
| *Info:* {{host_output}} | ||||
| *Date/Time:* {{long_date_time}} | ||||
| """ | ||||
|     template = Template(text) | ||||
|     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_service_payload(chat_id, args): | ||||
|     payload = {'chat_id': chat_id, 'parse_mode': 'MarkdownV2'} | ||||
|     text = r"""\*\*\*\*\* Nagios \*\*\*\*\* | ||||
| *Notification Type:* {{notification_type}} | ||||
| *Service:* {{service_desc}} | ||||
| *Host:* {{host_alias}} | ||||
| *Address:* {{host_address}} | ||||
| *State:* {{service_state}} | ||||
| *Date/Time:* {{long_date_time}} | ||||
| *Additional Info:* | ||||
| {{service_output}} | ||||
| """ | ||||
|     template = Template(text) | ||||
|     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): | ||||
|     r = requests.post(f'https://api.telegram.org/bot{auth_key}/sendMessage', json=payload) | ||||
|     if r.status_code != requests.codes.ok: | ||||
|         description = r.json().get('description') | ||||
|         logger.error(f'{r.status_code}: {description}') | ||||
|         logger.debug(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) | ||||
| 
 | ||||
|     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('sending message to telegram api') | ||||
|     send_message(auth_key=config['auth_key'], payload=payload) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue