├── README.md ├── img └── telegram_py.png └── telegram.py /README.md: -------------------------------------------------------------------------------- 1 | # Ansible Callback plugin for Telegram 2 | Used for your ansible playbook notification delivery 3 | Its highly recommended to use socks5 proxy to bypass RKN's restrictions but its not required (installed tor with socks5 proxy are perfect) 4 | 5 | ## Requiremets 6 | This plugin requires python libs: 7 | - pyTelegramBotApi 8 | - prettytable 9 | 10 | ## Install 11 | 1. Install python libraries and upgrade requests lib to latest 12 | 13 | ```sh 14 | $ pip install pyTelegramBotApi 15 | $ pip install prettytable 16 | $ pip install requests --upgrade 17 | ``` 18 | 19 | 2. Download plugin and put it to ansible 20 | 21 | ```sh 22 | $ cd /path/to/your/ansible/plugins/callback 23 | $ curl -O https://raw.githubusercontent.com/dfwmlb/ansible-callback-telegram/master/telegram.py 24 | ``` 25 | 26 | 3. Add configuration to your ansible.cfg 27 | 28 | ```sh 29 | callback_whitelist = telegram 30 | 31 | [callback_telegram] 32 | tg_token = ENTER_TOKEN 33 | tg_chat_id = ENTER_CHAT_ID 34 | socks5_uri = socks5://localhost:9050 35 | ``` 36 | 37 | ## Screens 38 |

39 | 40 |

