Errbot on Docker のデバッグ環境構築手順

## Errbotとは
chatbotの１つでslackなどのチャットサービスにbotとして簡単に導入できます。
python実装のため、プラグイン開発もpythonで行えます。

## 実現したいこと
ローカルフォルダで開発中のプラグインをDocker内のerrbotで実行し、VSCodeでデバッグ実行できる。
開発後は、slackに接続してchatbot運用する。

## 動作確認したDockerバージョン
```shell-session
$ docker version
Client: Docker Engine - Community
 Version:           18.09.2
 API version:       1.39
 Go version:        go1.10.8
 Git commit:        6247962
 Built:             Sun Feb 10 04:12:39 2019
 OS/Arch:           darwin/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          18.09.2
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.6
  Git commit:       6247962
  Built:            Sun Feb 10 04:13:06 2019
  OS/Arch:          linux/amd64
  Experimental:     true
```

## Errbot on Dockerの構築
errbot をビルドするために `Dockerfile`,`requirements.txt`,`entrypoint.sh`,errbot設定ファイル`srv/config.py`を用意します。

```shell-session:ファイル一覧
docker-errbot/
|--Dockerfile
|--entrypoint.sh
|--requirements.txt
|--srv
|  |--config.py
```

```shell-session:Dockerfile
FROM python:alpine
WORKDIR /app
COPY . /app
# Install Requirments Packages.
RUN apk add --no-cache --virtual .build-deps \
    gcc \
    libffi-dev \
    musl-dev \
    openssl-dev \
    && : Install pip packages \
    && python -m pip install -r requirements.txt \
    && : Delete build-deps packages \
    && apk del --purge .build-deps \
    && : Delete pip cache files \
    && rm -rf /root/.cache
# Install minimal Packages, and Setup Errbot.
RUN apk add --no-cache \
    bash \
    shadow \
    tzdata \
    && : Setup timezone \
    && cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \
    && echo "Asia/Tokyo" > /etc/timezone \
    && : Setup user \
    && groupadd -r errbot && useradd -m -r -g errbot errbot \
    && : Setup errbot \
    && errbot --init \
    && : Change owner and group \
    && chown -R errbot:errbot /app \
    && : Change permission  \
    && chmod +x entrypoint.sh

# Run
USER errbot
ENTRYPOINT [ "./entrypoint.sh" ]
CMD [ "run" ]
```

- できるだけイメージサイズを小さくしたいので`python:alpine`をベースイメージとして選択します
- pip install 時だけに必要なパッケージは、`apk add --no-cache --virtual .build-deps`でインストールし、pip install 後に`apk del --purge .build-deps`で削除します
- pip install 時のキャッシュファイル`/root/.cache`も削除します
- timezoneは`Asia/Tokyo`に設定します
- ユーザ`errbot`でerrbotを起動します

```shell-session:requirements.txt
errbot
slackclient
websocket-client
ptvsd
```

- `ptvsd(Python Tools for Visual Studio debug server)`を導入することでVSCodeからリモートデバッグできるようになります

```shell-session:entrypoint.sh
#!/bin/bash -eu

[[ -v DEBUG ]] && ${DEBUG} && set -x

case ${1} in
    run)
        errbot -c srv/config.py
        ;;
    debug)
        python -m ptvsd --host 0.0.0.0 --port 5678 /usr/local/bin/errbot -c srv/config.py
        ;;
    *)
        exec "$@"
        ;;
esac
```

- entrypoint.shスクリプトをデバッグしたい時は`DEBUG=true`とすることで`set -x`が実行されます
- `entrypoint.sh run`で通常実行
- `entrypoint.sh debug`でデバッグ実行
- デバッグ実行時は ptvsd が `0.0.0.0:5678` でデバッグ接続を待ち受けて errbot を起動します

