AWS LambdaとAPI Gatewayで匿名Slack投稿APIをつくってみた(JAWS Framework)


背景、きっかけ

  • 最近周りでSlackの導入が増えており、エンジニア以外でもSlackを使う人が増えてきた
  • ただ、どうしてもエンジニア同士の会話が中心になってしまいSlackに慣れてない人は投稿する際のハードルが高そう
  • 現状の問題点やサービス改善につながるアイデアやを持っていたとしても、投稿ハードルが高いのが理由で周りに共有されないのはもったいない
  • 匿名で気軽に投稿できるようにすれば、投稿のハードルを下がり改善につながるかもしれない(仮説)

ということで、 匿名でSlack投稿ができる機能をつくってみました。

また、技術面でのきっかけとしては、先日クラスメソッドさん主催のモバイルバックエンド勉強会に参加した際に、コマンドラインから手軽にLambdaやAPI Gatewayの操作ができるjaws-frameworkの存在を知ったので何かつくってみたいと思った、というのがあります。

なお、現状は単に機能をつくっただけの状態で実践導入はしていないので、匿名投稿できるようにすることで効果があるのかは分かりません 🙇

MMMさんの「社内向け Slack 匿名投稿ルームをやめた」という記事ではコミュニケーションが活発になったけど業務改善の提案はなかった、ということが書かれており、様々な環境で試して効果の検証や知見の共有ができればいいなぁと思います。

完成イメージ

