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.)
This commit is contained in:
parent
3d68e394dc
commit
0fd093a698
|
@ -30,6 +30,22 @@ def get_bots_directory_path() -> str:
|
||||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
return os.path.join(current_dir, 'bots')
|
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):
|
class RateLimit(object):
|
||||||
def __init__(self, message_limit: int, interval_limit: int) -> None:
|
def __init__(self, message_limit: int, interval_limit: int) -> None:
|
||||||
|
|
|
@ -7,12 +7,15 @@ import os
|
||||||
from os.path import basename, splitext
|
from os.path import basename, splitext
|
||||||
|
|
||||||
from zulip_bots.lib import (
|
from zulip_bots.lib import (
|
||||||
|
zulip_env_vars_are_present,
|
||||||
run_message_handler_for_bot,
|
run_message_handler_for_bot,
|
||||||
NoBotConfigException,
|
NoBotConfigException,
|
||||||
)
|
)
|
||||||
from zulip_bots import finder
|
from zulip_bots import finder
|
||||||
from zulip_bots.provision import provision_bot
|
from zulip_bots.provision import provision_bot
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
def parse_args() -> argparse.Namespace:
|
def parse_args() -> argparse.Namespace:
|
||||||
|
@ -32,7 +35,6 @@ def parse_args() -> argparse.Namespace:
|
||||||
|
|
||||||
parser.add_argument('--config-file', '-c',
|
parser.add_argument('--config-file', '-c',
|
||||||
action='store',
|
action='store',
|
||||||
required=True,
|
|
||||||
help='zulip configuration file (e.g. ~/Downloads/zuliprc)')
|
help='zulip configuration file (e.g. ~/Downloads/zuliprc)')
|
||||||
|
|
||||||
parser.add_argument('--bot-config-file', '-b',
|
parser.add_argument('--bot-config-file', '-b',
|
||||||
|
@ -51,17 +53,36 @@ def parse_args() -> argparse.Namespace:
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def exit_gracefully_if_zulip_config_file_does_not_exist(config_file: str) -> None:
|
def exit_gracefully_if_zulip_config_is_missing(config_file: Optional[str]) -> None:
|
||||||
if not os.path.exists(config_file):
|
error_msg = None
|
||||||
print('''
|
|
||||||
ERROR: %s does not exist.
|
|
||||||
|
|
||||||
|
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
|
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
|
if you have already done that, you need to specify the file
|
||||||
location correctly.
|
location correctly on the command line.
|
||||||
''' % (config_file,))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
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:
|
def exit_gracefully_if_bot_config_file_does_not_exist(bot_config_file: str) -> None:
|
||||||
if bot_config_file is 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
|
# It's a bit unfortunate that we have two config files, but the
|
||||||
# alternative would be way worse for people running multiple bots
|
# alternative would be way worse for people running multiple bots
|
||||||
# or testing against multiple Zulip servers.
|
# 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)
|
exit_gracefully_if_bot_config_file_does_not_exist(args.bot_config_file)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -19,7 +19,7 @@ class TestDefaultArguments(TestCase):
|
||||||
@patch('sys.argv', ['zulip-run-bot', 'giphy', '--config-file', '/foo/bar/baz.conf'])
|
@patch('sys.argv', ['zulip-run-bot', 'giphy', '--config-file', '/foo/bar/baz.conf'])
|
||||||
@patch('zulip_bots.run.run_message_handler_for_bot')
|
@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:
|
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()
|
zulip_bots.run.main()
|
||||||
|
|
||||||
mock_run_message_handler_for_bot.assert_called_with(bot_name='giphy',
|
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('sys.argv', ['zulip-run-bot', path_to_bot, '--config-file', '/foo/bar/baz.conf'])
|
||||||
@patch('zulip_bots.run.run_message_handler_for_bot')
|
@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:
|
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()
|
zulip_bots.run.main()
|
||||||
|
|
||||||
mock_run_message_handler_for_bot.assert_called_with(
|
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('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.finder.import_module_from_source', return_value=mock.Mock()):
|
||||||
with patch('zulip_bots.run.run_message_handler_for_bot'):
|
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()
|
zulip_bots.run.main()
|
||||||
|
|
||||||
self.assertIn(bot_dir_path, sys.path)
|
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('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('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.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()
|
zulip_bots.run.main()
|
||||||
mock_import_module.assert_called_once_with(bot_module_name)
|
mock_import_module.assert_called_once_with(bot_module_name)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue