この記事は 『CRESCO Advent Calendar 2017』 13日目の記事です。

 

こんにちは。技術研究所の910です。
もう旬が過ぎてしまった感じもしますが、Chaliceを利用してSlackへのWebhookを投げるWebAPIを以前作ったので、そのときのことを備忘録として残します。

1. Slack

最早Slackについて説明する必要は無いと思いますが、敢えて言うならば

  • 公開されているプラグインを導入して好きなように拡張できる
  • Webhookを受けられる

という特徴を持ったチャットツールと言えると思います。

2. AWS Chalice

一言で言うとFlaskライクに書けて、簡単にLambdaとAPI GatewayにデプロイできるPython用のサーバレスフレームワークです。
導入方法や使い方、実装例はすべてChaliceのGitHubにまとめられているので、そちらをご参照ください。
ちなみに、私が利用したChaliceのバージョンは1.0.4になります。

Chalice導入の際の注意

Chaliceは、LambdaがサポートするPythonのバージョンのみをサポートしています。
対応していないバージョンのPythonで利用しようとすると、以下のようなエラーが出て使えません。

Note: make sure you are using python2.7 or python3.6.
The chalice CLI as well as the chalice python package will support the versions of python supported by AWS Lambda.
Currently, AWS Lambda supports python2.7 and python3.6, so that's what this project supports.

Chaliceに限らず、特定のバージョンのPythonでしか動かないライブラリは多々あるので、用途毎に仮想環境を作れるようにしておくと何かと便利です。
仮想環境を扱う手段としては以下のようなものがあります。

これらの違いについては、以下のページをご参照ください。

ちなみに私は最近pyenvからvenvに移行しました。

3. ChaliceからSlackにWebhookを投げる

前置きが長くなりましたが、ようやくここからが本題です。
Chaliceを使って、SlackにWebhookを投げる迄の流れを記載していきます。

ⅰ. Slack Incoming Webhook APIの作成

  1. Workspaceを作っていない場合はこちらを参考に、Workspaceを作成してください。

  1. Slack APIにアクセスし、Start Buildingを選択します。
  1. 1にて用意したWorkspaceを選択し、Create Appを選択します。
  1. Incoming Webhooksを選択します。
  1. Activate Incoming WebhookをONにします。
  1. Activate Incoming WebhookをONにするとWebhook URLs for Your Workspaceという項目が画面下部に表示されます。
    この中のAdd New Webhook to Workspaceを選択します。
  1. ここでWorkspaceのどのチャネルに対してメッセージを投げるのかを指定します。

    Post toにメッセージを投げたいチャネルを指定してAuthorizeを選択すると、Webhookを投げる為に使うURLと、メッセージを投げる為のcurlコマンドが表示されます。試しにこのcurlコマンドをターミナルで叩くと、指定されたチャネルに対してメッセージが投げられます。

ⅱ. PythonからSlackにWebhookを投げる

ようやくここからが実装の部分になります。
PythonからSlackにメッセージを投げる手段としては、大きく分けて2つあります。

・汎用ライブラリを利用する方法(requests)

まずは汎用的なHTTPライブラリを使い、POSTリクエストを投げる方法です。
Python 3にはurllib.requestが標準搭載されていますが、ここではよりシンプルに実装できるrequestsを使っています。
requestsはpip install requestsでインストールできます。

ちなみに、curlコマンドを見る限りではSlackにメッセージを投げる為にはWebhook URLメッセージの文面があれば良いので、ソースコードは非常にコンパクトになります。

# ----- to_slack_with_requests.py ----- #
import json
import requests
WEBHOOK_URL = "YOUR_WEBHOOK_URL"
def to_slack(text: str):
# curlで投げたデータと同じ形のデータを用意
payload = json.dumps({'text': text})
res = requests.post(WEBHOOK_URL, data=payload)

・slackweb

もう一つの方法は、slackwebというSlack専用のライブラリを使う方法です。
requestsと同様、pip install slackwebでインストールできます。

流石にSlack専用のライブラリだけあって、requestsを使う場合よりも更にコンパクトなソースコードになりました。

