CINC Tech Blog

株式会社CINCのエンジニアチームが日々習得した技術やTipsを公開するブログです

SlackのSlash CommandsからLambdaを実行させる方法

開発部のFです。
弊社では開発部のコミュニケーションツールとしてSlackを導入しました。 Slackでは主にログや通知を受け取っているのですが、もっとSlackを活用したいということもありSlack apiのスラッシュコマンドを使ってアプリを作成しました。

今回はSlackのスラッシュコマンドからLambdaを通しS3にアクセスする方法について書きたいと思います。
あくまで最低限動くことを目的に書いていますので、セキュリティ等の考慮はしていませんので、ご了承ください。

Lambda関数の作成

まずは、Lambdaの関数を作成していきます。

「関数名」は任意の名前ですので、今回は「SlackApi」にしました。
「ランタイム」は「Node.js 8.10」を選択しました。
「実行ロール」には「LambdaFullAccess」のポリシーのみをアタッチしたロールを割り当てています。 (Lambdaの実行とS3への書き込み権限があれば大丈夫です。なければ、IAMから作成してください。)

f:id:coreinc:20190313174655p:plain

API Gatewayの設定

次に、API Gatewayの設定をしていきます。

「Protocol」は「REST」を選択、「新しいAPI」を選択し、「API名」に任意の名前を入力します。
今回はLambda関数と同じ「SlackApi」にしました。

f:id:coreinc:20190313175532p:plain

作成した後、「アクション」から「メソッドの作成」で「POST」を選択します。

セットアップは基本的にデフォルトで問題ないと思います。 「Lambda関数」には先ほど作成した「SlackApi」を選択します。

f:id:coreinc:20190313175649p:plain

「API Gateway」に、Lambda関数を呼び出す権限を与えても良いか聞かれるのでOKを押します。

これでAPI Gatewayは作成できました。

次に、「統合リクエスト」から「マッピングテンプレート」を設定していきます。
後述するSlack apiから送られてくるリクエストのContent-Typeapplication/x-www-form-urlencodedです。
このタイプはデフォルトではAPI GatewayからLambdaへ渡すことができません。 そのため、この形式を受け取れるように設定してあげる必要があります。

「マッピングテンプレートの追加」から「application/x-www-form-urlencoded」を設定し、 「テンプレートの生成」から「メソッドリクエストのパススルー」を選択すると下記のように記述されます。 設定ができたら保存してください。

f:id:coreinc:20190313175814p:plain

設定が完了したら、アクション > APIのデプロイを選択。
「デプロイされるステージ」に「新しいステージ」を選択し、ステージ名に任意の名前を入力し、「デプロイ」します。
デプロイが完了するとエンドポイントURLが表示されるかと思います。 こちらを後述のSlack apiで使っていきます。

Slack apiの作成

Slack apiにアクセスし、「Start Building」をクリック。
「App Name」には任意の名前、 「Development Slack Workspace」に「自身のワークスペース」を選択してください。

まずは、「Slash Commands」から、「Create New Command」をクリックします。

「Command」には任意のスラッシュコマンドを記載してください。今回は「/lambda」にしました。
「Request URL」には先程作成したAPI Gatewayで表示されたURLを指定してください。
「Short Description」には「スラッシュコマンドの説明」を記載してください。

その他のフォームはオプションですので、どちらでも大丈夫です。

f:id:coreinc:20190313182942j:plain

作成できましたら、「Install App」から自身のワークスペースにインストールして下さい。

これで、スラッシュコマンドを入力すると簡単なレスポンスを返す処理ができました。
Slackで/lambdaと入力すると、 {"statusCode":200,"body":"\"Hello from Lambda!\""}というレスポンスが返ってくるかと思います。

しかし、これだけではあまり役立たないでしょう。
そこで、LambdaからS3を操作する処理を加えていきたいと思います。

Lambdaの処理

1つ注意点として、Slack apiの仕様として、3秒以内にSlackへレスポンスを返さないとタイムアウトになってしまいます。 そのため、「S3を呼び出す処理」と「Slackへレスポンスを返す処理」を非同期に呼び出す必要があります。

Lambdaにはinvoke()という別のLambdaを同期・非同期で呼び出すことができるメソッドがありますので、今回はこちらを使用していきたいと思います。