-------------------------------------------------------------------------------- /img/telegram_py.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfwmlb/ansible-callback-telegram/f74330b2f6a5a3b0992d698347054d0cc585d8d3/img/telegram_py.png -------------------------------------------------------------------------------- /telegram.py: -------------------------------------------------------------------------------- 1 | from __future__ import (absolute_import, division, print_function) 2 | __metaclass__ = type 3 | 4 | DOCUMENTATION = ''' 5 | callback: telegram 6 | callback_type: notification 7 | requirements: 8 | - whitelist in configuration 9 | - telebot (pip install pyTelegramBotApi) 10 | - prettytable (pip install prettytable) 11 | - latest requests (pip install requests --upgrade) 12 | short_description: Sends play events to a telegram channel 13 | version_added: "2.1" 14 | description: 15 | - This is an ansible callback plugin that sends status updates to a telegram channel during playbook execution. 16 | - Before 2.4 only environment variables were available for configuring this plugin 17 | options: 18 | tg_token: 19 | required: True 20 | description: telegram bot token 21 | env: 22 | - name: TG_TOKEN 23 | ini: 24 | - section: callback_telegram 25 | key: tg_token 26 | tg_chat_id: 27 | required: True 28 | description: telegram chat id to post in. 29 | env: 30 | - name: TG_CHAT_ID 31 | ini: 32 | - section: callback_telegram 33 | key: tg_chat_id 34 | socks5_uri: 35 | description: socks5 proxy uri to bypass rkn's restarictions 36 | env: 37 | - name: SOCKS5_URI 38 | ini: 39 | - section: callback_telegram 40 | key: socks5_uri 41 | ''' 42 | 43 | import os 44 | from datetime import datetime 45 | 46 | from ansible import context 47 | from ansible.module_utils._text import to_text 48 | from ansible.module_utils.urls import open_url 49 | from ansible.plugins.callback import CallbackBase 50 | 51 | try: 52 | import telebot 53 | from telebot import apihelper 54 | HAS_TELEBOT = True 55 | except ImportError: 56 | HAS_TELEBOT = False 57 | 58 | try: 59 | import prettytable 60 | HAS_PRETTYTABLE = True 61 | except ImportError: 62 | HAS_PRETTYTABLE = False 63 | 64 | class CallbackModule(CallbackBase): 65 | """This is an ansible callback plugin that sends status 66 | updates to a telegram channel during playbook execution. 67 | """ 68 | CALLBACK_VERSION = 2.0 69 | CALLBACK_TYPE = 'notification' 70 | CALLBACK_NAME = 'telegram' 71 | CALLBACK_NEEDS_WHITELIST = True 72 | 73 | def __init__(self, display=None): 74 | 75 | super(CallbackModule, self).__init__(display=display) 76 | 77 | if not HAS_TELEBOT: 78 | self.disabled = True 79 | self._display.warning('The `telebot` python module is not ' 80 | 'installed. Disabling the Slack callback ' 81 | 'plugin.') 82 | 83 | if not HAS_PRETTYTABLE: 84 | self.disabled = True 85 | self._display.warning('The `prettytable` python module is not ' 86 | 'installed. Disabling the Slack callback ' 87 | 'plugin.') 88 | 89 | self.playbook_name = None 90 | self.play = None 91 | self.now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 92 | 93 | def set_options(self, task_keys=None, var_options=None, direct=None): 94 | 95 | super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct) 96 | 97 | self.tg_token = self.get_option('tg_token') 98 | self.tg_chat_id = self.get_option('tg_chat_id') 99 | self.socks5_uri = self.get_option('socks5_uri') 100 | 101 | if self.tg_token is None: 102 | self.disabled = True 103 | self._display.warning('tg_token was not provided. The ' 104 | 'tg_token can be provided using ' 105 | 'the `TG_TOKEN` environment ' 106 | 'variable.') 107 | 108 | if self.tg_chat_id is None: 109 | self.disabled = True 110 | self._display.warning('tg_chat_id was not provided. The ' 111 | 'tg_chat_id can be provided using ' 112 | 'the `TG_CHAT_ID` environment ' 113 | 'variable.') 114 | 115 | def send_msg(self, msg): 116 | if self.socks5_uri is not None: 117 | apihelper.proxy = {'https': self.socks5_uri} 118 | bot = telebot.TeleBot(self.tg_token) 119 | bot.send_message(self.tg_chat_id, msg, parse_mode='HTML') 120 | 121 | def v2_playbook_on_start(self, playbook): 122 | 123 | self.playbook_name = os.path.abspath(playbook._file_name) 124 | 125 | def v2_playbook_on_play_start(self, play): 126 | self.play = play 127 | 128 | title = [ 129 | 'Ansible: STARTED ⚙️' 130 | ] 131 | 132 | msg_items = [' '.join(title)] 133 | msg_items.append('\n time: ' + '' + str(self.now) + '') 134 | msg_items.append('playbook: ' + '' + self.playbook_name + '') 135 | msg_items.append(' hosts:') 136 | for host in play.hosts: 137 | msg_items.append(' - ' + host + '') 138 | msg_items.append(' tags:') 139 | for tag in play.only_tags: 140 | msg_items.append(' - ' + tag + '') 141 | msg = '\n'.join(msg_items) 142 | self.send_msg(msg=msg) 143 | 144 | def v2_runner_on_failed(self, result, ignore_errors=False): 145 | 146 | msg = [] 147 | title = [ 148 | 'Ansible: FAILED ❌' 149 | ] 150 | msg_items = [' '.join(title)] 151 | msg_items.append('\n time: ' + '' + str(self.now) + '') 152 | msg_items.append('playbook: ' + '' + self.playbook_name + '') 153 | msg_items.append(' host: ' + '' + result._host.get_name() + '') 154 | msg_items.append(' stderr: ' + '' + result._result['stderr'] + '') 155 | 156 | msg = '\n'.join(msg_items) 157 | 158 | self.send_msg(msg=msg) 159 | 160 | def v2_playbook_on_stats(self, stats): 161 | """Display info about playbook statistics""" 162 | 163 | hosts = sorted(stats.processed.keys()) 164 | 165 | t = prettytable.PrettyTable(['Host', 'Ok', 'Changed', 'Unreachable', 166 | 'Failures', 'Rescued', 'Ignored']) 167 | 168 | failures = False 169 | unreachable = False 170 | 171 | for h in hosts: 172 | s = stats.summarize(h) 173 | 174 | if s['failures'] > 0: 175 | failures = True 176 | if s['unreachable'] > 0: 177 | unreachable = True 178 | 179 | t.add_row([h] + [s[k] for k in ['ok', 'changed', 'unreachable', 180 | 'failures', 'rescued', 'ignored']]) 181 | 182 | msg = [] 183 | title = 'Ansible: ENDED' 184 | if failures or unreachable: 185 | msg_items = [ 186 | title + ' ❌' 187 | ] 188 | else: 189 | msg_items = [ 190 | title + ' ✅' 191 | ] 192 | msg_items.append('\n time: ' + '' + str(self.now) + '') 193 | msg_items.append('playbook: ' + '' + self.playbook_name + '') 194 | msg_items.append('\n%s\n' % t) 195 | 196 | msg = '\n'.join(msg_items) 197 | 198 | self.send_msg(msg=msg) --------------------------------------------------------------------------------