# ----- to_slack_with_slackweb.py ----- #
import slackweb
WEBHOOK_URL = "YOUR_WEBHOOK_URL"
slack = slackweb.Slack(url=WEBHOOK_URL)
def to_slack(text: str):
slack.notify(text=text)

こちらもto_slack("Slackwebからきました。")を実行して、Slackにメッセージが投げられるのを確認できました。

このように、僅か数行のスクリプトで簡単にSlackにメッセージを投げられるということが分かるかと思います。

ⅲ. ローカルでChaliceを動かしてSlackにメッセージを投げる

ようやくここからChaliceを使っていきます。
いきなりデプロイするのは怖いので、とりあえずはローカルでChaliceを動かしてSlackにメッセージを投げてみます。
また、先程はrequestsslackwebを使う2つの方法を示しましたが、以降はslackwebを利用したスクリプトを使います。

Chaliceもrequestsなどと同様、pip install chaliceで導入できます。
また、Chaliceの導入と同時にchaliceコマンドにPATHが通されます。

chaliceコマンドで使えるオプションはいくつかあるのですが、今回は以下の3つしか使いません。
オプションを全てチェックしたいときはchalice --helpで確認してみてください。
また、この作業ではChalice Python Serverless Microframework for AWS 1.0.4 documentationを参照しました。

機能 概要
deploy ChaliceのプロジェクトをLambda、API Gatewayにデプロイする
local Chaliceのプロジェクトをlocalhostで実行する
new-project 新規プロジェクトを作成する
  1. 新規プロジェクトの作成
    chalice new-projectコマンドを任意のディレクトリで実行すると、最小限の構成のディレクトリが生成されます。
