こんにちは。
…さて、日が経ってしまいましたが、前回 の続きでAPI GatewayからSlack通知を行った作業ログを残していきたいと思います。先週のうちに書いておく予定だった!!
前回の記事
API Gateway->Lambdaを経てSlackのWebhookURLへ送信する-その1- – Shimabox Blog
やったこと
- API Gatewayでエンドポイントを作成 ← 今回の対象
- API GatewayのトリガーとしてLambdaを作成 ← 今回の対象
- このLambdaからSlackのWebhookURLに送信 ← 今回の対象
- Slack Appの
Incoming Webhooks
を利用 ← 前回の記事参照
- Slack Appの
直接、WebhookURLにcurlで送ればいいじゃん!!と思うかもしれませんが、まぁ、あれです、素振りってやつです。大事なことなので前回に引き続き2回。
じゃあやってみましょう!今回はプログラミングしていきますよ!
免責
今回はAWSでの作業もありますが、お試し実装ということもありPowerUserAccess
,IAMFullAccess
で作っているので、そこらへんは察してください。
本来であれば権限を絞って設定するのがいいです。
APIGatewayとLambdaの使用なので、そんなにお金はかからないと思いますが、無茶はやめておきましょう。
AWSでの作業
ユーザーを用意
とりあえず作業用ユーザー、ユーザーグループを作成します。
上でも述べましたが、ユーザーグループにはPowerUserAccess, IAMFullAccessを付与しています。
作業用ユーザー(api-gateway-user
としました)は、このユーザーグループに追加しておきます。
以降、このユーザーで作業をしていきます。
ロールを作成
同じく、IAMからロールの作成より以下のロールを作成します。
Lambdaを作るときに、お任せするのもありですが名前をつけておきます。
- ロール名
- 任意
- ここでは、
api-gateway-to-lambda-role
としています
- 許可ポリシー
AWSLambdaBasicExecutionRole
Lambdaの作成
APIGatewayに紐付けるLambdaを作成します。
関数作成
- Lambda -> 関数の作成 を開きます
- 以下を入力し、関数の作成 をクリックします
- 関数の作成は、
一から作成
を選択 - 関数名
- 任意の関数名を入力
- ここでは、
notification-to-slack
としました
- ランタイム
Go 1.x
を選択
- アーキテクチャ
x86_64
を選択
- デフォルトの実行ロールの変更
-
既存のロールを使用する
を選択し、ロールを作成
で作成したロール(api-gateway-to-lambda-role)を選択
-
- 関数の作成は、
ハンドラ名修正
※ ビルド時に、バイナリ名をhello
にして利用するのならこの作業は要りません
ランタイム設定の編集から、
ハンドラ名を main
に変えて保存します。
環境変数の設定
環境変数の編集より、
以下の環境変数を設定して、保存します。
- キー
- SLACK_WEBHOOK_URL
- 値
Incoming Webhooks
で発行したURL- 前回の記事 を参考にしてみてください
いったんここで、Lambdaの作成は止めておきます。あとでソースを書きます。
APIGatewayの作成
構築
API Gateway -> APIを作成 を開き、APIタイプを選択
より、HTTP API
を選択し、構築
をクリックします。
REST API
使ったほうがいいのでは?と思うかもしれませんが、パッと試すためHTTP API
を利用します。
APIの作成
以下を入力し、次へをクリックします。
- 統合を追加
- Lambda を選択
- Lambda関数
- Lambda関数の作成で作成したLambda関数(notification-to-slack)を選択
- API名
- 任意の名前を入力
- ここでは、
notification-to-slack-api
としました
ルートを設定
ステージを定義
お試しなのでステージ名そのまま、自動デプロイを有効にしたまま、次へをクリックします。
開発用、本番用など分けたい場合は、自動デプロイは外して名前をつけておくといいと思います。
確認して作成
これで、APIGatewayの作成は完了です。
Lambdaの確認
作成したLambdaに戻り、設定 -> トリガー を確認してみると、上記で作成したAPIGatewayと紐づいていることがわかると思います。エンドポイントの確認も出来ますね。
Lambda関数の作成
いよいよ、Lambda関数の作成を行います。Goで書きます。
Goのバージョンは、1.20.4
です。
go version go version go1.20.4 darwin/amd64
前準備
適当にディレクトリを作って、
mkdir notification-to-slack cd notification-to-slack
go mod init しておきます。
go mod init notification-to-slack go: creating new go.mod: module notification-to-slack
Go の AWS Lambda 関数ハンドラー – AWS Lambda に則り、github.com/aws/aws-lambda-go/lambda
をインストールしておきます。
go get github.com/aws/aws-lambda-go/lambda go: downloading github.com/aws/aws-lambda-go v1.41.0 go: added github.com/aws/aws-lambda-go v1.41.0
events package – github.com/aws/aws-lambda-go/events – Go Packages
コード
以下のjsonがPOSTされることを想定したコードを書きます。
{ "name": "shimabox", "favorite_foods": [ "ゴーヤーチャンプル", "マンゴー" ] }
main.go
main.go
を作成します。
package main import ( "bytes" "encoding/json" "errors" "fmt" "io" "net/http" "os" "strings" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) type myEvent struct { Name string `json:"name"` FavoriteFoods list `json:"favorite_foods"` } type list []string type requestBody struct { Blocks []block `json:"blocks"` } type block struct { Type string `json:"type"` Text textType `json:"text"` } type textType struct { Type string `json:"type"` Text string `json:"text"` } func handler(r events.APIGatewayProxyResponse) error { // BodyにPOSTデータが入っているので構造体に変換 var event myEvent err := json.Unmarshal([]byte(r.Body), &event) if err != nil { return err } // Slackへ送信するリクエストボディを作成 body := &requestBody{ Blocks: []block{ { Type: "section", Text: textType{ Type: "mrkdwn", Text: fmt.Sprintf("こんにちは `%v` さん:smile:", event.Name), }, }, { Type: "section", Text: textType{ Type: "plain_text", Text: "そうですか、", }, }, { Type: "section", Text: textType{ Type: "plain_text", Text: toListNotation(event.FavoriteFoods), }, }, { Type: "section", Text: textType{ Type: "plain_text", Text: "が、好きなんですね!", }, }, }, } bodyBytes, err := json.Marshal(body) if err != nil { return err } // 環境変数からWebhook用エンドポイントを取得 endpoint := os.Getenv("SLACK_WEBHOOK_URL") // リクエストを生成 req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(bodyBytes)) if err != nil { return err } // Headerつけてリクエストを投げる req.Header.Add("Content-Type", "application/json") res, err := http.DefaultClient.Do(req) if err != nil { return err } defer res.Body.Close() // Slackからのレスポンス判定 if res.StatusCode != http.StatusOK { b, err := io.ReadAll(res.Body) if err != nil { return err } return errors.New(fmt.Sprintf("error!! code: %d, body: %v", res.StatusCode, string(b))) } return nil } func toListNotation(list list) string { l := len(list) values := make([]string, 0, l) for i := 0; i < l; i++ { values = append( values, fmt.Sprintf(`• %v`, list[i]), ) } return strings.Join(values, "\n") } func main() { lambda.Start(handler) }
返り値で、(events.APIGatewayProxyResponse error)
とすることもできますが、今回はSlackに送信するだけなので単純にerrorを返すだけにしています。
余談
リストのところは、マークダウンの-
でリスト表記出来るかと思いきやまさかの•
を書くという力技…
Block Kit Builder – Slack
ガチでやりたい場合は、以下の形式でSlackに投げるとインデントを指定してリスト表記できるようです。
{ "blocks": [ { "type": "rich_text", "elements": [ { "type": "rich_text_list", "elements": [ { "type": "rich_text_section", "elements": [ { "type": "text", "text": "test" } ] } ], "style": "bullet", "indent": 0 }, { "type": "rich_text_list", "elements": [ { "type": "rich_text_section", "elements": [ { "type": "text", "text": "test2" } ] } ], "style": "bullet", "indent": 1 } ] } ] }
参考: kotlin – How do I post a bulleted list using the slack api – Stack Overflow
Makefile
Makefile
を用意してビルドをちょっと楽にしておきます。
.PHONY: build build: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main main.go zip main.zip main
構成
構成はこんな感じです。
tree . ├── Makefile ├── go.mod ├── go.sum └── main.go
これでLambda関数の作成は完了です。
デプロイ
ビルド
goをビルドしてzipを作ります。
make build CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main main.go zip main.zip main adding: main (deflated 45%)
デプロイ
zipが出来たので、Lambdaを開いてコード
のアップロード元
から.zipファイル
を選択します。
アップロードから、zipファイルを選択し保存をクリックします。
関数 notification-to-slack が正常に更新されました。
と表示されればOKです。
動作確認
curlでAPIGatewayを叩く
準備が出来たのでコマンドを叩きます。
curl -X POST -H 'Content-type: application/json' \ --data ' { "name": "shimabox", "favorite_foods": [ "ゴーヤーチャンプル", "マンゴー" ] }' \ https://1234xxxxxx.execute-api.ap-northeast-1.amazonaws.com/notification-to-slack // APIGatewayのエンドポイント
null が返却されれば成功です。
{"message":"Internal Server Error"}
みたいなのが返ってきたらログ(CloudWatch)を確認してみてください。
CloudWatch
CloudWatchは以下で確認できると思います。
CloudWatch > ロググループ > /aws/lambda/notification-to-slack(Lambda名)
結果
slackに以下のように投稿されればOKです!
おわりに
というわけで、APIGatewayからLambdaを経由してSlackに投稿するまでを試してみました。
手順はちょっと多くて面倒かもです(AWSのところはCDKとかSAMとか使えばマシになるとは思います)が、1回用意してしまえば色々とお試し出来るのかなぁと思います!
(ちゃんと組むなら、VPCで制限とかIP制限とかしたほうがいいかなぁと思います。そのへん今度やってみよ〜。)