API Gateway + Lambda で slack
前回slack appを登録した続きからです。
slash commandとinteractive messageを利用して対話的にslackで処理を進めてみます。
lambdaの登録
まずは動作テストのためにapexのhelloを置いてしまいます。
apex deploy hello
で実行されるとテキストだけ出力するサンプルが配置されます。
API Gatewayの登録
/
に配置してしまいます。
- [アクション] -> [メソッドの作成] -> POSTを選んでチェックをクリック
- セットアップ画面になるので、先ほど作成したlambdaを選んで[保存]
- 権限許可確認画面がでるので[OK]
- メソッドの実行画面で[統合リクエスト]を選択
- [本文マッピングテンプレート]をクリックして詳細を出してから、[マッピングテンプレーとの追加] -> application/x-www-form-urlencoded を入力してチェックをクリック
- パススルー動作の変更画面がでるので [はい、この統合を保護します]
- POSTの中身をquerystringに押し込むテンプレートを記述して[保存]
前回は詳細を書きませんでしたが、これはvelocityのテンプレートです。awsで規定されている変数をvelocityのテンプレートを使ってlambdaに渡すことができるという仕組みです。
POSTのデータを展開して渡すことができるので下記を参考にしたテンプレートを配置しています。基本的に使うのは、querystring内に展開されるPOSTで渡されたパラメータです。
http://qiita.com/durosasaki/items/83af014aa85a0448770e
http://dev.classmethod.jp/cloud/aws/sugano-013-api-gateway/
#set($rawAPIData = $input.path('$'))
## escape any quotes
#set($rawAPIData = $rawAPIData.replace('"', '\"'))
## first we get the number of "&" in the string, this tells us if there is more than one key value pair
#set($countAmpersands = $rawAPIData.length() - $rawAPIData.replace("&", "").length())
## if there are no "&" at all then we have only one key value pair.
## we append an ampersand to the string so that we can tokenise it the same way as multiple kv pairs.
## the "empty" kv pair to the right of the ampersand will be ignored anyway.
#if ($countAmpersands == 0)
#set($rawPostData = $rawAPIData + "&")
#end
## now we tokenise using the ampersand(s)
#set($tokenisedAmpersand = $rawAPIData.split("&"))
## we set up a variable to hold the valid key value pairs
#set($tokenisedEquals = [])
## now we set up a loop to find the valid key value pairs, which must contain only one "="
#foreach( $kvPair in $tokenisedAmpersand )
#set($countEquals = $kvPair.length() - $kvPair.replace("=", "").length())
#if ($countEquals == 1)
#set($kvTokenised = $kvPair.split("="))
#if ($kvTokenised[0].length() > 0)
## we found a valid key value pair. add it to the list.
#set($devNull = $tokenisedEquals.add($kvPair))
#end
#end
#end
{
"headers" : {
#foreach( $key in $input.params().header.keySet() )
"$key" : "$input.params().header.get($key)"#if( $foreach.hasNext ),#end
#end
},
"querystring" : {
#foreach( $kvPair in $tokenisedEquals )
## finally we output the JSON for this pair and append a comma if this isn't the last pair
#set($kvTokenised = $kvPair.split("="))
#if($kvTokenised.size() == 2 && $kvTokenised[1].length() > 0)
#set($kvValue = $kvTokenised[1])
#else
#set($kvValue = "")
#end
#if( $foreach.hasNext )
#set($itemDelimiter = ",")
#else
#set($itemDelimiter = "")
#end
"$kvTokenised[0]" : "$kvValue"$itemDelimiter
#end
},
"stage" : "$context.stage",
"sourceIp" : "$context.identity.sourceIp",
"userAgent" : "$context.identity.userAgent"
}
- デプロイしないと反映されないので[アクション] -> [APIのデプロイ] -> APIのデプロイウィンドウがでるので、ステージ名を前回作ったものを選択して[デプロイ]
- URLの呼び出し:で示されるURLを取っておきます
slash commandを登録
slack appの画面から[slash commands]を選択して、[Create New Command]を選びます。
この時点で、API gatewayにapplication/x-www-form-urlencoded
でPOSTすると、helloworldが帰ってくるはずです。
$ curl -XPOST -d "test" [API gatewayのURL]
{"hello":"world"}
slack appの設定
- 作成したslack appの[Slash Commands]から[Create New Command]
- コマンドを登録する
登録するコマンド名をCommand
、Request URL
にAPI Gatewayで控えたURL、Short Description
に適当な説明を入れて[Save]
slackで/hello
したら返事が返って来れば成功です。
tokenチェックの追加
このままではcurl
でテストした通り、どこからでもURLを叩けば結果を出してしまいます。
hello
のように特に意味のない結果を返すだけなら問題ないですが、作ったslack appの通知だということを確認するためにtokenをチェックするようにします。
tokenは作成したslack appのclient id
やclient secret
が記載されていた、[Basic Infomation]のVerification Token
に記載されています。
これが、slackからPOSTされた時のパラメータ
のtokenとして送られてくるので、文字列が一致するかチェックするだけです。
他、使用するユーザやチャンネルを制限したいなら、user_id
やchannel_id
をチェックしたりすればいいことになります。
interactive message
interactive messageとは、slackのドキュメントに乗っているものを見てもらうのが一番早いかと思います。
ボタンを表示して、押したボタンに対してさらに反応させることができるものです。
これを利用すると、jenkinsでやっていたようなデプロイの実行などをslackで完結させることが可能になります。
slack appの設定
interactive message用に別のlambdaを作ってもいいですが、ここでは同じものを使いまわします。
- 登録したslack appの[Interactive Messages]から[Enable Interactive Messages]
- [Request URL]にAPI Gatewayで控えたURLを入れて[Enable Interative Messages]
lambda functionの変更
slackのドキュメント通り、返却するメッセージにactionを付け加えます。
console.log('starting function')
exports.handle = function(e, ctx, cb) {
console.log('processing event: %j', e)
cb(null, { text: "button", attachments: [{ "callback_id": "test", actions: [{name: "test", text: "Test", type: "button", value: "test"}] }] } )
}
apex deploy hello
でデプロイして準備完了です。
実行してみる
slackで /hello
を実行すると下記のようになるはずです。
この[Test]ボタンが押せるようになっています。押すと、ボタンは消えてしまうはずです。
ボタンを押した際に、interactive messagesで登録したURLにリクエストが飛んでおり、その返答が表示されているのですが、今回はボタンを押した際にも同じcallback_id
のボタンを出しているので、ボタンが表示されない結果になっています。
あとは、ボタンを押した時に渡されるパラメータ
に従って処理を進めていけば対話的にslack上で処理を組み立てることができます。
注意点として、slash commandの場合と異なり、payloadパラメータにURLエンコードされたjsonでデータが渡されるので、slash commandと同じlambdaを使う場合は、payloadパラメータの有無で処理を分けるといいでしょう。
TIPS
3秒以上かかる処理
slash commandsの最初の応答は3秒以内ではないとエラーになる仕様です。その場合、非同期で別のlambdaを呼びだしてあげたりすることで回避します。
呼び出されたlambdaでは、response_urlパラメータに記載されているURLに対して、メッセージをPOSTします。そうすればメッセージが表示されるようになります。response_urlは30分以内で5回までしか利用できません。
メッセージのの表示非表示
コマンドを実行したときに、本人にしか見えないメッセージかどうかは response_type
がin_channel
かephemeral
かで決まります。デフォルトはephemeral
なので本人にしか表示されません。
response_url
を使って複数回メッセージを送ると、デフォルトではメッセージを書き変えますが、メッセージを書き変えずに新しく途中からメッセージを表示するようにしたりといったことができます。