# Chaliceの新規プロジェクトを作成
(chalice)$ chalice new-project hook_to_slack
# 新規プロジェクトの構造
# ※ treeコマンドは brew install tree で導入できる(macOSの場合)
(chalice)$ tree hook_to_slack/
hook_to_slack/
├── app.py
└── requirements.txt
  • 必要なライブラリの配置 & requirements.txtへの追記
    Chaliceを利用する場合、指定されたディレクトリに手持ちのスクリプトやライブラリのファイルを配置しておくことでそれらのライブラリを利用することができます。
    その為、既存のモジュールを再利用したり、適宜モジュール分割したりといったことができます。
    ディレクトリ構成のルールはこちらにあるので、適宜ご参照ください。

  • 先程実装したto_slack_with_slackweb.pyslackwebライブラリを配置して、最終的にはこのようなディレクトリ構成になりました。

    (chalice)$ tree hook_to_slack/
    hook_to_slack/
    ├── app.py
    ├── chalicelib # 手持ちのモジュールを入れるトコ
    │   └── to_slack_with_requests.py
    ├── requirements.txt # サードパーティ製のライブラリの一覧
    └── vendor # pipで取得したサードパーティ製のライブラリを入れるトコ
    └── slackweb-1.0.5-py3-none-any.whl

    また、Pythonを使っている方ならばお馴染みのrequirements.txtも用意する必要があります。
    以下のように、サードパーティ製のライブラリの名称を記載して配置してください。
    今回はslackwebが必要なので、このような内容のrequirements.txtを用意しました。

    # ----- requirements.txtの記載例 ----- #
    slackweb==1.0.5

    ライブラリの形式について

    pip downloadで取得したファイルの形式がwhl形式でない場合、デプロイする際にはwhl形式に変換する必要があります。
    以下の流れで変換してからvendorディレクトリに配置してください。

    # ----- ライブラリのダウンロード 〜 whl形式への変換迄の流れ ----- #
    # wheelが入っていない場合は入れる
    (chalice)$ pip install wheel
    (chalice)$ cd vendor
    (chalice)$ pip download slackweb # 使いたいライブラリをダウンロード
    Collecting slackweb Using cached slackweb-1.0.5.tar.gz
    Saved ./slackweb-1.0.5.tar.gz
    Successfully downloaded slackweb
    (chalice)$ pip wheel slackweb-1.0.5.tar.gz # whl形式に変換
    Processing ./slackweb-1.0.5.tar.gz
    Building wheels for collected packages: slackweb
    Running setup.py bdist_wheel for slackweb ... done
    Stored in directory: /Users/user/hook_to_slack/vendor
    Successfully built slackweb
    (chalice)$ ls -al
    total 16
    drwxr-xr-x 4 user staff 128 Nov 10 14:39 .
    drwxr-xr-x 9 user staff 288 Nov 10 14:39 ..
    -rw-r--r-- 1 user staff 2617 Nov 10 14:39 slackweb-1.0.5-py3-none-any.whl
    -rw-r--r-- 1 user staff 1337 Nov 10 14:39 slackweb-1.0.5.tar.gz
    (chalice)$ rm slackweb-1.0.5.tar.gz # 要らない方は消す

    リファレンスにはもっと詳細な説明がありますので、もしサードパーティ製ライブラリに起因する問題が発生した場合にはこちらを参照してみてください。

  • リクエストを受けた際の処理を実装する
    app.pyにリクエストを受けたときの処理を実装します。
    既にSlackに対してPOSTリクエストを投げる部分は実装済みですので、その関数を呼び出すだけで済みます。
    また、書き方はFlaskとそっくりなので、非常に書き易いかと思います。
    (スクリプトのファイル名が冗長ですが、ここでは眼をつむってください…)

  • from chalice import Chalice
    from chalicelib.to_slack_with_slackweb import to_slack
    app = Chalice(app_name="post2slack")
    app.debug = True
    @app.route('/toslack', methods=['POST'])
    def index():
    text = app.current_request.json_body["text"]
    to_slack(text)
    1. Chaliceをローカルで動かして動作確認
      ここ迄終わったらchalice localを実行して動作確認をします。
      これで問題なくメッセージが投げられれば、後はデプロイするだけです。
    # Chaliceをローカルで実行
    (chalice)$ chalice local
    Serving on localhost:8000
    # 別のターミナルからcurlを叩く
    (chalice)$ curl -v -H "Content-Type: application/json" -d '{"text":"chaliceからきました"}' localhost:8000/toslack
    * Trying 127.0.0.1...
    * TCP_NODELAY set
    * Connected to localhost (127.0.0.1) port 8000 (#0)
    POST /toslack HTTP/1.1
    Host: localhost:8000
    User-Agent: curl/7.54.0
    Accept: */*
    Content-Type: application/json
    Content-Length: 36
    * upload completely sent off: 36 out of 36 bytes
    HTTP/1.1 200 OK
    Server: BaseHTTP/0.6 Python/3.6.3
    Date: Fri, 10 Nov 2017 06:16:43 GMT
    Content-Length: 4
    Content-Type: application/json
    * Connection #0 to host localhost left intact

    問題がなければこのように、メッセージが投げられるはずです。

    ⅳ. Lambda, API Gatewayにデプロイしてメッセージ投げる

    デプロイを行う為には認証情報が必要となりますので、予めAWS CLIのセットアップを済ませておいてください。
    CLIのセットアップが終わっていれば、deployコマンドを叩くだけでデプロイされます。

    (chalice)$ chalice deploy
    Creating role: hook_to_slack-dev
    Creating deployment package.
    Creating lambda function: hook_to_slack-dev
    Initiating first time deployment.
    Deploying to API Gateway stage: api
    https://@@@@@@@@@@@@@@@@@@@@.execute-api.ap-northeast-1.amazonaws.com/api/

    chalice deployすると、最後にURLが表示されます。
    このURLを使ってAPIの呼び出しを行いますので、このURLを控えておいてください。

    デプロイ可能なファイルサイズについて

    2017年11月現在では、Lambdaには合計50MB迄アップロードできます。
    TensorFlowを入れたプロジェクトをchalice deployしようとしたら、Lambdaのファイル容量の制限に引っ掛かってしまいデプロイすることができませんでした…

    まとめ

    正直、拍子抜けするくらい簡単にWebAPIが作れてびっくりしました。
    ちなみにLINE BOTでもWebhookを受けられるので、ChaliceでBOTを拡張していくのも楽しそうだなーと思っています。