#はじめに
去年Amazon API Gatewayを触り始めてから、Slack、API Gateway、Lambda、EC2 Run commandを使って、EC2に対してSlackからシェルコマンドを送るようにやってみたことがあります。その後、SlackのChannelにAWS CLIを送ったら、Lambdaが実行してくれるといいなと思っていました。
LambdaでAWS CLIを実行する方法が既に知っていましたが、ずっとやれていなくて(最近仕事中に全然DeepDiveできなくて)、今回せっかく三連休ですので、SlackのChannelにAWS CLIを送って、Lambdaが実行するようにしてみました!
Qiita上には、既にSlackからAWS CLIを実行する投稿がありましたが、個人的もう少し簡単に実現したいので、自分のやり方でやりました。
今回は自分なりのやり方を皆さんに共有します。LambdaのコードはGitHubに上げておきました。
#概要
##利用するサービス
- Slack + Slash Command
- Amazon API Gateway
- Lambda( python + AWS CLI )
##全体像
何をどのようにやったかを簡単に説明しますと、
Slash CommandでSlackのChannelにAWS CLIのコマンド(message)を送ったら、
そのAWS CLIのコマンドがAPI Gateway経由でLambdaに送られ、
Lambdaが送信されたAWS CLIのコマンドを実行して、実行結果をSlackに返す
ということです。
全体像は図に示す通りです。
(なぜ、2つのLambdaを利用している理由はあとで説明します。)
#何が嬉しいのか
そもそも何でSlackのChannelからAWS CLIを実行できるようにしたいのかと言いますと、正直に言って、特に明確な目的がありません.......が、こんな嬉しいことがあると思っています。
- ローカル環境でAWS CLIを叩かなくてもいい
- Credencial情報をローカル環境に置かなくてもいいので、漏れる心配もない
- 複数メンバーがいる場合、AWS CLI実行環境を統一できる(かもしれません)
- PCであろうがスマホーであろうがタブレットであろうが、Slackがインストールされていれば、AWS CLIを使えます。まさに、Multi Device対応(凄くない?!)
#おさえておきたいこと
下記4点を是非皆さんにおさえておきたいです。
-
① Slash CommandsのTimeout時間は3000ms
一連の処理(特にLambda上のコマンド実行)が少し時間がかかりますので、Slash CommandsがTimeoutしないように、Front Lambdaで一旦Slackからの情報(AWS CLIのコマンドを含め)を受け取り、Slackに返信すると同時に、受け取った情報をExecution Lambdaに渡すようにしています。これが、2つのLambdaを利用している理由です。 -
② Slackから送信される情報
以下の情報がSlackからAPI Gatewayに送られますが、注意点2点あります.
- textの内容にspaceが入っている場合,space は + に置き換えられます
- response_urlはエンコードされますので、Lambdaでデコードする必要があります
token=3mVXdAC5EFNbsXYGNk0sah3M
team_id=T0001
team_domain=example
channel_id=C2147483705
channel_name=test
user_id=U2147483697
user_name=Steve
command=/weather
text=94070
response_url=https://hooks.slack.com/commands/1234/5678
- ③ API Gatewayでデータマッピング
Slackから送られてきた情報(文字列)をJSONにマッピングする必要があります。VTLという言語を使っているそうです。
マップピング用のテンプレートは以下になります。
## &区切りでPOSTされてきたデータを分割する
#set($httpPost = $input.path('$').split("&"))
## 出力されるJSONデータの生成
{
#foreach( $keyValue in $httpPost )
## "key=value"というデータをkeyとvalueに分ける
#set($data = $keyValue.split("="))
## JSONのフォーマットに整形する(まだ走査するデータがあれば","を挿入する)
"$data[0]" : "$data[1]"#if( $foreach.hasNext ),#end
#end
}
- ④ Lambda上のAWS CLI環境
デフォルトのLambdaの環境では、AWS CLIが入っていないので、AWS CLIをパッケージ化して、Lambdaにアップロードする必要があります。
やり方は【AWS Lambda Python で AWS CLI を実行する方法】にてご確認ください。
#設定手順
さって、さっそくSlackからAWS CLIを実行できるように設定していきましょう!特に難しいことはやっていませんので、ご安心ください!
##Lambda
今回権限まわりをちゃんと調査していないので、LambdaのRoleにPowerUserAccessをつけてあります。(自分はIAMにまだ弱いです)
###Front Lambda
詳細な作成手順は省きますが、1点だけ説明します。
Front Lambdaの役割は受付(情報を受け取って、Execution Lambdaに渡す)だけですので、利用するRoleは最小権限で良いと思っています。
Front Lambdaのコードは下記の通りです。
# -*- coding: utf-8 -*-
from __future__ import print_function
import boto3
import json
print('Loading function')
def lambda_handler(event, context):
print("Received event: " + json.dumps(event, indent=2))
#messageが "+"で連結されたため,"+"をスペースに戻す
event['text'] = event['text'].replace('+',' ')
clientLambda = boto3.client("lambda")
clientLambda.invoke(
#invokeするLambda Name
FunctionName="Execution-Lambda",
InvocationType="Event",
Payload=json.dumps(event)
)
return event # Echo back the first key value
###Execution Lambda
【AWS Lambda Python で AWS CLI を実行する方法】を参考に、やって頂ければ、LambdaでAWS CLIを実行できるようになりますので、詳細な手順は割愛します。2点だけ説明します。
- lambda_function.pyは記事の内容と違っています。
- Execution LambdaがAWS CLIを実行しますので、適切な権限を与える必要があります。
自分の場合は、検証ですので、PowerUserAccessを与えています。
lambda_function.pyのコードは下記の通りです。
# -*- coding: utf-8 -*-
import commands
import json
import os
from cStringIO import StringIO
import re
import urllib
from urllib2 import Request, urlopen, URLError, HTTPError
print('Loading function')
def _(cmd):
return commands.getoutput(cmd)
def lambda_handler(event, context):
print("Received event: " + json.dumps(event, indent=2))
#response_urlのデコード
response_url = urllib.unquote(event['response_url']).encode('raw_unicode_escape').decode('utf-8')
print(response_url)
slack_message = {
'channel': event['channel_name'],
'response_type': 'ephemeral',
'isDelayedResponse': 'true',
'text': "response for: `aws " + event['text'] + "`\n" + _("./aws " + event['text'])
}
req = Request(response_url)
req.add_header('Content-Type', 'application/json')
try:
response = urlopen(req, json.dumps(slack_message))
response.read()
print("Message posted to %s", slack_message['channel'])
except HTTPError as e:
print("Request failed: %d %s", e.code, e.reason)
except URLError as e:
print("Server connection failed: %s", e.reason)
##API Gateway
- 新しいAPIを作成します。
- 今回はAPI Nameをslackにします。 適宜変更してください。
-
「Lambda Function」をクリックして、Regionを選択します。
- 自分の場合は、Tokyo Regionのfront-lambdaを利用します。
- Permissionを聞かれますが、「OK」で良いです。
- 下記の画面が表示されますと、「Integration Request」をクリックします。
- 「Body Mapping Templates」にて、mapping templateを追加します。Request body passthroughはAWS推奨の項目にします。
- Content-Typeにapplication/x-www-form-urlencodedを追加します。
- templateを定義して、保存します。
- templateが保存されましたら、「Actions」の「Deploy API」を選択します。
- Stage nameは適宜でよいです。
##SlackのSlash Commands
Slack Commandsについては、SlackのDocumentation( Slack Commands )を確認してください。
-
Slash Commandsにアクセスして、新しいSlash Commandを作成します。
(例では、Slash Command /aws を作成します) -
URL欄にはAPI GatewayのInvoke URLを入力し、Method欄はPOSTにして、(残りの設定はお好みで)、「Save Integration」を押します。
-
SlackのSlash Commandsの設定は完了
#利用イメージ
SlackのChannelにAWS CLIを投げると、実行してくれます.
以下は利用例です。
- aws s3 ls
- aws ec2 describe-regions
#最後
SlackのChannelからAWS CLIを実行できるようになりました。AWS CLIのコマンドを投げて、実行結果を返されるまで、少し時間がかかってしまい、多少ストレスが感じるかもしれませんが、Serverless且つMulti DeviceでAWS CLIを実行できることが凄く便利だと思っています。是非、使ってみてください!
最後に1つ注意点だけ皆さんに伝えたいです、API Gatewayには認証とかできていないので、エンドポイント(Invoke URL)がバレてしまうと、だれでもAPIを叩ける状態になっています。ですので、少なくてもfront-lambdaに簡単な判断ロジックを入れいる必要があると思います。例えば、決められたSlackのChannel(team_id&channel_id)からのコマンドしか受け付けないようにすることです。
最後の最後となりますが、まだ実運用されていないので、正直に言って、どこまで実用性があるかはまだ分かりませんが、少なくても、飲み会のネタにはビッタリではないでしょうか。
今回はここまでにします。