環境
Serverless Framework: 2.14.0
構成
Cloudwatch Eventsの定期実行でGoで記述されたLambdaを呼び出し、LambdaからSlackのincoming webhookを呼び出します。
実装
SlackのIncoming Webhook URLをドキュメントに従って取得します。
Serverless Framework
Serverless Frameworkを導入して、設定を記述していきます。
serverless create -u https://github.com/serverless/serverless-golang/ -p go-lambda-slack
go.modを作成して、必要なパッケージを導入します。
cd go-lambda-slack
go mod init go-lambda-slack
go get github.com/aws/aws-lambda-go
go get github.com/najeira/jpholiday
go get github.com/slack-go/slack
go get github.com/stretchr/testify
最終的なディレクトリ構成は以下のようにします。
.
├── README.md
├── config
│ └── setting.yml
├── main.go
├── go.mod
├── go.sum
├── notification
│ ├── notification.go
│ └── notification_test.go
└── serverless.yml
config/setting.yml
には先程取得したSlackのIncoming Webhook URLを記載します。このファイルは.gitignore
に記述してgitの管理から外したりする処置が必要です。
WEB_HOOK_URL: https://hooks.slack.com/services/xxxxxxxxx/xxxxxxxxx/xxxxxxxxx
serverless.yml
です。作成するLambda内で環境変数として扱えるよう、ファイルから読み出したURLを環境変数として設定しています。
CloudWatch Eventsでのcronの時間指定はJSTではなく、UTCになるので注意が必要です。今回は月曜の朝9時に実行されるよう設定しています。これが例えば、月曜の朝8時に実行したいという場合には、UTCでは日曜日の23時に実行する必要があります。
service: go-lambda-slack
provider:
name: aws
runtime: go1.x
stage: dev
region: ap-northeast-1
logRetentionInDays: 1
versionFunctions: false
package:
exclude:
- ./**
include:
- ./bin/**
functions:
notification:
handler: bin/main
environment: ${file(./config/setting.yml)}
events:
- schedule: cron(0 0 * * MON *)
Lambda
祝日の判定にはnajeira/jpholiday、Slackの送信にはslack-go/slackを使用します。 Goで祝日を扱うライブラリはいくつかあるようですが、2021東京オリンピック等の祝日判定に対応しているものを探してこちらを選択しました。振替休日にも対応されています。
// main.go
package main
import (
"fmt"
"os"
"go-lambda-slack/notification"
"github.com/aws/aws-lambda-go/lambda"
)
// Handler ...
func Handler() error {
url := os.Getenv("WEB_HOOK_URL")
err := notification.Send(url)
if err != nil {
fmt.Println(err)
}
return nil
}
func main() {
lambda.Start(Handler)
}
// notification/notification.go
package notification
import (
"fmt"
"time"
holiday "github.com/najeira/jpholiday"
"github.com/slack-go/slack"
)
// Send ...
func Send(url string) error {
today := time.Now()
if isHolyday(today) {
fmt.Println("今日は" + holiday.Name(today) + "!")
return nil
}
text := "<!here>\n今日は平日!"
msg := slack.WebhookMessage{
Text: text,
}
return slack.PostWebhook(url, &msg)
}
func isHolyday(t time.Time) bool {
return holiday.Name(t) != ""
}
祝日判定に問題ないかだけテストを行います。
// notification/notification_test.go
package notification
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestIsHolyday(t *testing.T) {
tests := []struct {
input time.Time
expected bool
}{
{time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC), true}, // 2021-01-01 元日
{time.Date(2021, time.February, 23, 0, 0, 0, 0, time.UTC), true}, // 2021-02-23 天皇誕生日
{time.Date(2021, time.July, 22, 0, 0, 0, 0, time.UTC), true}, // 2021-07-22 海の日
{time.Date(2021, time.July, 23, 0, 0, 0, 0, time.UTC), true}, // 2021-07-23 スポーツの日
{time.Date(2021, time.August, 8, 0, 0, 0, 0, time.UTC), true}, // 2021-08-08 山の日
{time.Date(2021, time.August, 9, 0, 0, 0, 0, time.UTC), true}, // 2021-08-09 山の日振替休日
{time.Date(2021, time.December, 23, 0, 0, 0, 0, time.UTC), false}, // 2021-12-23 天皇誕生日ではない
{time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC), true}, // 2022-01-01 元日
{time.Date(2022, time.July, 18, 0, 0, 0, 0, time.UTC), true}, // 2022-07-18 海の日
{time.Date(2022, time.July, 22, 0, 0, 0, 0, time.UTC), false}, // 2022-07-22 海の日ではない
{time.Date(2022, time.July, 23, 0, 0, 0, 0, time.UTC), false}, // 2022-07-23 スポーツの日ではない
{time.Date(2022, time.August, 8, 0, 0, 0, 0, time.UTC), false}, // 2022-08-08 山の日ではない
{time.Date(2022, time.August, 11, 0, 0, 0, 0, time.UTC), true}, // 2022-08-11 山の日
{time.Date(2022, time.October, 10, 0, 0, 0, 0, time.UTC), true}, // 2022-10-10 スポーツの日
}
for _, tt := range tests {
assert.Equal(t, isHolyday(tt.input), tt.expected)
}
}
$ go test ./notification/
ok go-lambda-slack/notification 0.530s
大丈夫そうですね。
デプロイ
Goのbuildを実行して、デバッグ用にcronの設定を毎分実行されるようにしてからデプロイを実行します。
GOOS=linux go build -o bin/main
sls deploy -v
本日は祝日ではないので、Slack通知が実行されます。
notification/notification.go
中のtoday
を2021/01/01に書き換えて再度デプロイしてみます。Slackの通知が来ず、CloudWatch Logsにメッセージが出力されているのが確認できます。
まとめ
祝日には実行されないSlackリマインダをCloudWatch Events+Lambda(Go)で作成しました。Serverless Frameworkだとサクッと作れて良いですね。