Skip to content

Commit edcde18

Browse files
authored
Merge pull request #1 from Perceptyx/apply-patch-93-upstream
Add patch slack-samples#93 from upstream
2 parents 1adc5df + 7bec018 commit edcde18

File tree

3 files changed

+161
-0
lines changed

3 files changed

+161
-0
lines changed

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ Installation
5656

5757
For example, if your python path includes '/path/to/myproject' and you include `plugins.repeat.RepeatPlugin` in ACTIVE_PLUGINS, it will find the RepeatPlugin class within /path/to/myproject/plugins/repeat.py and instantiate it, then attach it to your running RTMBot.
5858

59+
### Environment variables
60+
61+
All of the configurations variables mentioned can be overriden using environment
62+
variables. For example, the following will override slack token.
63+
64+
$ SLACK_TOKEN="xoxb-foo-bar" rtmbot
65+
66+
This is especially useful when you want or need to hide credentials.
67+
5968
A Word on Structure
6069
-------
6170
To give you a quick sense of how this library is structured, there is a RtmBot class which does the setup and handles input and outputs of messages. It will also search for and register Plugins within the specified directory(ies). These Plugins handle different message types with various methods and can also register periodic Jobs which will be executed by the Plugins.
@@ -127,6 +136,29 @@ The repeat plugin will now be loaded by the bot on startup. Run `rtmbot` from co
127136
rtmbot
128137
```
129138

139+
### Configuration
140+
141+
You can configure your plugins using `rtmbot.conf` and environment variables.
142+
For example, running the following
143+
144+
$ REPEAT_PLUGIN_DUMMY_VARIABLE="true" rtmbot
145+
146+
will result in effectively same as the following `rtmbot.conf`:
147+
148+
```
149+
plugins.repeat.RepeatPlugin:
150+
dummy_variable: "true"
151+
```
152+
153+
Note that environment variable values are treated as strings. If you need to
154+
override for example lists, booleans or numbers, handle it in your plugin. Plugin
155+
configuration can be accessed in the plugin from `self.plugin_config`.
156+
157+
```python
158+
def process_message(self, data):
159+
print(self.plugin_config['dummy_variable']) # prints "true"
160+
```
161+
130162
Create Plugins
131163
--------
132164

rtmbot/bin/run_rtmbot.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import sys
44
import os
55
import yaml
6+
import re
67

78
from rtmbot import RtmBot
89

@@ -20,12 +21,61 @@ def parse_args():
2021
return parser.parse_args()
2122

2223

24+
def prefix_from_plugin_name(name):
25+
basename = name.split('.')[-1]
26+
# Attribution: https://stackoverflow.com/a/1176023
27+
splitted = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', basename)
28+
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', splitted).upper() + '_'
29+
30+
31+
def load_overrides_from_env(config):
32+
'''
33+
Read and apply overrides from environment variables. For plugins, name of the class
34+
is turned into a prefix. For example:
35+
YourAwesomePlugin.foo_bar => YOUR_AWESOME_PLUGIN_FOO_BAR
36+
'''
37+
def boolish(v):
38+
return v == '1' or v == 'true' # bool('false') == True, so do it the hard way
39+
40+
params = [
41+
{'name': 'SLACK_TOKEN', 'type': str},
42+
{'name': 'BASE_PATH', 'type': str},
43+
{'name': 'LOGFILE', 'type': str},
44+
{'name': 'DEBUG', 'type': boolish},
45+
{'name': 'DAEMON', 'type': boolish}
46+
]
47+
48+
# Override the rtmbot-specific variables. Here we can take advantage
49+
# of the fact that we know what type they are supposed to be in.
50+
config.update({
51+
param['name']: param['type'](os.environ[param['name']])
52+
for param in params
53+
if param['name'] in os.environ})
54+
55+
# Override plugin-specific variables. Since we don't know a schema,
56+
# treat values as string. Leave type conversion for plugins themselves.
57+
for plugin in config.get('ACTIVE_PLUGINS', []):
58+
prefix = prefix_from_plugin_name(plugin)
59+
plugin_configs = [
60+
var for var in os.environ
61+
if var.startswith(prefix)]
62+
63+
# Create if necessary
64+
if plugin not in config:
65+
config[plugin] = {}
66+
67+
config[plugin].update({
68+
param.split(prefix)[-1].lower(): os.environ[param]
69+
for param in plugin_configs})
70+
71+
2372
def main(args=None):
2473
# load args with config path if not specified
2574
if not args:
2675
args = parse_args()
2776

2877
config = yaml.load(open(args.config or 'rtmbot.conf', 'r'))
78+
load_overrides_from_env(config)
2979
bot = RtmBot(config)
3080
try:
3181
bot.start()

tests/test_rtmbot_runner.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import os
2+
3+
from contextlib import contextmanager
4+
from rtmbot.bin.run_rtmbot import load_overrides_from_env
5+
6+
@contextmanager
7+
def override_env(env):
8+
_environ = dict(os.environ)
9+
try:
10+
os.environ.clear()
11+
os.environ.update(env)
12+
yield
13+
finally:
14+
os.environ.clear()
15+
os.environ.update(_environ)
16+
17+
def test_normal_overrides():
18+
config = {
19+
'SLACK_TOKEN': 'xoxb-foo-bar',
20+
'BASE_PATH': os.getcwd(),
21+
'LOGFILE': 'foobar.log',
22+
'DEBUG': True,
23+
'DAEMON': True
24+
}
25+
26+
override = {
27+
'SLACK_TOKEN': 'xoxb-this-should-change',
28+
'BASE_PATH': '/opt/nope',
29+
'LOGFILE': 'awesome.log',
30+
'DEBUG': 'false',
31+
'DAEMON': 'false'
32+
}
33+
34+
with override_env(override):
35+
load_overrides_from_env(config)
36+
expected = {
37+
'SLACK_TOKEN': 'xoxb-this-should-change',
38+
'BASE_PATH': '/opt/nope',
39+
'LOGFILE': 'awesome.log',
40+
'DEBUG': False,
41+
'DAEMON': False
42+
}
43+
assert config == expected
44+
45+
def test_plugin_overrides():
46+
config = {
47+
'ACTIVE_PLUGINS': ['plugins.AwesomePlugin']}
48+
49+
override = {
50+
'AWESOME_PLUGIN_FOO': '1',
51+
'AWESOME_PLUGIN_BAR_BAR': 'some_awesome_value'
52+
}
53+
54+
with override_env(override):
55+
load_overrides_from_env(config)
56+
expected = {
57+
'foo': '1',
58+
'bar_bar': 'some_awesome_value'
59+
}
60+
assert config['plugins.AwesomePlugin'] == expected
61+
62+
def test_plugin_config_already_exists():
63+
config = {
64+
'ACTIVE_PLUGINS': ['plugins.AwesomePlugin'],
65+
'plugins.AwesomePlugin': {
66+
'some_credential': 'foo'
67+
}
68+
}
69+
70+
override = {
71+
'AWESOME_PLUGIN_SOME_CREDENTIAL': 'bar'
72+
}
73+
74+
with override_env(override):
75+
load_overrides_from_env(config)
76+
expected = {
77+
'some_credential': 'bar'
78+
}
79+
assert config['plugins.AwesomePlugin'] == expected

0 commit comments

Comments
 (0)