devひよこのあしあと

いつでもひよっこな気持ちで学びと挑戦を

初心者なのでAPI Gateway+LambdaなSlack Botで円の面積を求めてみた

整数で値をひとつ読み込み、それを半径とする円の面積を求めて表示するプログラムを作成しなさい。 円周率は3.14とし、計算結果は、小数第2位を四捨五入して小数第一位まで表示すること。

知恵袋方面に上記のプログラムをJavaScriptで書きたい人がいるという噂を聞きました。というわけで、AWSAPI GatewayとLambdaを利用して書いてみましょう。API GatewayもLambdaもNode.jsも初心者ですが、がんばります!

元ネタ:

qiita.com

Slack Bot「parvati」の作成

まずはSlack Bot本体を作成します。AWS Lambdaと連携したSlack Botを作るnpmモジュールが公開されていたのでこれを利用してみます。

qiita.com

GitHub - mumoshu/lambda_bot

まずはnpm初期化と必要なnpmモジュールのインストールをします。

$ npm init -y
Wrote to /Users/d-tasaki/dev/parvati/package.json:

$ npm install --save lambda_bot node-env-file bluebird
npm WARN package.json parvati@1.0.0 No description
npm WARN package.json parvati@1.0.0 No repository field.
npm WARN package.json parvati@1.0.0 No README data
lambda_bot@1.0.0 node_modules/lambda_bot

node-env-file@0.1.8 node_modules/node-env-file

bluebird@3.3.4 node_modules/bluebird

lambda_botのモジュールからサンプルをコピーしてきます。

$ cp node_modules/lambda_bot/example_bot.js ./index.js

コピーしてきたサンプルを元に今回の要件に合わせてロジックを修正します。

// NODE_ROOT/index.js

var LambdaBot = require('lambda_bot');
var env = require('node-env-file');

env(__dirname + '/.env');

var bot = new LambdaBot({
    iconEmoji: process.env['SLACK_ICON_EMOJI'],
    userName: process.env['SLACK_USER_NAME'],
    channelName: process.env['SLACK_CHANNEL_NAME'],
    slackIncomingWebhookURL: process.env['SLACK_INCOMING_WEBHOOK_URL']
});

var round = function(x) {
    return Math.round(x * 10) / 10.0;
};

bot.respond(/parvati:?\s*([\d\.]+)/, function(res) {
    const pi = 3.14;
    var r = parseFloat(res.match[1]);
    var area = round(r * r * pi);

    return res.reply(area);
});

exports.handler = bot.createHandler();

SlackのIncoming WebHookの登録

計算した円の面積をSlackのチャンネル上で回答を投稿するためにIncoming WebHookを登録します。

f:id:devchick:20160314233342p:plain

SlackのApp DirectoryからIncoming WebHookを選択します。

f:id:devchick:20160314233708p:plain

回答を投稿するチャンネルを指定します。

f:id:devchick:20160314233909p:plain

WebHook URLというモノが表示されますので、これをローカルコンソールの環境変数に登録します。 私の場合は普段direnvを利用しているので、ローカルの.envrcに登録して環境変数に反映されるようにしました。

# NODE_ROOT/.envrc

export SLACK_INCOMING_WEBHOOK_URL='https://hooks.slack.com/services/XXXXXX/XXXXXXXXXXXXXXXXXXXX'
export SLACK_CHANNEL_NAME='times_tasaki'
export SLACK_USER_NAME='parvati'
export SLACK_ICON_EMOJI=':parvati:'

AWSアカウントの開設

AWS LambdaとAPI Gatewayを利用するためにAWSアカウントを開設します。

f:id:devchick:20160314230344p:plain

メールアドレスとパスワードを入力します。

f:id:devchick:20160314230555p:plain

連絡先情報を入力します。

f:id:devchick:20160314230741p:plain

お支払い情報を入力します。

f:id:devchick:20160314230855p:plain

電話を使って本人確認します。

f:id:devchick:20160314231111p:plain

サポートプランを選択します。

これでアカウント開設は完了です。

Lambda関数の登録用のアクセスキーの作成

lambda_bot npmモジュールではLambda関数のデプロイ作業もやってくれる機能があります。中身を見るとどうやらaws cliを実行しているようなので、ローカルPCからaws cliでlambda create-functionなどが利用できるようにIAMユーザーを作成してアクセスキー&シークレットキーを取得します。

f:id:devchick:20160314231643p:plain

IAMの画面のUsersメニューを開き、Create New Userをクリックします。

f:id:devchick:20160314232013p:plain

ユーザー名は適当にuploaderさんとしてCreateをクリックします。

f:id:devchick:20160314232158p:plain

表示されたアクセスキーとシークレットキーをメモします。

アクセスキー&シークレットキーは環境変数に登録するか、~/.aws/credentialsにdefaultプロファイルとして登録します。 先述の通りdirenvを利用していますので、.envrcに追加登録します。

# NODE_ROOT/.envrc

export SLACK_INCOMING_WEBHOOK_URL='https://hooks.slack.com/services/XXXXXX/XXXXXXXXXXXXXXXXXXXX'
export SLACK_CHANNEL_NAME='times_tasaki'
export SLACK_USER_NAME='parvati'
export SLACK_ICON_EMOJI=':parvati:'

