この記事について
slackのスタンプでTrelloにカード追加を追加します。
lambdaを使ったサーバーレス構成の勉強もかねてやってみました。
全体図
サーバー(AWS)側の設定
lambdaの作成
AWSのアプリケーションで、「Lambda」を選択して、「関数の作成」を選びます。
適当な名前をつけて、ぽちぽちで作成できます。
rubyが好きなので、rubyで書きます。
lambdaが作成されます。
中身は変えずに、そのままにしておきます。
API-Gatewayの作成
lambdaは、サーバーレスでロジックを実行することができますが、
このロジックを実行するためのリクエストURLが必要です。そこにAPI-Gatewayが利用できます。
これもぽちぽちのみで完了します。
lambdaのコンソールから、自動で生成できます。
「トリガーを追加」をクリック
「Api Gateway」を選択して、セキュリティは「オープン」を選択して、追加します。
疎通の確認
- API-Gatawayを開くと、先ほど作成したlambdaと同名のapiが作成されています。
- 名前をクリックして、URLとルートを確認します。
- コンソールでリクエストしてみる
"Hello from Lambda!"と表示されれば成功です。
$curl https://{API ID}.execute-api.ap-northeast-1.amazonaws.com/slack-reaction-trello
"Hello from Lambda!"%
Slack Events APIの設定
Appの作成
slackで何かしらのアクションを拾う場合は、Events APIを使います。
https://api.slack.com/apps にアクセスして、「Create New App」を押します。
以下の2つを入力して「OK」します。
・ App名(適当)
・ workspace
登録できると、こんな画面が表示されますので、「Event Subscriptions」を選択します。
challenge認証を成功させる
「Event Subscriptions」の画面で、URLを単純に入力すると、エラー
Event APIを作成する際、challenge認証が要求されます。
lambda側で、毎度異なった値が送信されている、"challenge"パラメータをresponseとして返却する必要があります。
requestのbodyは、引数の"event"内に格納されていますので、そちらを返却値にセットするだけです。
def lambda_handler(event:, context:)
# 送信されたbodyをそのまま返却する
{ statusCode: 200, body: JSON.generate(event["body"]) }
end
bot eventsの追加
どんなeventが発生したら、このAppを起動するかを設定してきます。
今回は、スタンプによるeventの発火を狙いたいので、
「Subscribe to bot events」内で、reaction_add
を選択して、save changesします。
eventが発火されるかの確認
するとworkspace上で、上記のbotをチャンネルに招待することができるようになります。
このbotが招待されているチャンネルのみで、このeventは発火されるようになります。
早速チャンネルに、botを招待してみます。
/invite @{bot名}
lambdaコンソール > モニタリング > cludwatchLogsのログを確認
で、実行ログを確認できるので見てみます。
新たなログが作成されていました。
イベントの発火もこれで完了です。
Trello API用のtokenの準備
アクセスキーの準備
下記の3つを準備します。
こちらの記事を参考にさせていただきました。
https://qiita.com/isseium/items/8eebac5b79ff6ed1a180
- TrelloAPIの access_token
- TrelloAPIの secret_key
- 追加したいリストの list_id
試しにカードを作成させてみる
ここまできたら、Trelloにカードが追加できるようになります。
lambdaのロジックを修正して、カードを作成させます。
require 'json'
require 'net/http' #追加
def lambda_handler(event:, context:)
params = { #追加
key: ENV['trello_access_key'],
token: ENV['trello_access_token'],
idList: ENV['trello_target_list_id'],
name: 'slackのスタンプによるカード追加'
}
uri = URI.parse("https://trello.com/1/cards") #追加
response = Net::HTTP.post_form(uri, params) #追加
{ statusCode: 200, body: JSON.generate(event["body"]) }
end
再度、同じチャンネルでスタンプを押してみます。
Trelloにカードが追加されれば、成功です。
slack APIによるメッセージ・スレッドの取得
あとはイベントからメッセージを取得して、TrelloAPI叩けば完了だー。わーい。
意気揚々と、送信されてきたeventのパラーメータを確認すると、、、
// id系はxxxxでマスクしています。
{
"token"=>"xxxx",
"team_id"=>"xxxx",
"api_app_id"=>"xxxx",
"event"=>
{
"type"=>"reaction_added",
"user"=>"xxxx",
"item"=>
{
"type"=>"message",
"channel"=>"xxxx",
"ts"=>"1591069623.000700"
},
"reaction"=>"trello",
"event_ts"=>"1591071843.001100"
},
"type"=>"event_callback",
"event_id"=>"xxxx",
"event_time"=>1591071843,
"authed_users"=>["xxxx"]
}
そう、メッセージの情報は含まれていないのです。。。😭
ついでに先に言ってしまうと、メッセージにスタンプが押された場合と、スレッドにスタンプが押された場合で内容の取得方法が異なっていました。
なのでメッセージの取得をできるようにします。
権限の追加
「OAuth & Permissions」のページ内、「Scopes」にchannels:history
を追加します。
※もし、privateチャンネルで利用したい場合はgroups:history
の追加が必要です。
slack access-tokenの取得
OAuth Access Token
をcopyして、trelloのキーと同じように、lambdaの環境変数に保存します。
lambdaでskack apiを利用してメッセージの内容を取得する
結果だけ記載します。
綺麗に書き直してはいないので、ご容赦ください。。。
require 'json'
require 'net/http'
def lambda_handler(event:, context:)
event_hash = JSON.parse(event['body'])
# trelloスタンプの場合に実行する
stamp = event_hash['event']['reaction']
if stamp == 'trello'
slack_channel_id = event_hash['event']['item']['channel']
#slackメッセージ取得
slack_uri = URI.parse('https://slack.com/api/channels.history')
slack_params = {
channel: slack_channel_id,
token: ENV['slack_access_token'],
oldest: event_hash['event']['item']['ts'],
latest: event_hash['event']['item']['ts'],
inclusive: true
}
slack_res = Net::HTTP.post_form(slack_uri, slack_params)
slack_res_hash = JSON.parse(slack_res.body)
# 取得できなかったらスレッドなので再度取得
unless slack_res_hash['ok']
slack_uri = URI.parse('https://slack.com/api/conversations.replies')
slack_params = {
channel: slack_channel_id,
token: ENV['slack_access_token'],
ts: event_hash['event']['item']['ts']
}
slack_res = Net::HTTP.post_form(slack_uri, slack_params)
slack_res_hash = JSON.parse(slack_res.body)
end
params = {
key: ENV['trello_access_key'],
token: ENV['trello_access_token'],
idList: ENV['trello_target_list_id'],
name: slack_res_hash['messages'].first['text']
}
uri = URI.parse("https://trello.com/1/cards")
response = Net::HTTP.post_form(uri, params)
end
{ statusCode: 200, body: JSON.generate(event["body"]) }
end
動作確認
以上で、スタンプを押した時に、Trelloにカードを追加できるようになりました!
動作を確認してみましょう。
メッセージと、スレッドに、それぞれtrelloスタンプを押します。
無事、Trelloにカードが追加されました。
注意など
lambdaの冪等生性について
上記で、カードを追加できるように放ったのですが運用しているとカードが2つ作成される現状が見られました。
調べてみると、こちらはLambdaの仕様で、Lambdaの実行は最低1回以上実行されることが保証されているだけであって、処理の冪等性については、サーバーロジック側で担保が必要とのことでした。
DynamoDBなどを利用して、タイムスタンプでゴニョゴニョするのが良いらしいです。
アプリケーションにおける不整合性とデータ損失を防ぐために Lambda 関数を冪等にするにはどうすればよいですか?
今回の場合は、別に2つ作成されても消せば良いのでそこまでの対応はしませんでした。