Python
AWSLambda
CiscoSpark

Cisco Spark の Bot を AWS Lambda + Amazon API Gateway (Python) で実装してみる

More than 1 year has passed since last update.


1. Cisco Spark Bot の概要

Cisco Spark には Bot 機能が用意されている。人にメンションして話かける代わりに、Bot にメンションして話しかけると、Bot はそれをコマンドとして理解して処理を行い、結果のメッセージを返す(実際には Bot は中継をするだけで実処理は別のプログラムが行う)。

「どんな処理」をして「どんな結果」を返すかはアイデア次第。ここでは最も単純な処理として、ping というメッセージに対して pong と返答する Bot を作ってみる。実処理の部分は AWS Lambda + Amazon API gateway を使うこととする。


1-1. 処理の全体像


  1. Bot にメンションしてメッセージを投稿

  2. Bot が Target URL 宛てに次の形式に従って送信(中継):


    • 必ず HTTP POST を使う

    • ペイロードは定型のフォーマット(Envelop と呼ぶ)が使われる

    • 非定型のデータは Envelop の中の ‘data’ へ入れられる



  3. Target URL を持つのは API Gateway の役割

  4. Lambda は API Gateway の受信を「トリガー」として起動

  5. Bot からの POST に対するリアクションは Lambda にて自分でプログラミングする必要がある

CiscoSparkBot.png

注意点として、2. で Bot が Target URL に HTTP POST する際、ペイロードは メッセージそのものを含まず、メッセージ ID しか含まない。メッセージそのものを取得するには Lambda から Cisco Spark に対して HTTP GET を実施し、ID に対応するメッセージを取得する必要がある。このプログラムは自分で用意する(図中の 6)。また、結果を Cisco Spark で表示するプログラム(HTTP POST)も用意が必要(図中の 7)。


2. Cisco Spark 側の設定手順


2-1. Bot の作成

Bot を作成するには実ユーザでログインする必要がある。Bot は実ユーザに紐づいて登録されるため。

以下のサイトへアクセスして「Create a Bot」をクリックする:

https://developer.ciscospark.com/add-app.html

image.png

続く画面で以下の三項目を全て埋める:


  • Display Name:Bot の名前

  • Bot Username:識別子。Bot の名前と同じでも良いが他の人と重複してはならない

  • Icon:画像の URL を入力

image.png

正しく入力すると Bot 用の Access Token が表示されるのでメモする。失効はしない。取り扱いに注意。


2-2. Room へ Bot を追加

Cisco Spark を開いて任意の Room へ行き、人を Room へ加えるのと同じ要領で Bot を Room へ追加する(Bot の名前で検索した後、追加)。


2-3. Webhook について

Cisco Spark へメッセージを投稿すると、Bot はそれを中継し、HTTP POST で別のプログラムへ渡す。プログラムの処理方法は様々だが通常結果は Bot へ戻され(いわゆるコールバック)Cisco Spark 上に表示する。HTTP によるコールバックの仕組みを Webhook と呼ぶ。

Webhook を設定する上で特に重要なのが Target URL である。インターネット上に存在する指定の URL で Bot が HTTP POST を投げる宛先となる。

Target URL 及びプログラムは Bot を作る人が用意しなければならない。そのために、例えば・・・サーバを準備して、OS をインストールして、Web サーバを稼働させ、IPアドレスを割り当てた後、URL を割り当てる・・・等と従来の方法を使っても良いが、相当な手間がかかるので、ここでは API Gateway を使って Target URL を用意し、実プログラムを Lambda で動作させる。

Target URL を決定するには Lambda + API Gateway の設定を先に行う必要がある。Cisco Spark の Webhook の設定は 4-1. で行う。


3. AWS Lambda + Amazon API Gateway 側の設定

※ この項は Amazon API Gateway での AWS Lambda の使用 (オンデマンド HTTPS 経由) のチュートリアルを基本情報として、Cisco Spark Bot へ対応するよう必要に応じて手順をカスタマイズしています。

