slackのincoming webhookからoutgoing webhookに移行するついでにサーバレス化したお話し
背景
弊社のときふじが過去に「社WebをAWS s3 + cloudfrontなサーバーレス環境に移行した話」こんな知見を残していたことから、そろそろサーバレス化進めていくかと考えていました。
参考にしたURL
SlackからAWS API Gatewayを通してLambdaを起動するまで
Lambdaの使い方をローカルで試すのであればdocker-lambdaが早いです。
AWS Lambdaの開発環境を構築~docker-lambdaの紹介~
まずは概要図から
slackから特定の文字列を入力すると、outgoing webhookでAPI GatewayのURLにform-urlencodedで送信され、AWS Lambdaにjson形式でデータが渡り、関数を経由してjsonが返ってきます。返されたAPI Gatewayはそのままslackに送信してくるという仕様です。
※図の作成に使ったのはCacooです。
はじめにLambda関数を作成する
AWSコンソールから、はじめてAWS Lambdaにアクセスするとこんな画面がでてきます。「今すぐ始める」をクリックしましょう。
はじめだけ表示される画面
次に設計図の選択画面が出るので、ランタイムの選択やフィルターを駆使して目的の設計図(テンプレート)を探しましょう。最終的に「Blank Function」ばかりつかうことになるでしょう。
関数の作成を行うとはじめに表示される画面
フィルターのテキストボックスに「hello」と入力すると、「hello-world」が出てきます。こちらを使って入門編を進めても良いです。
一度でも関数を作ったことがある場合は、AWS Lambdaの画面はここからになります。「Lambda関数の作成」ボタンからいつでも新しいLambda関数を作成することができます。
設計図の選択で「次へ」ボタンを押すと、トリガーの設定画面に遷移します。ここで連携するAWSサービスを選びますが、今回はAPI Gateway側からLambda関数を選択するので、スキップしても構いません。
トリガーの設定画面からもAPI Gatewayを選ぶことができますが、今回はこのトリガーを使いません。
トリガーを設定してもしなくても「次へ」を押して関数をコーディングできる画面に遷移します。
関数の設定画面
こちらで名前とランタイムを入力します。
名前は適当に「slackAPI」、ランタイムには「Node.js 6.10」を選択しました。
ランダムにテキストを返す関数のコード
そして、コードテキストエリアに、以下のコードを記述します。
res.usernameはslack上のbot名になります。
arrayにランダムに選択される文章をカンマ区切りで記述しています。
exports.handler = (event, context, callback) => {
var res = {};
res.username = "outgoing-webhook-from-lambda";
var array = [
'わからないことは<https://api.slack.com/custom-integrations|こちら>を参照してください。',
'こんにちは、私は'+ res.username +'です。\nAWSのLambda functionで処理されています。\n',
'API Gatewayを使ってLambda functionを起動する良い練習になりますね。',
'outgoing-webhookを使用すればLambda functionをAPI Gateway経由でキックすることができます。'
];
res.text = array[Math.floor(Math.random() * array.length)];
callback(null, res);
};
Lambda 関数ハンドラおよびロールを設定する
関数の設定画面をもう少しスクロールすると、ハンドラとロールを設定する箇所が現れます。
ここでは、ハンドラは「index.handler」のままで、ロールに「テンプレートから新しいロールを作成」を選択、ロール名に任意のロール名を入力して、「次へ」ボタンをクリックします。
作成した関数の確認画面が表示されるので、「関数の作成」ボタンをクリックします。
関数のテスト
関数のテスト画面が表示されるので、テストデータを準備して、テストを実行してもよいですが、ここでは省略します。
API Gatewayを作成する
AWSコンソールのメニューからアプリケーションサービスにある「API Gateway」をクリックして、API GatewayのGetting Start画面を表示します。中央の「APIの作成」ボタンをクリックすると「新しいAPIの作成」画面に遷移します。
API名を入力して、「APIの作成」ボタンをクリックします。
すると、メソッドが空の状態のリソースが表示されます。
アクションから「リソースの作成」をクリックして、新しい子リソースの「リソース名」に任意の名前を入力します。※ここでは例として「start」を入力しています。
次にアクションから「メソッドの作成」をクリックして、プルダウンから「POST」を選択してチェックマーク(レ点)をクリックします。
次にリージョンを選択します。(東京リージョンはap-northeast-1です)
そして、表示されるプルダウンから、作成済みのLambda関数名を入力して「保存」ボタンをクリックします。※Lambda関数名は途中まで入力すると補完されます。
権限を追加するか確認のダイアログが表示されるので、「OK」をクリックします。
すると作成API Gatewayのメソッドの実行画面が表示されるので、「統合リクエスト」のリンクをクリックします。
統合リクエストの設定画面が表示されるので、「本文マッピングテンプレート」をクリックして設定画面を表示させます。
「+マッピングテンプレートの追加」リンクをクリックして、「application/json」と記入例が書かれたテキストボックスに「application/x-www-form-urlencoded」と入力してから、チェックマーク(レ点)をクリックします。
「パススルー動作の変更」確認画面が表示されるので、「はい」をクリックします。
コードエディタが表示されるので、以下のコードを記入して「保存」をクリックします。
form-urlencoded形式をjson形式に変換するコード
このコードでは、「application/x-www-form-urlencoded」の時は json 型に変換するマッピングを定義しています。
## convert HTML POST data or HTTP GET query string to JSON
## get the raw post data from the AWS built-in variable and give it a nicer name
#if ($context.httpMethod == "POST")
#set($rawAPIData = $input.path('$'))
#elseif ($context.httpMethod == "GET")
#set($rawAPIData = $input.params().querystring)
#set($rawAPIData = $rawAPIData.toString())
#set($rawAPIDataLength = $rawAPIData.length() - 1)
#set($rawAPIData = $rawAPIData.substring(1, $rawAPIDataLength))
#set($rawAPIData = $rawAPIData.replace(", ", "&"))
#else
#set($rawAPIData = "")
#end
## 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
## next we set up our loop inside the output structure "{" and "}"
{
#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("="))
"$util.urlDecode($kvTokenised[0])" : #if($kvTokenised[1].length() > 0)"$util.urlDecode($kvTokenised[1])"#{else}""#end#if( $foreach.hasNext ),#end
#end
}
APIをデプロイしてURLを取得
保存したらアクションから「APIのデプロイ」をクリックします。
APIのデプロイを行うステージを入力する画面が表示されます。
「デプロイされるステージ」で「[新しいステージ]」を選択すると入力欄が増えます。
ここでは仮に「beta」と入力しました。※説明欄は任意入力です。
「デプロイ」ボタンをクリックするとAPIのデプロイが実行され、水色背景の箇所にURLが表示されます。これをslackの設定時に使用するのでメモしておきます。
slackの設定
slackチームのapp or integrateから遷移するAPP Browse画面の検索ボックスに「outgoing」と入力して、「Outgoing Webhooks」を選択します。
表示されたOutgonig Webhooksの画面より、緑色の「Add Configuration」ボタンをクリックします。
次に「Add Outgoing Webhooks integration」ボタンをクリックして、詳細設定画面へ遷移します。
「Outgoing Payload and Responses」の右側にある「Close」ボタンをクリックします。
「Outgoing Payload and Responses」の次の項の「Integration Settings」で、「Trigger Word(s)」にbotに反応してもらいたい文字列と「URL(s)」にAPI Gatewayでデプロイ後にメモしたURLを入力します。
例として「Trigger Word(s)」に「lambda」、「URL(s)」にメモしたURLを入力しました。入力したら、最下段の「Save Settings」ボタンをクリックします。
試してみましょう!
できましたね!!