Lambdaはインラインで編集もできますが、今回はnpmでモジュールをインストールして使用していますので、ローカルで編集してzipでアップロードする方法を取りたいと思います。

Slack api

作業スペースでnpm install aws-sdkを実行し、AWSの関数を実行するためのモジュールをインストールします。

次に、Slackのslash commandから呼び出すLambdaを作成します。(最初に作った関数です。)
index.jsというファイルを作成し、以下を記載します。

'use strict';

const AWS = require('aws-sdk');

exports.handler = (event, context) => {
  const options = {
    FunctionName: 'uploadToS3',
    InvocationType: 'Event',
  };

  const lambda = new AWS.Lambda();
  lambda.invoke(options, (err, data) => {
    if (err) {
      context.fail('Command failed');
    } else {
      context.succeed('Command Success!');
    }
  });
};

lambdainvoke()関数では、第1引数にオプション、第2引数にコールバック関数を渡します。

今回はオプションで2つのデータを渡しています。

FunctionNameでは、呼び出すLambdaの関数を指定しています。
InvocationTypeでは、呼び出すLambda関数を同期処理で呼ぶか非同期処理で呼ぶかを指定します。
デフォルトでは同期処理になっています。
こちらにEventを指定することで、非同期に呼び出すことができるようになります。

コールバック関数内では、次の処理を行っています。 context.failは失敗時にレスポンスを返すメソッドで、context.succeedは成功時にレスポンスを返すメソッドです。 ここのレスポンスがSlackへ送信されます。

処理が書けたら、index.jsnode_modulesをzip化し、SlackApiという名前のLambda関数にアップロードします。

uploadToS3

次に、SlackApi関数から呼ぶLambda関数を作成します。 今回は、uploadToS3という名前で作成しました。 その他の設定項目はSlackApiと同様で問題ありません。

こちらもローカルで作成していきますが、ファイル名は同じindex.jsで作成していきたいので、別のディレクトリ内に作った方が良いでしょう。

index.jsというファイルに、以下を記載します。

'use strict';

const AWS = require('aws-sdk');

exports.handler = (event, context) => {
  const s3 = new AWS.S3({
    apiVersion: '2006-03-01',
    accessKeyId: process.env['accessKeyID'], // AWSのアクセスキーID
    secretAccessKey: process.env['secretAccessKey'], // AWSのアクセスキー
    region: 'ap-northeast-1',
  });
  
  const params = {
    Bucket: 'fromslackapi',
    Key: 'slackapi.json', // ファイルの名前
    Body: '{ "foo": "bar" }', // ファイルの内容
    ContentType: 'application/json; charset=utf-8',
    ACL: 'private'
  };

  s3.putObject(params, (err, data) => {
    if (err) {
      console.log('エラーが発生しました。', err);
    } else {
      console.log(data);
    }
  });
};

こちらでは、SlackApi関数から呼ばれてS3にファイルをアップロードしています。

AWSのアクセスキーがない場合は、「IAM > ユーザー > アクセスキーの作成」から作成してください。
また、今回アクセスキーはLambdaの環境変数に指定しています。
process.env['キー名']とすることで環境変数の値を取得することができます。

このindex.jsと先ほどアップロードしたものと同じnode_modulesをzip化しアップロードします。

念の為、Lambda上でテストを実行しておきましょう。

まず、S3にアップロードする用にバケットを作成しておきましょう。
先のソースコードではfromslackapiという名前を指定していましたので、こちらで作成していきます。
オプションは基本的に何でも問題ありません。

「SlackApi」関数の「テストイベントの設定」から新しいテストイベントを作成していきます。 イベント名に「SlackApi」と入力し、作成を押します。

「テスト」を押すと「"Command Success!"」というレスポンスが返ってきているかと思います。

動作確認

では、実際にSlackからコマンドを実行してみましょう。

Slack上で「/lambda」と入力して、送信すると

f:id:coreinc:20190313181200p:plain

「Command Success!」と返信が返ってきます。

f:id:coreinc:20190313184147p:plain

S3にもちゃんとアップロードされていますね。 f:id:coreinc:20190313181312p:plain

まとめ

このようにLambdaを使えば簡単にSlackからアプリを実行させることができます。
今回は、S3にファイルを置くという単純な処理を書きましたが、実務でも使えるものもたくさんあるかと思います。
この記事が何かの役立てば幸いです。