From 0fd093a6989f2f079524c54cbea39acb68226c61 Mon Sep 17 00:00:00 2001 From: Steve Howell Date: Sun, 9 Dec 2018 18:26:02 +0000 Subject: [PATCH] bots: Allow users to supply ZULIP_API_KEY, etc. This will make it easier to run standalone bots in containers like Heroku that prefer env-var-style configuration. For now this is undocumented, but we should update the server docs once a few folks have tried it out. (The history behind requiring the config file is that I wanted to keep things simple and be strongly opinionated about how you run bots, so that the docs didn't overwhelm folks, but this use case has come up more frequently.) --- zulip_bots/zulip_bots/lib.py | 16 ++++++++++ zulip_bots/zulip_bots/run.py | 39 +++++++++++++++++++------ zulip_bots/zulip_bots/tests/test_run.py | 8 ++--- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/zulip_bots/zulip_bots/lib.py b/zulip_bots/zulip_bots/lib.py index b8f2ed0..55c7e37 100644 --- a/zulip_bots/zulip_bots/lib.py +++ b/zulip_bots/zulip_bots/lib.py @@ -30,6 +30,22 @@ def get_bots_directory_path() -> str: current_dir = os.path.dirname(os.path.abspath(__file__)) return os.path.join(current_dir, 'bots') +def zulip_env_vars_are_present() -> bool: + # We generally require a Zulip config file, but if + # the user supplies the correct environment vars, we + # waive the requirement. This can be helpful for + # containers like Heroku that prefer env vars to config + # files. + if os.environ.get('ZULIP_EMAIL') is None: + return False + if os.environ.get('ZULIP_API_KEY') is None: + return False + if os.environ.get('ZULIP_SITE') is None: + return False + + # If none of the absolutely critical env vars are + # missing, we can proceed without a config file. + return True class RateLimit(object): def __init__(self, message_limit: int, interval_limit: int) -> None: diff --git a/zulip_bots/zulip_bots/run.py b/zulip_bots/zulip_bots/run.py index 450264e..0634bf8 100755 --- a/zulip_bots/zulip_bots/run.py +++ b/zulip_bots/zulip_bots/run.py @@ -7,12 +7,15 @@ import os from os.path import basename, splitext from zulip_bots.lib import ( + zulip_env_vars_are_present, run_message_handler_for_bot, NoBotConfigException, ) from zulip_bots import finder from zulip_bots.provision import provision_bot +from typing import Optional + current_dir = os.path.dirname(os.path.abspath(__file__)) def parse_args() -> argparse.Namespace: @@ -32,7 +35,6 @@ def parse_args() -> argparse.Namespace: parser.add_argument('--config-file', '-c', action='store', - required=True, help='zulip configuration file (e.g. ~/Downloads/zuliprc)') parser.add_argument('--bot-config-file', '-b', @@ -51,17 +53,36 @@ def parse_args() -> argparse.Namespace: return args -def exit_gracefully_if_zulip_config_file_does_not_exist(config_file: str) -> None: - if not os.path.exists(config_file): - print(''' - ERROR: %s does not exist. +def exit_gracefully_if_zulip_config_is_missing(config_file: Optional[str]) -> None: + error_msg = None + if config_file: + if os.path.exists(config_file): + # We're good. (There may be problems with the config file, + # but we'll catch those later. + return + else: + error_msg = 'ERROR: %s does not exist.' % (config_file,) + + else: + if zulip_env_vars_are_present(): + return + else: + error_msg = 'ERROR: You did not supply a Zulip config file.' + + if error_msg: + print('\n') + print(error_msg) + print(''' You may need to download a config file from the Zulip app, or if you have already done that, you need to specify the file - location correctly. - ''' % (config_file,)) - sys.exit(1) + location correctly on the command line. + If you don't want to use a config file, you must set + these env vars: ZULIP_EMAIL, ZULIP_API_KEY, ZULIP_SITE. + ''') + + sys.exit(1) def exit_gracefully_if_bot_config_file_does_not_exist(bot_config_file: str) -> None: if bot_config_file is None: @@ -120,7 +141,7 @@ def main() -> None: # It's a bit unfortunate that we have two config files, but the # alternative would be way worse for people running multiple bots # or testing against multiple Zulip servers. - exit_gracefully_if_zulip_config_file_does_not_exist(args.config_file) + exit_gracefully_if_zulip_config_is_missing(args.config_file) exit_gracefully_if_bot_config_file_does_not_exist(args.bot_config_file) try: diff --git a/zulip_bots/zulip_bots/tests/test_run.py b/zulip_bots/zulip_bots/tests/test_run.py index 2a9c8d7..d4096a1 100644 --- a/zulip_bots/zulip_bots/tests/test_run.py +++ b/zulip_bots/zulip_bots/tests/test_run.py @@ -19,7 +19,7 @@ class TestDefaultArguments(TestCase): @patch('sys.argv', ['zulip-run-bot', 'giphy', '--config-file', '/foo/bar/baz.conf']) @patch('zulip_bots.run.run_message_handler_for_bot') def test_argument_parsing_with_bot_name(self, mock_run_message_handler_for_bot: mock.Mock) -> None: - with patch('zulip_bots.run.exit_gracefully_if_zulip_config_file_does_not_exist'): + with patch('zulip_bots.run.exit_gracefully_if_zulip_config_is_missing'): zulip_bots.run.main() mock_run_message_handler_for_bot.assert_called_with(bot_name='giphy', @@ -31,7 +31,7 @@ class TestDefaultArguments(TestCase): @patch('sys.argv', ['zulip-run-bot', path_to_bot, '--config-file', '/foo/bar/baz.conf']) @patch('zulip_bots.run.run_message_handler_for_bot') def test_argument_parsing_with_bot_path(self, mock_run_message_handler_for_bot: mock.Mock) -> None: - with patch('zulip_bots.run.exit_gracefully_if_zulip_config_file_does_not_exist'): + with patch('zulip_bots.run.exit_gracefully_if_zulip_config_is_missing'): zulip_bots.run.main() mock_run_message_handler_for_bot.assert_called_with( @@ -61,7 +61,7 @@ class TestDefaultArguments(TestCase): with patch('sys.argv', ['zulip-run-bot', bot_qualifier, '--config-file', '/path/to/config']): with patch('zulip_bots.finder.import_module_from_source', return_value=mock.Mock()): with patch('zulip_bots.run.run_message_handler_for_bot'): - with patch('zulip_bots.run.exit_gracefully_if_zulip_config_file_does_not_exist'): + with patch('zulip_bots.run.exit_gracefully_if_zulip_config_is_missing'): zulip_bots.run.main() self.assertIn(bot_dir_path, sys.path) @@ -74,7 +74,7 @@ class TestDefaultArguments(TestCase): with patch('sys.argv', ['zulip-run-bot', 'bot.module.name', '--config-file', '/path/to/config']): with patch('importlib.import_module', return_value=mock_bot_module) as mock_import_module: with patch('zulip_bots.run.run_message_handler_for_bot'): - with patch('zulip_bots.run.exit_gracefully_if_zulip_config_file_does_not_exist'): + with patch('zulip_bots.run.exit_gracefully_if_zulip_config_is_missing'): zulip_bots.run.main() mock_import_module.assert_called_once_with(bot_module_name)