```shell-session:config-tempalateとの差分
$ diff -u config-tempalate.py srv/config.py
--- config-template.py	2019-05-04 18:02:05.000000000 +0900
+++ srv/config.py	2019-05-04 18:13:20.000000000 +0900
@@ -17,6 +17,7 @@
 ##########################################################################
 
 import logging
+import os
 
 ##########################################################################
 # Core Errbot configuration                                              #
@@ -44,7 +45,7 @@
 # 'Telegram' - cloud-based mobile and desktop messaging app with a focus
 #              on security and speed. (https://telegram.org/)
 
-# BACKEND = 'XMPP'  # defaults to XMPP
+BACKEND = os.environ.get('BACKEND', 'Text')
 
 # STORAGE selection.
 # This configures the type of persistence you wish to use Errbot with.
@@ -63,7 +64,7 @@
 
 # The location where all of Err's data should be stored. Make sure to set
 # this to a directory that is writable by the user running the bot.
-BOT_DATA_DIR = '/var/lib/err'
+BOT_DATA_DIR = '/app/data'
 
 ### Repos and plugins config.
 
@@ -82,7 +83,7 @@
 # locally before publishing it. Note that you can specify only a single
 # directory, however you are free to create subdirectories with multiple
 # plugins inside this directory.
-BOT_EXTRA_PLUGIN_DIR = None
+BOT_EXTRA_PLUGIN_DIR = '/app/plugins'
 
 # If you use an external backend as a plugin,
 # this is where you tell Errbot where to find it.
@@ -126,7 +127,7 @@
 # If you encounter any issues with Err, please set your log level to
 # logging.DEBUG and attach a log with your bug report to aid the developers
 # in debugging the issue.
-BOT_LOG_LEVEL = logging.INFO
+BOT_LOG_LEVEL = logging.getLevelName(os.environ.get('BOT_LOG_LEVEL', 'INFO'))
 
 # Enable logging to sentry (find out more about sentry at www.getsentry.com).
 # This is optional and disabled by default.
@@ -153,9 +154,9 @@
 # The identity, or credentials, used to connect to a server
 BOT_IDENTITY = {
     # XMPP (Jabber) mode
-    'username':
-    'err@localhost',  # The JID of the user you have created for the bot
-    'password': 'changeme',  # The corresponding password for this user
+    # 'username':
+    # 'err@localhost',  # The JID of the user you have created for the bot
+    # 'password': 'changeme',  # The corresponding password for this user
     # 'server': ('host.domain.tld',5222), # server override
 
     ## HipChat mode (Comment the above if using this mode)
@@ -170,7 +171,7 @@
     # 'endpoint': 'https://api.hipchat.com'
 
     ## Slack mode (comment the others above if using this mode)
-    # 'token': 'xoxb-4426949411-aEM7...',
+    'token': os.environ.get('BOT_SLACK_TOKEN'),
     ## you can also include the proxy for the SlackClient connection
     # 'proxies': {'http': 'some-http-proxy', 'https': 'some-https-proxy'}
 
@@ -198,7 +199,7 @@
 #
 # Unix-style glob patterns are supported, so 'gbin@localhost'
 # would be considered an admin if setting '*@localhost'.
-BOT_ADMINS = ('gbin@localhost', )
+BOT_ADMINS = tuple(os.environ.get('BOT_ADMINS', '@admin').split(','), )
 
 # Set of admins that wish to receive administrative bot notifications.
 #BOT_ADMINS_NOTIFICATIONS = ()
@@ -238,7 +239,8 @@
 # names, rather than the BOT_PREFIX above. This option allows you to
 # specify alternative prefixes the bot will respond to in addition to
 # the prefix above.
-#BOT_ALT_PREFIXES = ('Err',)
+BOT_ALT_PREFIXES = tuple(
+    os.environ.get('BOT_ALT_PREFIXES', 'Err').split(','), )
 
 # If you use alternative prefixes, you might want to allow users to insert
 # separators like , and ; between the prefix and the command itself. This
```