※ AWS マネジメントコンソール、及び、AWS CLI の設定が完了しアクセス可能であることが前提です。完了していない場合はこちらの記事の「ステップ 1: 準備」等を参考に設定を完了して下さい。


3-1. Python 用デプロイパッケージを作成する

デプロイパッケージとは以下の二つを zip ファイルとして圧縮したもの:


  • Lambda のプログラム(今回は Python3.6)

  • 上記が使用するライブラリ

作成は以下の手順で行う:

1.AWS CLI へログインする。

2.Lambda 用の Python プログラムを作成する。ファイル名は Lambda 関数名と同じにする(LF4CiscoSpark)。ここでは以下のサンプルプログラムを作成する:

$ vi LF4CiscoSpark.py

---
from __future__ import print_function

import boto3
import json
import requests
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

access_code = '' # BotのAccess Codeを入力
botDisplayName = '' # Bot名を入力

def sendSparkGET(event):
url = 'https://api.ciscospark.com/v1/messages/{0}'.format(event.get('data')['id'])
headers = {
'Authorization' : 'Bearer ' + access_code,
'Content-Type' : 'application/json'
}
r1 = requests.get(url, headers = headers)
return json.loads(r1.text)

def sendSparkPOST(event, message_detail):
url = 'https://api.ciscospark.com/v1/messages/'
headers = {
'Authorization' : 'Bearer ' + access_code,
'Content-Type' : 'application/json'
}
payload = {
"roomId" : event.get('data')['roomId'],
"text" : 'pong'
}
r1 = requests.post(url, headers = headers, data = json.dumps(payload))
return True

def handler(event, context):
print("Received event: " + json.dumps(event, indent=2))

message_detail = sendSparkGET(event)
bot_command = message_detail['text']

bot_commands = {
botDisplayName + 'ping' : lambda x, y : sendSparkPOST(x, y)
}

if bot_command in bot_commands:
return bot_commands[bot_command](event, message_detail)
else:
raise ValueError('Unrecognized operation')
---

3.上記プログラム中で requests を import しているが、Lambda の実行環境には標準で含まれていない。標準外ライブラリを使いたい場合、デプロイパッケージへ該当ライブラリ( requests)のファイルを含める必要がある。対応ファイルは通常 site-packages の中にあるはずなので、まず site-packages/requests ディレクトリを zip する:

$ zip LF4CiscoSpark.zip -r /(path-to-site-packages)/site-packages/requests

4.次に、今作成した LF4CiscoSpark.py を zip ファイルへ追加する:

$ zip -g LF4CiscoSpark.zip LF4CiscoSpark.py


プログラムについて

Bot にメンションして "ping" とメッセージを送ると、Bot が "pong" と返信してくれる。 "ping" 以外のコマンドを使いたい場合は bot_commands に処理を記述すればよい。


  • sendSparkGET() … 「1-1. 処理の全体像」の図中 6 に対応

  • sendSparkPOST() … 「1-1. 処理の全体像」の図中 7 に対応


3-2. 実行ロール (IAM role) を作成する

