lambda
Slack
APIGateway

slackのincoming webhookからoutgoing webhookに移行するついでにサーバレス化したお話し

More than 1 year has passed since last update.

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-apigateway-slack.png

はじめにLambda関数を作成する

AWSコンソールから、はじめてAWS Lambdaにアクセスするとこんな画面がでてきます。「今すぐ始める」をクリックしましょう。

はじめだけ表示される画面

lambda_get_start.png

次に設計図の選択画面が出るので、ランタイムの選択やフィルターを駆使して目的の設計図(テンプレート)を探しましょう。最終的に「Blank Function」ばかりつかうことになるでしょう。

関数の作成を行うとはじめに表示される画面

lambda_get_template.png

フィルターのテキストボックスに「hello」と入力すると、「hello-world」が出てきます。こちらを使って入門編を進めても良いです。

lambda_set_filter.png

一度でも関数を作ったことがある場合は、AWS Lambdaの画面はここからになります。「Lambda関数の作成」ボタンからいつでも新しいLambda関数を作成することができます。

lambda_dashboard.png

設計図の選択で「次へ」ボタンを押すと、トリガーの設定画面に遷移します。ここで連携するAWSサービスを選びますが、今回はAPI Gateway側からLambda関数を選択するので、スキップしても構いません。

lambda_set_trigger.png

トリガーの設定画面からもAPI Gatewayを選ぶことができますが、今回はこのトリガーを使いません。

lambda_set_api_gateway.png

トリガーを設定してもしなくても「次へ」を押して関数をコーディングできる画面に遷移します。

lambda_set_iam.png

関数の設定画面

こちらで名前とランタイムを入力します。
名前は適当に「slackAPI」、ランタイムには「Node.js 6.10」を選択しました。

lambda_set_function.png

ランダムにテキストを返す関数のコード

そして、コードテキストエリアに、以下のコードを記述します。
res.usernameはslack上のbot名になります。
arrayにランダムに選択される文章をカンマ区切りで記述しています。

index.js
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」のままで、ロールに「テンプレートから新しいロールを作成」を選択、ロール名に任意のロール名を入力して、「次へ」ボタンをクリックします。

lambda_handler_role.png

作成した関数の確認画面が表示されるので、「関数の作成」ボタンをクリックします。

lambda_function_create.png

関数のテスト

関数のテスト画面が表示されるので、テストデータを準備して、テストを実行してもよいですが、ここでは省略します。

API Gatewayを作成する

api_gateway_menu.png

AWSコンソールのメニューからアプリケーションサービスにある「API Gateway」をクリックして、API GatewayのGetting Start画面を表示します。中央の「APIの作成」ボタンをクリックすると「新しいAPIの作成」画面に遷移します。

api_gateway_create.png

API名を入力して、「APIの作成」ボタンをクリックします。
すると、メソッドが空の状態のリソースが表示されます。

api_gateway_root.png

アクションから「リソースの作成」をクリックして、新しい子リソースの「リソース名」に任意の名前を入力します。※ここでは例として「start」を入力しています。

api_gateway_resource_create.png

次にアクションから「メソッドの作成」をクリックして、プルダウンから「POST」を選択してチェックマーク(レ点)をクリックします。

api_gateway_method_create.png

次にリージョンを選択します。(東京リージョンはap-northeast-1です)

api_gateway_region.png

そして、表示されるプルダウンから、作成済みのLambda関数名を入力して「保存」ボタンをクリックします。※Lambda関数名は途中まで入力すると補完されます。

api_gateway_function_select.png

権限を追加するか確認のダイアログが表示されるので、「OK」をクリックします。

api_gateway_function_kakunin.png

すると作成API Gatewayのメソッドの実行画面が表示されるので、「統合リクエスト」のリンクをクリックします。

api_gateway_function_setting.png

統合リクエストの設定画面が表示されるので、「本文マッピングテンプレート」をクリックして設定画面を表示させます。

api_gateway_request_setting.png

「+マッピングテンプレートの追加」リンクをクリックして、「application/json」と記入例が書かれたテキストボックスに「application/x-www-form-urlencoded」と入力してから、チェックマーク(レ点)をクリックします。

api_gateway_maping_template.png

「パススルー動作の変更」確認画面が表示されるので、「はい」をクリックします。

api_gateway_maping_template_confirm.png

コードエディタが表示されるので、以下のコードを記入して「保存」をクリックします。

api_gateway_maping_codeeditor.png

form-urlencoded形式をjson形式に変換するコード

このコードでは、「application/x-www-form-urlencoded」の時は json 型に変換するマッピングを定義しています。

mapping_template.txt
## 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_gateway_deploy.png

APIのデプロイを行うステージを入力する画面が表示されます。

api_gateway_deploy_stage1.png

「デプロイされるステージ」で「[新しいステージ]」を選択すると入力欄が増えます。

api_gateway_deploy_stage2.png

ここでは仮に「beta」と入力しました。※説明欄は任意入力です。

api_gateway_deploy_stage3.png

「デプロイ」ボタンをクリックするとAPIのデプロイが実行され、水色背景の箇所にURLが表示されます。これをslackの設定時に使用するのでメモしておきます。

api_gateway_deploy_fin.png

slackの設定

slackチームのapp or integrateから遷移するAPP Browse画面の検索ボックスに「outgoing」と入力して、「Outgoing Webhooks」を選択します。

slack_app_browse.png

表示されたOutgonig Webhooksの画面より、緑色の「Add Configuration」ボタンをクリックします。

slack_app_outgouing.png

次に「Add Outgoing Webhooks integration」ボタンをクリックして、詳細設定画面へ遷移します。

slack_app_outgouing_integration.png

「Outgoing Payload and Responses」の右側にある「Close」ボタンをクリックします。

slack_app_outgouing_setting3.png

「Outgoing Payload and Responses」の次の項の「Integration Settings」で、「Trigger Word(s)」にbotに反応してもらいたい文字列と「URL(s)」にAPI Gatewayでデプロイ後にメモしたURLを入力します。

slack_app_outgouing_integrate_url.png

例として「Trigger Word(s)」に「lambda」、「URL(s)」にメモしたURLを入力しました。入力したら、最下段の「Save Settings」ボタンをクリックします。

slack_app_outgouing_integrate_url2.png

試してみましょう!

lambda_apigateway_slack.png

できましたね!!