- errbotの[config-template](https://raw.githubusercontent.com/errbotio/errbot/master/errbot/config-template.py)を取得し、上記のように書き換えて`srv/config.py`にします
- デフォルトはTextモードで起動されます
- `BACKEND=Slack`とすることでslackモードで起動し、slackに接続されます
- slackのAPIトークンは`BOT_SLACK_TOKEN=xoxb-...`で設定します

## dockerイメージのビルド

```shell-session
$ docker build ./ -t errbot
Sending build context to Docker daemon  50.18kB
Step 1/8 : FROM python:alpine
 ---> 715a1f28828d
Step 2/8 : WORKDIR /app
 ---> Running in 1a057f81fc87
Removing intermediate container 1a057f81fc87
 ---> 9cca706eba25
Step 3/8 : COPY . /app
 ---> 780a7a9beb56
Step 4/8 : RUN apk add --no-cache --virtual .build-deps     gcc     libffi-dev     musl-dev     openssl-dev     && : Install pip packages     && python -m pip install -r requirements.txt     && : Delete build-deps packages     && apk del --purge .build-deps     && : Delete pip cache files     && rm -rf /root/.cache
 ---> Running in c19b0fedc8f4
fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz
(1/16) Installing binutils (2.31.1-r2)
(2/16) Installing gmp (6.1.2-r1)
(3/16) Installing isl (0.18-r0)
※省略
```

## Errbotを通常(Text)モードで起動確認
下記のコマンドでerrbotを起動します。
`[@admin ➡ @errbot] >>>` 
でerrbotの起動が成功し、コマンド待ち状態となります。

```shell-session
$ docker run --rm -it --name errbot errbot
20:29:58 INFO     errbot.bootstrap          Found Storage plugin: Shelf.
20:29:58 INFO     errbot.bootstrap          Found Backend plugin: Text
20:29:59 INFO     errbot                    webhooks:  Flag to bind /echo to echo
────────────────────────────────────────────────────────────────────────────────
 You start as a bot admin in a one-on-one conversation with the bot.

    Context of the chat

• Use !inroom to switch to a room conversation.
• Use !inperson to switch back to a one-on-one conversation.
• Use !asuser to talk as a normal user.
• Use !asadmin to switch back as a bot admin.

    Preferences

• Use !ml to flip on/off the multiline mode (Enter twice at the end to send).
────────────────────────────────────────────────────────────────────────────────

[@admin ➡ @errbot] >>> 
```

## VSCode側のデバッグ構成
VSCodeでデバッグ構成ファイル(launch,json)を下記のように設定します

```shell-session:launch.json
"configurations": [
    {
        "name": "Python: Attach",
        "type": "python",
        "request": "attach",
        "port": 5678,
        "host": "localhost",
        "pathMappings": [
            {
                "localRoot": "${workspaceFolder}/plugins",
                "remoteRoot": "/app/plugins"
            }
        ]
    },
※省略
```

- `Python: Attach`実行時に`localhost:5678`に接続します
- `Python: Attach`実行時にローカルの`plugins`フォルダとリモートの`/app/plugins`フォルダをマッピングします

## Errbotをデバッグ(Text)モードで起動
下記コマンドでerrbotを起動してVSCodeでデバッグ`Python:attach`を実行するとブレークポイントの指定や変数の状態なども確認でき、デバッグが簡単にできるようになります。

```shell-session
$ docker run --rm -it --name errbot -v $(pwd)/plugins:/app/plugins:ro -p 5678:5678 -e DEBUG=true errbot debug
+ case ${1} in
+ python -m ptvsd --host 0.0.0.0 --port 5678 /usr/local/bin/errbot -c srv/config.py
20:42:37 INFO     errbot.bootstrap          Found Storage plugin: Shelf.
20:42:37 INFO     errbot.bootstrap          Found Backend plugin: Text
20:42:39 INFO     errbot                    webhooks:  Flag to bind /echo to echo
────────────────────────────────────────────────────────────────────────────────
 You start as a bot admin in a one-on-one conversation with the bot.

    Context of the chat

• Use !inroom to switch to a room conversation.
• Use !inperson to switch back to a one-on-one conversation.
• Use !asuser to talk as a normal user.
• Use !asadmin to switch back as a bot admin.

    Preferences

• Use !ml to flip on/off the multiline mode (Enter twice at the end to send).
────────────────────────────────────────────────────────────────────────────────

[@admin ➡ @errbot] >>> 
```

- pluginsフォルダ配下に開発中のプラグインソースを置きます
- docker起動時の -vオプションで`plugins`フォルダをdocker内`/app/plugins`にマウントします
- docker起動時の -pオプションで`port 5678`をdocker内のptvsdが待ち受けている`port 5678`に接続できます
- docker起動時の -eオプションで`DEBUG=true`で`entrypoin.sh`スクリプトの実行も確認できます

# slackのchatbotとしての運用方法
slackモードで運用するときには、docker runで渡すパラメータが増えて大変なのでdocker-composeで起動できるように下記の`docker-compose.yml`を用意します

```shell-session:docker-compose.yml
version: '2.1'

services:
  errbot:
    image: errbot
    build: 
      context: .
    environment:
      - BACKEND=Slack
      - BOT_SLACK_TOKEN
      - BOT_ADMINS=@admin
    stdin_open: true
    tty: true
```

`BOT_SLACK_TOKEN`にslackのAPIトークンを直接記載してもよいのですが、`docker-compose.yml`もgit管理にしたいので`BOT_SLACK_TOKEN`は`.env`ファイルに記載します。
`.env`ファイルを`.gitignore`でgit管理外に設定することでAPIトークンを誤ってgithubにpushしないようにするためです。

```shell-session:.env
BOT_SLACK_TOKEN=xoxb-...
```