Lambda + API Gatewayで匿名Slack投稿APIを作り、Chrome拡張から匿名投稿APIを呼び出すようにしてみました。(使い方に関してはこちら

Chromeのメニューバーに匿名Slack投稿アイコンが追加されます。

slack1

文字入力して投稿を行うとSlackに匿名投稿されます。

slack2

「わざわざAPIを用意しなくても、直接Chrome拡張からSlackAPIを呼び出して投稿すればいいのでは?」という疑問が湧くと思いますが、例えば投稿先チャンネルを変えたいとなった時に、直接SlackAPIを呼び出すChrome拡張の場合だとSlackAPIのURLを変更して、Chrome拡張を配布した全端末に再インストールしてもらう必要があるので使う人数が多いと運用が大変になるかなと思いました。

API Gateway経由にしておくことで、チャンネルが変更になった際はAPI Gatewayが内部で呼び出すLambdaの設定を変更すればよく、Chrome拡張が呼び出すAPIのURL自体は変化しないのでChrome拡張側に手を加える必要がありません。(作ってから思った後付けの理由です)

前置きがだいぶ長くなってしまいましたが、それではjaws-frameworkを使ってAPIをつくってみます。

確認環境

node: v0.12.2
jaws: 1.3.3

今回はjaws1.3.3で試しましたが、近日中にjaws1.4のアップデートがあるらしいので1.4が出たらまた試してみます。

Lambda + API GatewayでSlack匿名投稿APIを作成

  • JAWSのインストール
npm install jaws-framework -g
  • プロジェクト作成
jaws project create

       ____   _____  __      __  _________
      |    | /  _  \/  \    /  \/   _____/
      |    |/  /_\  \   \/\/   /\_____  \
  /\__|    /    |    \        / /        \
  \________\____|__  /\__/\__/ /_________/ v1 (BETA)

       *** The Server-Less Framework ***

JAWS: Enter a project name:  (jaws-xxxxxx) AnonymouSlack
JAWS: Enter a project domain (You can change this at any time:   (myapp.com)
JAWS: Enter an email to use for AWS alarms:  (you@yourapp.com) 
JAWS: Enter a stage for this project:  (dev)
JAWS: Select a region for your project:
    us-east-1
    us-west-2
    eu-west-1
  > ap-northeast-1
JAWS: Select an AWS profile for your project:
  > default
JAWS: Creating CloudFormation Stack for your new project (5 mins)...
JAWS: Preparing your runtime and installing jaws-core module...
jaws-core-js@0.0.2 node_modules/jaws-core-js
└── dotenv@1.2.0
JAWS: Your project "AnonymouSlack" has been successfully created in the current directory.
cd AnonymouSlack
npm install slack-node --save
  • モジュール作成
jaws module create slack post
  • 環境変数にAPIトークンと投稿先チャンネルを設定

事前にSlack Web APIでAPIトークンの作成を行っておきます。

今回は例として#anonymous_diaryチャンネルに匿名投稿するようにしてみます。

jaws env set dev ap-northeast-1 API_TOKEN your_api_token
jaws env set dev ap-northeast-1 CHANNEL '#anonymous_diary'

環境変数でチャンネルとトークン情報を管理しておくことでコードを変更せずに設定変更できます。

環境変数の確認は以下のコマンドでできます。

jaws env list dev ap-northeast-1
  • awsm.jsonの編集

自動生成されたawsm.jsonを一部編集して、lambda側でtextパラメータを受け取れるようにしています。

# aws_modules/slack/post/awsm.json
{
  "lambda": {
    "envVars": [],
    "deploy": true,
    "package": {
      "optimize": {
        "builder": "browserify",
        "minify": true,
        "ignore": [],
        "exclude": [
          "aws-sdk"
        ],
        "includePaths": [
          "node_modules/slack-node"
        ]
      },
      "excludePatterns": []
    },
    "cloudFormation": {
      "Description": "",
      "Handler": "aws_modules/slack/post/handler.handler",
      "MemorySize": 1024,
      "Runtime": "nodejs",
      "Timeout": 6
    }
  },
  "apiGateway": {
    "deploy": false,
    "cloudFormation": {
      "Type": "AWS",
      "Path": "slack/post",
      "Method": "POST",
      "AuthorizationType": "none",
      "ApiKeyRequired": true,   # 現状はtrueにしても反応しない(1.4で対応されるとのこと)
      "RequestTemplates": {
        "application/json": "{\"text\":\"$input.path('$').text\"}" # lambda側でtextパラメータを受け取れるように
      },
      "RequestParameters": {},
      "Responses": {
        "400": {
          "statusCode": "400"
        },
        "default": {
          "statusCode": "200",
          "responseParameters": {},
          "responseModels": {},
          "responseTemplates": {
            "application/json": ""
          }
        }
      }
    }
  }
}

ところどころにコメントを書いていますが、実際のjsonではコメントはエラーになるのでコメント部分は削除してください。

  • lambdaのロジックを作成

slack-nodeのサンプルをちょっといじって作りました。

# aws_modules/slack/post/index.js

/**
 * AWS Module: Action: Modularized Code
 */

var Slack = require('slack-node');

module.exports.run = function(event, context, cb) {
  var apiToken = process.env.API_TOKEN;
  var channel = process.env.CHANNEL;
  var icon_emoji = process.env.ICON_EMOJI || ':innocent:';
  var username = process.env.USERNAME || 'bot'

  var slack = new Slack(apiToken);
  var text = event.text;
  if (!text) {
    return cb(400, null);
  }
  slack.api('chat.postMessage', {
    text: text,
    channel: channel,
    icon_emoji: icon_emoji,
    username: username
  }, function(err, response){
    if (err) {
      return cb(null, err);
    }
    return cb(null, response);
  });
};

投稿ユーザ名やアイコン絵文字を変えたい場合は環境変数で設定できます。

jaws env set dev ap-northeast-1 USERNAME 'anonymous'
jaws env set dev ap-northeast-1 ICON_EMOJI ':smile:'

Slack APIの詳細に関しては公式ドキュメントを参照ください。

  • デプロイ

これで匿名投稿APIの設定ができたので、デプロイしてみます。

jaws dashと入力するとデプロイする項目の選択画面が表示されるので、それぞれの項目でEnterを入力して選択状態(黄色表示)にして、Deploy Selectedを選択。(jaws dashコマンドは1.4で別のものに置き換わるそうです)

jaws dash

JAWS: Dashboard for project "AnonymouSlack"
 -------------------------------------------
 Project Summary
 -------------------------------------------
    Stages:
       dev ap-northeast-1
    Lambdas: 1
    Endpoints: 1
 -------------------------------------------
 Select Resources To Deploy
 -------------------------------------------
    slack/post
      L) lSlackPost
      E) /slack/post - POST
    - - - - -
  >   Deploy Selected -->