export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXX
export AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
export AWS_REGION=ap-northeast-1

f:id:devchick:20160314232848p:plain

作成したIAMユーザーにはAWSLambdaFullAccessポリシーを適用しておきます。

Lambda実行用のIAMロールの作成

Lambda関数が実行できるように権限を付与したIAMロールを作成します。

f:id:devchick:20160314235503p:plain

lambda_botのデプロイコマンドの中身を見るとlambda_basic_executionという名前のロールを探しているようなので、この名前で作成します。

f:id:devchick:20160314235602p:plain

ロールタイプにはAWS Lambdaを選択します。

f:id:devchick:20160314235709p:plain

適用するポリシーにはAWSLambdaBasicExecutionRoleを選択します。

Lambda関数の登録

さていよいよLambdaにNodeモジュールを登録します。

登録するLambda関数名とハンドラーを環境変数に登録します。例によって.envrcに追加登録です。最終的に.envrcは下記の内容となりました。

# NODE_ROOT/.envrc

export SLACK_INCOMING_WEBHOOK_URL='https://hooks.slack.com/services/XXXXXX/XXXXXXXXXXXXXXXXXXXX'
export SLACK_CHANNEL_NAME='times_tasaki'
export SLACK_USER_NAME='parvati'
export SLACK_ICON_EMOJI=':parvati:'

export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXX
export AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
export AWS_REGION=ap-northeast-1

export LAMBDA_FUNCTION_NAME='parvati_bot'
export LAMBDA_HANDLER='index.handler'

これらの環境変数を利用してlambda_botのデプロイコマンドを実行します。

$ ./node_modules/lambda_bot/bin/lambda_bot deploy
Archiving a lambda function.
Uploading the lambda function.
The lambda function named parvati_bot does not exist. Creating.
Testing the lambda function.

おぉー! なんかできたっぽい。念のためaws cliで確認してみます。

$ aws lambda list-functions --region ap-northeast-1
{
    "Functions": [
        {
            "Version": "$LATEST",
            "CodeSha256": "MpF1uD2maCj4bGsDv9GcggbjkqC7/1/YVtbWDNa7/60=",
            "FunctionName": "parvati_bot",
            "MemorySize": 128,
            "CodeSize": 177250,
            "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXX:function:parvati_bot",
            "Handler": "index.handler",
            "Role": "arn:aws:iam::XXXXXXXXXXXXX:role/lambda_basic_execution",
            "Timeout": 3,
            "LastModified": "2016-03-14T12:35:37.110+0000",
            "Runtime": "nodejs",
            "Description": ""
        }
    ]
}

API Gateway

次はLambda関数を発火させるAPI Gatewayを設定します。

f:id:devchick:20160315000342p:plain

API名はなんか適当に。

f:id:devchick:20160315000535p:plain

なんかよくわからないけど/にPOSTメソッドAPIを作成したらいいらしい。

f:id:devchick:20160315000656p:plain

Lambda Functionを選択肢、先ほど作成したLambda関数名を指定します。

f:id:devchick:20160315000944p:plain

Lambda関数呼び出すための権限付与すんぜ!!? とかなんかきかれるので渋々OKします。

f:id:devchick:20160315001321p:plain

するとこんな画面が表示されます。なんかAPI GatewayとLambdaが連携できたっぽい感じになりました。

ただ、この状態だとAPI GatewayにはSlackからの通知はContent-typeがapplication/x-www-formurlencodedとして渡ってきます。一方、LambdaはJSON形式を想定しているので途中で変換する必要があるとかないとか。

それを上記画面のIntegration Requestというところで指定します。

f:id:devchick:20160315001903p:plain

画面下部のMapping TemplateというところでContent-typeのところにapplication/x-www-formurlencodedします。すると右側になんか入力するフォームが表示されるので、そこに以下のコードを入力します。

#set($httpPost = $input.path('$').split("&"))

{
#foreach( $keyValue in $httpPost )
 #set($data = $keyValue.split("="))
 "$util.urlDecode($data[0])" : "$util.urlDecode($data[1])"#if( $foreach.hasNext ),#end
#end
}

初めて見る書式ですが、VTL(Velocity Template Language)とかいう言語らしいです。

f:id:devchick:20160315003000p:plain

APIをデプロイします。ステージ名は適当に。

f:id:devchick:20160315003248p:plain

Invoke URLというものが表示されるのでこれをメモっておきます。

SlackのOutgoing WebHookの登録

最後はSlack上で入力された文字列をAPI Gatewayに通知するための設定です。

SlackのApp DirectoryからOutgoing WebHookを選択します。

f:id:devchick:20160315003426p:plain

監視するチャンネルを選択し、URLに先ほどメモっておいたAPI GatewayのInvoke URLを入力します。

ここでTrigger WordというのにBot名を指定しておきます。これを指定していないと全部の発言がLambdaに通知されます。今回は要件にないし、AWSの無料枠での運用なので少しでも稼働を少なくさせるために指定しました。

円の面積は?

SlackでBotに向かって発言してみます。

f:id:devchick:20160315004218p:plain

やった!初心者だけど出来ちゃった!

参考