IAM はユーザーがどの AWS リソースへアクセスできるか、そのリソースをどのような方法で使用できるかを制御する仕組み。一般的な設定手順としては、1)ロールの作成 2)ポリシーの作成 3)ロールへポリシーをアタッチ、の3ステップを行う。今回ポリシーは標準ポリシーを使うので2)は行わなくてよい。手順が完了すると ロール ARN と呼ばれる文字列が表示される。これは次の手順で Lambda 関数を作成するのに必要。


  1. IAM コンソール (https://console.aws.amazon.com/iam/) にサインインする。

  2. 「ロール」をクリック。

  3. 「新しいロールの作成」をクリック。

  4. 「AWS サービスロール」が選択されていることを確認。

  5. 「AWS Lambda」の横の「選択」をクリック。

  6. 「フィルター」と記載されたフィールドに lambda と入力

  7. 表示された標準ポリシーから適切なポリシーにチェックを入れる。ここでは AWSLambdaBasicExecutionRole を選択する。注)セキュリティ上適切なポリシーを選択することは重要。こちらに AWS 標準ポリシーの一覧があるので "AWSLambda" でポリシー名を検索して AWSLambdaBasicExecutionRole より適切なものがあればそちらを選択する。"Action" に注目して比較するとよい。

  8. 「ロール名」は lambda-gateway-execution-role とする。

  9. 「ロールの作成」をクリック。

  10. 今作成した lambda-gateway-execution-role (チェックを入れるのではなくロール名)をクリック

  11. ロール ARN をメモする(arn:で始まる文字列)。これは次のステップで Lambda 関数を作るのに必要


3-3. Lambda 関数を作成する

1.AWS CLI へログインする。

2.以下のコマンドで lambda 関数を作成する。arn: には前の手順で取得したものを、region と profile には適切なものを指定する。zip-file 名と handler 名は function-name と同じにする必要がある模様。

$ aws lambda create-function --region us-west-2 \

--function-name LF4CiscoSpark \
--zip-file fileb://LF4CiscoSpark.zip \
--role arn:xxxxxxxxxxxxxxxxxx \
--handler LF4CiscoSpark.handler \
--runtime python3.6 \
--profile adminuser

※ なお、コードを update するときは以下:

$ aws lambda update-function-code \

--function-name LF4CiscoSpark \
--zip-file fileb://LF4CiscoSpark.zip


3-4. API Gateway を作成する

今回 API Gateway の作成には AWS CLI を使う。少々ステップが多いが、CLI での作業結果を逐一 AWS コンソールで確認しながら作業を行うとわかりやすい。


3-4-1. API の作成

1.AWS CLI へログインする。

2.以下のコマンドで API4CiscoSpark という名の API を作成する。作成に成功すると API を識別するための API ID が生成される。

※ API が生成されるとルートリソースも同時に生成される。次の手順でルートリソースの子リソースを作成するが、そのためのはルートリソースの ID も必要。

$ aws apigateway create-rest-api \

--name API4CiscoSpark \
--region us-west-2 \
--profile adminuser
(以下実行結果)
{
"name": "API4CiscoSpark",
"createdDate": 1501839827,
"id": "" # API ID
}

AWS コンソールの表示結果(API Gateway):

API_Gateway_step1.png

3.API のルートリソースの ID を調べる:

$ aws apigateway get-resources \

--rest-api-id (API ID)
(以下実行結果)
{
"items": [
{
"path": "/",
"id": "" # ROOT RESOURCE ID
}
]
}


3-4-2. API 子リソースの作成

1.以下のコマンドで API4CiscoSpark API の子リソース Resource4CiscoSpark を作成する:

$ aws apigateway create-resource \

--rest-api-id (API ID) \
--parent-id (ROOT RESOURCE ID) \
--path-part Resource4CiscoSpark
(以下実行結果)
{
"pathPart": "Resource4CiscoSpark",
"parentId": "",
"path": "/Resource4CiscoSpark",
"id": "" # RESOURCE ID
}

AWS コンソールの表示結果(API Gateway):

API_Gateway_step2.png


3-4-3. メソッドの作成

1.以下のコマンドで Resource4CiscoSpark リソースに POST メソッドを追加する:

$ aws apigateway put-method \

--rest-api-id (API ID) \
--resource-id (RESOURCE ID) \
--http-method POST \
--authorization-type NONE

AWS コンソールの表示結果:

API_Gateway_step3.png


3-4-4. Lambda 関数を POST メソッドの送信先に設定

1.以下のコマンドで Lambda 関数を POST メソッドの送信先に設定する。(アカウント番号) には AWS のアカウント番号12桁の数字を入れる:

$ aws apigateway put-integration \

