From aa171a546824da9e1376541c4a6fce7c49d2f760 Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Mon, 19 Dec 2016 00:08:40 +0100 Subject: [PATCH 1/6] Add support to Telegram Bot messages beacon --- salt/beacons/telegram_bot_msg.py | 95 ++++++++++++ .../beacons/telegram_bot_msg_beacon_test.py | 143 ++++++++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 salt/beacons/telegram_bot_msg.py create mode 100644 tests/unit/beacons/telegram_bot_msg_beacon_test.py diff --git a/salt/beacons/telegram_bot_msg.py b/salt/beacons/telegram_bot_msg.py new file mode 100644 index 000000000000..5f632c948a11 --- /dev/null +++ b/salt/beacons/telegram_bot_msg.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +''' +Beacon to emit Telegram messages of a bot +''' + +# Import Python libs +from __future__ import absolute_import +import logging + +# Import 3rd Party libs +try: + import telegram + logging.getLogger('telegram').setLevel(logging.CRITICAL) + HAS_TELEGRAM = True +except ImportError: + HAS_TELEGRAM = False + +log = logging.getLogger(__name__) + + +__virtualname__ = 'telegram_bot_msg' + + +def __virtual__(): + if HAS_TELEGRAM: + return __virtualname__ + else: + return False + + +def __validate__(config): + ''' + Validate the beacon configuration + ''' + if not isinstance(config, dict): + return False, ('Configuration for telegram_bot_msg ' + 'beacon must be a dictionary.') + + if not all(config.get(required_config) + for required_config in ['token', 'accept_from']): + return False, ('Not all required configuration for ' + 'telegram_bot_msg are set.') + + if not isinstance(config.get('accept_from'), list): + return False, ('Configuration for telegram_bot_msg, ' + 'accept_from must be a list of usernames.') + + return True, 'Valid beacon configuration.' + + +def beacon(config): + ''' + Emit a dict name "msgs" whose value is a list of messages sent to + the configured bot by the listed users. + + .. code-block:: yaml + + beacons: + telegram_bot_msg: + token: "" + accept_from: + - + interval: 10 + + ''' + log.debug('telegram_bot_msg beacon starting') + ret = [] + output = {} + output['msgs'] = [] + + bot = telegram.Bot(config['token']) + updates = bot.get_updates(limit=100, timeout=0, network_delay=10) + + log.debug('Num updates: {0}'.format(len(updates))) + if len(updates) < 1: + log.debug('Telegram Bot beacon has no new messages') + return ret + + latest_update_id = 0 + for update in updates: + message = update.message + + if update.update_id > latest_update_id: + latest_update_id = update.update_id + + if message.chat.username in config['accept_from']: + output['msgs'].append(message.to_dict()) + + # mark in the server that previous messages are processed + bot.get_updates(offset=latest_update_id + 1) + + log.debug('Emitting {0} messages.'.format(len(output['msgs']))) + if len(output['msgs']) > 0: + ret.append(output) + return ret diff --git a/tests/unit/beacons/telegram_bot_msg_beacon_test.py b/tests/unit/beacons/telegram_bot_msg_beacon_test.py new file mode 100644 index 000000000000..3b2b39f49150 --- /dev/null +++ b/tests/unit/beacons/telegram_bot_msg_beacon_test.py @@ -0,0 +1,143 @@ +# coding: utf-8 + +# Python libs +from __future__ import absolute_import +import datetime + +# Salt testing libs +from salttesting import TestCase, skipIf +from salttesting.helpers import ensure_in_syspath +from salttesting.mock import NO_MOCK, NO_MOCK_REASON, MagicMock, patch + +# Salt libs +from salt.beacons import telegram_bot_msg + +# Third-party libs +try: + import telegram + HAS_TELEGRAM = True +except ImportError: + HAS_TELEGRAM = False + + +ensure_in_syspath('../../') + + +@skipIf(not HAS_TELEGRAM, 'telegram is not available') +@skipIf(NO_MOCK, NO_MOCK_REASON) +class TelegramBotMsgBeaconTestCase(TestCase): + ''' + Test case for salt.beacons.telegram_bot + ''' + def setUp(self): + telegram_bot_msg.__context__ = {} + + def test_validate_empty_config(self, *args, **kwargs): + ret = telegram_bot_msg.__validate__(None) + self.assertEqual(ret, (False, ('Configuration for telegram_bot_msg ' + 'beacon must be a dictionary.'))) + + def test_validate_missing_accept_from_config(self, *args, **kwargs): + ret = telegram_bot_msg.__validate__({ + 'token': 'bcd' + }) + self.assertEqual(ret, (False, ('Not all required configuration for ' + 'telegram_bot_msg are set.'))) + + def test_validate_missing_token_config(self, *args, **kwargs): + ret = telegram_bot_msg.__validate__({ + 'accept_from': [] + }) + self.assertEqual(ret, (False, ('Not all required configuration for ' + 'telegram_bot_msg are set.'))) + + def test_validate_config_not_list_in_accept_from(self, *args, **kwargs): + ret = telegram_bot_msg.__validate__({ + 'token': 'bcd', + 'accept_from': {'nodict': "1"} + }) + self.assertEqual(ret, (False, ('Configuration for telegram_bot_msg, ' + 'accept_from must be a list of ' + 'usernames.'))) + + def test_validate_valid_config(self, *args, **kwargs): + ret = telegram_bot_msg.__validate__({ + 'token': 'bcd', + 'accept_from': [ + 'username' + ] + }) + self.assertEqual(ret, (True, 'Valid beacon configuration.')) + + @patch("salt.beacons.telegram_bot_msg.telegram") + def test_call_no_updates(self, telegram_api, *args, **kwargs): + token = 'abc' + config = { + 'token': token, + 'accept_from': ['tester'] + } + inst = MagicMock(name='telegram.Bot()') + telegram_api.Bot = MagicMock(name='telegram', return_value=inst) + inst.get_updates.return_value = [] + + ret = telegram_bot_msg.beacon(config) + + telegram_api.Bot.assert_called_once_with(token) + self.assertEqual(ret, []) + + @patch("salt.beacons.telegram_bot_msg.telegram") + def test_call_telegram_return_no_updates_for_user( + self, telegram_api, *args, **kwargs): + token = 'abc' + username = 'tester' + config = { + 'token': token, + 'accept_from': [username] + } + inst = MagicMock(name='telegram.Bot()') + telegram_api.Bot = MagicMock(name='telegram', return_value=inst) + + username = 'different_user' + user = telegram.User(id=1, first_name='', username=username) + chat = telegram.Chat(1, 'private', username=username) + date = datetime.datetime(2016, 12, 18, 0, 0) + message = telegram.Message(1, user, date=date, chat=chat) + update = telegram.update.Update(update_id=1, message=message) + + inst.get_updates.return_value = [update] + + ret = telegram_bot_msg.beacon(config) + + telegram_api.Bot.assert_called_once_with(token) + self.assertEqual(ret, []) + + @patch("salt.beacons.telegram_bot_msg.telegram") + def test_call_telegram_returning_updates(self, telegram_api, + *args, **kwargs): + token = 'abc' + username = 'tester' + config = { + 'token': token, + 'accept_from': [username] + } + inst = MagicMock(name='telegram.Bot()') + telegram_api.Bot = MagicMock(name='telegram', return_value=inst) + + user = telegram.User(id=1, first_name='', username=username) + chat = telegram.Chat(1, 'private', username=username) + date = datetime.datetime(2016, 12, 18, 0, 0) + message = telegram.Message(1, user, date=date, chat=chat) + update = telegram.update.Update(update_id=1, message=message) + + inst.get_updates.return_value = [update] + + ret = telegram_bot_msg.beacon(config) + + telegram_api.Bot.assert_called_once_with(token) + self.assertTrue(ret) + self.assertEqual(ret[0]['msgs'][0], message.to_dict()) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(TelegramBotMsgBeaconTestCase, needs_daemon=False) From 88589fb1f6968b01b1d19cb1c320a86c17f80735 Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Mon, 19 Dec 2016 00:44:50 +0100 Subject: [PATCH 2/6] Add documentation for the Telegram bot --- doc/ref/beacons/all/index.rst | 1 + doc/ref/beacons/all/salt.beacons.telegram_bot_msg.rst | 6 ++++++ salt/beacons/telegram_bot_msg.py | 8 ++++---- 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 doc/ref/beacons/all/salt.beacons.telegram_bot_msg.rst diff --git a/doc/ref/beacons/all/index.rst b/doc/ref/beacons/all/index.rst index c3fe570231fa..c0970f4f6c02 100644 --- a/doc/ref/beacons/all/index.rst +++ b/doc/ref/beacons/all/index.rst @@ -32,5 +32,6 @@ beacon modules service sh status + telegram_bot_msg twilio_txt_msg wtmp diff --git a/doc/ref/beacons/all/salt.beacons.telegram_bot_msg.rst b/doc/ref/beacons/all/salt.beacons.telegram_bot_msg.rst new file mode 100644 index 000000000000..27c9128da9a4 --- /dev/null +++ b/doc/ref/beacons/all/salt.beacons.telegram_bot_msg.rst @@ -0,0 +1,6 @@ +============================= +salt.beacons.telegram_bot_msg +============================= + +.. automodule:: salt.beacons.telegram_bot_msg + :members: diff --git a/salt/beacons/telegram_bot_msg.py b/salt/beacons/telegram_bot_msg.py index 5f632c948a11..b9ea196b2ba7 100644 --- a/salt/beacons/telegram_bot_msg.py +++ b/salt/beacons/telegram_bot_msg.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- ''' -Beacon to emit Telegram messages of a bot +Beacon to emit Telegram messages ''' # Import Python libs @@ -50,8 +50,8 @@ def __validate__(config): def beacon(config): ''' - Emit a dict name "msgs" whose value is a list of messages sent to - the configured bot by the listed users. + Emit a dict with a key "msgs" whose value is a list of messages + sent to the configured bot by one of the allowed usernames. .. code-block:: yaml @@ -59,7 +59,7 @@ def beacon(config): telegram_bot_msg: token: "" accept_from: - - + - "" interval: 10 ''' From 7e9ee9045b5b2033a220c151d590876f537ba818 Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Fri, 6 Jan 2017 00:21:06 +0100 Subject: [PATCH 3/6] Better code style for empty collections --- salt/beacons/telegram_bot_msg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/salt/beacons/telegram_bot_msg.py b/salt/beacons/telegram_bot_msg.py index b9ea196b2ba7..220264107116 100644 --- a/salt/beacons/telegram_bot_msg.py +++ b/salt/beacons/telegram_bot_msg.py @@ -72,7 +72,7 @@ def beacon(config): updates = bot.get_updates(limit=100, timeout=0, network_delay=10) log.debug('Num updates: {0}'.format(len(updates))) - if len(updates) < 1: + if not updates: log.debug('Telegram Bot beacon has no new messages') return ret @@ -90,6 +90,6 @@ def beacon(config): bot.get_updates(offset=latest_update_id + 1) log.debug('Emitting {0} messages.'.format(len(output['msgs']))) - if len(output['msgs']) > 0: + if output['msgs']: ret.append(output) return ret From a620926f9d6bbae5516dc2172967ee973614ade6 Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Sun, 5 Mar 2017 16:12:31 +0100 Subject: [PATCH 4/6] Add to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index f6ed7bf79b06..80d92c23dfa3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -96,6 +96,7 @@ Pedro Algarvio Peter Baumgartner Pierre Carrier Rhys Elsmore +Rafael Caricio Robert Fielding Sean Channel Seth House From be2c2fab3f47ab315c9763c765696e257f6f9f63 Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Tue, 7 Mar 2017 09:53:48 +0100 Subject: [PATCH 5/6] Move to new style of tests --- ...ot_msg_beacon_test.py => test_telegram_bot_msg_beacon.py} | 5 ----- 1 file changed, 5 deletions(-) rename tests/unit/beacons/{telegram_bot_msg_beacon_test.py => test_telegram_bot_msg_beacon.py} (97%) diff --git a/tests/unit/beacons/telegram_bot_msg_beacon_test.py b/tests/unit/beacons/test_telegram_bot_msg_beacon.py similarity index 97% rename from tests/unit/beacons/telegram_bot_msg_beacon_test.py rename to tests/unit/beacons/test_telegram_bot_msg_beacon.py index 3b2b39f49150..0e12f324c300 100644 --- a/tests/unit/beacons/telegram_bot_msg_beacon_test.py +++ b/tests/unit/beacons/test_telegram_bot_msg_beacon.py @@ -136,8 +136,3 @@ def test_call_telegram_returning_updates(self, telegram_api, telegram_api.Bot.assert_called_once_with(token) self.assertTrue(ret) self.assertEqual(ret[0]['msgs'][0], message.to_dict()) - - -if __name__ == '__main__': - from integration import run_tests - run_tests(TelegramBotMsgBeaconTestCase, needs_daemon=False) From c5a7b68aacaaa331ddb0fb0a5a348848838cb90e Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Tue, 7 Mar 2017 11:40:10 +0100 Subject: [PATCH 6/6] Use new test libs --- tests/unit/beacons/test_telegram_bot_msg_beacon.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/unit/beacons/test_telegram_bot_msg_beacon.py b/tests/unit/beacons/test_telegram_bot_msg_beacon.py index 0e12f324c300..6d22e3493642 100644 --- a/tests/unit/beacons/test_telegram_bot_msg_beacon.py +++ b/tests/unit/beacons/test_telegram_bot_msg_beacon.py @@ -4,14 +4,13 @@ from __future__ import absolute_import import datetime -# Salt testing libs -from salttesting import TestCase, skipIf -from salttesting.helpers import ensure_in_syspath -from salttesting.mock import NO_MOCK, NO_MOCK_REASON, MagicMock, patch - # Salt libs from salt.beacons import telegram_bot_msg +# Salt testing libs +from tests.support.unit import TestCase, skipIf +from tests.support.mock import NO_MOCK, NO_MOCK_REASON, MagicMock, patch + # Third-party libs try: import telegram @@ -20,9 +19,6 @@ HAS_TELEGRAM = False -ensure_in_syspath('../../') - - @skipIf(not HAS_TELEGRAM, 'telegram is not available') @skipIf(NO_MOCK, NO_MOCK_REASON) class TelegramBotMsgBeaconTestCase(TestCase):