JAWS: -------------------------------------------
JAWS:  Dashboard:  Deploying Lambdas...
JAWS: -------------------------------------------

JAWS: -------------------------------------------
JAWS:  Dashboard:  Deployments Completed
JAWS: -------------------------------------------

これでデプロイが完了しました。

マネージメントコンソールでの対応

ここまでJAWSを使ってコマンドラインから一通りの対応ができましたが、現状、jsからクロスドメイン通信を行うためのCORS設定とAPIアクセスを制限するためのAPIキー設定はJAWSからだとできなかったのでマネージメントコンソールで対応することにしました。

  • CORS設定

以前は手動でOPTIONメソッドやヘッダーを追加してCORS設定を行う必要があったのですが、先日のアップデートでワンクリックでCORS設定ができるようになりました。

該当APIのリソースを選択して「Enable CORS」を選択すると自動でCORS設定をしてくれます。

cors

(画像ではすでにOPTIONメソッドが作成されていますが、CORSを有効にすることでOPTIONメソッドが追加されます)

  • APIキーの作成と設定

ドキュメントに沿ってAPIキーを追加します。

API Gateway で API キーを使用する – Amazon API Gateway

APIキー追加後、このAPIキーを匿名投稿APIで使えるように関連付けておきます。

apikey

APIはChrome拡張設定時に使用するのでどこかにメモしておいてください。

最後にPOST APIのMethod Request設定で、API Key Requiredをtrueに変更し、「Deploy API」で手動デプロイを行います。

apikey2

これでCORS設定とAPIキー設定が有効になりました。

jaws dashでAPI Gatewayの設定をデプロイしてしまうと、またAPIキーの設定がfalseに戻ってしまうためご注意ください。

今回のAPIだと、API Gatewayの設定に関してはこれ以上変更点はないので、今後デプロイする必要はありません。

チャンネルやアイコンを変更したい場合はLambda側だけをデプロイすればできます。

使い方

APIの設定が完了したら、管理画面上に表示されている「Invoke URL」の部分をメモしておいてください。

apigateway

続いてChrome拡張の設定を行います。

ストア公開はしていないため、Githubから以下のChrome拡張をダウンロードしてください。

hilotter/AnonymouSlack

post.jsというjsファイルの以下の部分をそれぞれ先ほどメモした値に置き換えてください。

const API_KEY = "Your API Key";

url: 'https://your_api_gateway.amazonaws.com/dev/slack/post',

post.jsの編集後、Chromeの「ウインドウ」=>「拡張機能」で拡張管理画面を開き、そこにAnonymouSlackディレクトリをドラッグアンドドロップします。

そうするとChromeのメニューバーにアイコンが表示されるので、そこからAPIで指定したSlackチャンネルに匿名投稿できます。

匿名投稿できることが確認できたら、拡張をチームメンバーに配布し、それぞれの環境でインストールしてもらえば設置完了です。

まとめ

jaws-frameworkを使うことで手軽にマネージメントコンソールでポチポチ進めるのではなく、コードベースでAPIの実装を進めることができました。

API GatewayとLambdaを組み合わせれば手軽にAPIが作れるので便利ですね。

Chrome拡張に関しては現状は即席で作ったものなので見た目もいまいちなとりあえず動くもの状態になっています 😓

もしお使いいただけることがあればPullRequest等いただけると嬉しいです。

hilotter/AnonymouSlack

ちょっとした機能ですが、チームのコミュニケーション改善のきっかけになればいいなと思います。

参考