--rest-api-id (API ID) \
--resource-id (RESOURCE ID) \
--http-method POST \
--type AWS \
--integration-http-method POST \
--uri arn:aws:apigateway:(region):lambda:path/2015-03-31/functions/arn:aws:lambda:(region):(アカウント番号):function:LF4CiscoSpark/invocations

AWS コンソールの表示結果(API Gateway):

API_Gateway_step4.png


3-4-5. POST メソッドのレスポンスを JSON に設定

1.以下のコマンドで POST メソッドのレスポンスを JSON に設定する。この設定により API メソッドが返すレスポンスのタイプが指定される。

$ aws apigateway put-method-response \

--rest-api-id (API ID) \
--resource-id (RESOURCE ID) \
--http-method POST \
--status-code 200 \
--response-models "{\"application/json\": \"Empty\"}"


3-4-6. POST メソッドの統合レスポンスを JSON に設定

1.以下のコマンドで POST メソッドの統合レスポンスを JSON に設定する。この設定により

Lambda 関数が返すレスポンスのタイプを指定している。

$ aws apigateway put-integration-response \

--rest-api-id (API ID) \
--resource-id (RESOURCE ID) \
--http-method POST \
--status-code 200 \
--response-templates "{\"application/json\": \"\"}"

AWS コンソールの表示結果(API Gateway):

API_Gateway_step5.png


3-4-7. API をデプロイする

1.次のコマンドで、作成した API を prod (プロダクション=本番環境)というステージにデプロイする:

$ aws apigateway create-deployment \

--rest-api-id (API ID) \
--stage-name prod

AWS コンソールの表示結果(API Gateway):

API_Gateway_step6.png

「URL の呼び出し」の右側に記載されている URL が Cisco Spark の Webhook 用 Target URL となる。


3-4-8. Lambda のトリガーとして API Gateway を指定する

$ aws lambda add-permission \

--function-name LF4CiscoSpark \
--statement-id apigateway \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:us-west-2:(アカウント番号):(API ID)/prod/POST/Resource4CiscoSpark"

AWS コンソールの表示結果(AWS Lambda):

API_Gateway_step7.png


4. 設定の完了と動作確認


4-1. Cisco Spark で Webhook を設定

Webhook を設定するには、HTTP PUT を以下の条件で実行すればよい:


  • URL:



  • ヘッダ


    • 'Authorization' : 'Bearer ' + Bot のアクセスコード

    • 'Content-Type' : 'application/json' を指定



  • ペイロード


    • 'name' : webhook の名前を任意でつける

    • 'targetUrl' : Amazon API Gateway で設定した Target URL

    • 'resource' : 'message' を指定する

    • 'event' : 今回は webhook を作るので 'created' を指定する

    • 'filter' : 'roomId=' に続けて Bot の所属する roomId を指定する



roomId はこちらから調査可能。

API_Gateway_step11.png

import requests

access_code = '' # ここに Access Code を入力

url = 'https://api.ciscospark.com/v1/webhooks'
headers = {
'Authorization' : 'Bearer ' + access_code,
'Content-Type' : 'application/json'
}

r = requests.put(url, headers = headers)

{
'name' : 'My Awesome Webhook',
'targetUrl' : 'https://example.com/mywebhook',
'resource' : 'messages',
'event' : 'created',
'filter' : 'roomId=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
}


4-2. 動作確認

Cisco Spark にて Bot にメンションして "ping" と入力すると、Bot が "pong" と返してくれる。

API_Gateway_step10.png


(補足)API Gateway のログを生成するには

1.AWS コンソールで API Gateway サービスを開く。

2.「API名(API4CiscoSpark)」>「ステージ」>「prod」とクリックして「設定」タブを表示する。

3.「CloudWatch ログを有効化」にチェックを入れる。「ログレベル」を設定する。

4.「変更を保存」

API_Gateway_step8.png

5.CloudWatch にてログを確認可能