社内のハンズオン向けに書いていた資料ですが、まぁ Public Qiita でも差し支えない内容だったしそれなりに良い内容にまとまったと思ったので公開します。
※ 筆者の Go 力は「たったの 5 か、ゴミめ」レベルなので解説とか間違いの指摘大歓迎
背景
ChatOps したい。したくない?
Go で Slack bot 作ると結構簡単に ChatOps できるよ。
使うもの
- Go 1.12
- Slack (Bots)
- macOS 10.14.5
ハンズオン
Go をインストールしよう
筆者は公式サイト派。.pkg なのでダブルクリックするだけ。
https://golang.org/dl/
$ go version
go version go1.12.5 darwin/amd64
一瞬。
Homebrew でもインストールできるらしいがやってない。
Go で Hello, World してみよう
まぁ定番ですよね。
インタプリタは無いので、vim で書きます。
vim じゃない人は知りません、お使いのエディタで書いてください。
package main
import "fmt"
func main() {
fmt.Println("Hello, world.")
}
go run で実行。
$ go run hello.go
Hello, world.
簡単ですね。
ビルドもしてみましょう。
go build
$ go build hello.go
$ ls
hello hello.go
$ ./hello
Hello, world.
とっても簡単ですね。
ちなみにちょっと間違えてみましょう。
1 package main
2
3 import "fmt"
4
5 func main() {
6 hello = "Hello, world."
7 fmt.Println(hello)
8 }
$ go build hello.go
# command-line-arguments
./hello.go:6:2: undefined: hello
./hello.go:7:14: undefined: hello
hello
は宣言されていない変数のため、値の代入時に「hello
なんて宣言されてないよ」と怒られています(6 行目)。
また、宣言できていないため、7 行目の fmt.Println
での呼び出しについても怒られています。
コンパイルは実行前にエラーが分かっていいですね。
ちょっと解説
package
は Go で名前空間を定義するために使うようです。
一つのファイルに全部書いちゃうなら、main だけでいいですね。
http://cuto.unirita.co.jp/gostudy/post/go-package/
余談
筆者は vim-go 使ってます。
が、特にこれと言って機能を使い込んでいるわけではありません。
ハイライトがいい感じです。
https://github.com/fatih/vim-go
Slack bot を使う準備をしよう
そんなに(bot 自身に)尖った機能をつける気はないので、Apps から Bots の Integration を追加しましょう。
名前には日本語は(今は)使えません(前は使えたと思うんだけど・・・)
- 追加完了したら API Token を控えましょう(漏れないよう注意!)
- emoji とか icon とかいい感じに設定しましょう
- 説明を入れましょう
- 雑すぎる説明だと管理者にうっかり消されちゃうかも知れません
- API Token を利用した実行 IP が固定されてるならついでに IP 制限を設定しておきましょう
Bot で Slack に Hello, World してみよう
API Token さえ手に入ればあとはやりたい放題です。
まずは Slack bot をいい感じに使えるパッケージを入れましょう。
今回使うのはこちらです。
https://github.com/nlopes/slack
※ 同じディレクトリに複数の main() は存在できないので、さっきの hello は消しておきましょう。(ディレクトリを変えるのでも化)
まずは(API をうっかりお漏らししたくないので)他のファイルに書いて、import するようにします。
$ ls
bots.go token
$ ls token/
slack.go
$ cat token/slack.go
package token
const Slack = "INPUT YOUR SLACK TOKEN" ←ここに書いておく
ついでにいつうっかり git push してもいいように ignore しておきましょう。
$ cat .gitignore
token/*
これでまぁ間違っても漏らさないでしょう。
(git の管理とか Token の管理は、漏洩しなければ好きにしてもらって構いません。もっといい方法をご存知であれば教えてください。)
指定のチャンネルにメッセージをポストしてみます。
package main
import (
"./token"
"github.com/nlopes/slack"
)
func main() {
api := slack.New(token.Slack)
channel := "Input your channel name(or ID)"
value := "hello, world!"
api.PostMessage(channel, slack.MsgOptionText(value, false))
}
怒られましたね。
$ go run bots.go
bots.go:5:2: cannot find package "github.com/nlopes/slack" in any of:
/usr/local/go/src/github.com/nlopes/slack (from $GOROOT)
/Users/hogehoge/go/src/github.com/nlopes/slack (from $GOPATH)
インストールしていないパッケージについてはこのように怒られます。
慌てず騒がず go get しましょう。
$ go get github.com/nlopes/slack
もう一度。
$ go run bots.go
slack にメッセージが投稿されました
Bot をチャンネルに常駐させて特定のワードに反応させてみよう
※ 先に、ターゲットとなるチャンネルに作成した bot を invite しておいてください。
このへんから Go の良さみを感じられます。
いい感じにコードを書きます。
package main
import (
"./token"
"github.com/nlopes/slack"
"log"
"os"
"strings"
)
func run(api *slack.Client) int {
rtm := api.NewRTM()
go rtm.ManageConnection()
for {
msg := <-rtm.IncomingEvents
log.Printf("MSG: %#v\n", msg.Data)
switch ev := msg.Data.(type) {
case *slack.HelloEvent:
log.Printf("Start up!")
case *slack.MessageEvent:
if strings.Contains(ev.Text, "おはよう") {
text := "Good morning!"
rtm.SendMessage(rtm.NewOutgoingMessage(text, ev.Channel))
}
}
}
}
func main() {
api := slack.New(token.Slack)
os.Exit(run(api))
}
実行します。
$ go run bots.go
2019/05/30 17:51:06 MSG: &slack.ConnectingEvent{Attempt:1, ConnectionCount:0}
2019/05/30 17:51:08 MSG: &slack.ConnectedEvent{ConnectionCount:0, Info:(*slack.Info)(0xc0000)}
2019/05/30 17:51:08 MSG: &slack.HelloEvent{}
2019/05/30 17:51:08 Start up!
bot が反応して挨拶してくれました
これでいくらでも人工無脳が作れますね。
slackbot の1機能を車輪の再開発できました。
ちょっと解説
13 go rtm.ManageConnection()
ここはいわゆる goroutine というやつですね。
ここから非同期で rtm.ManageConnection()
が実行されるので、落ちるまで Slack のコネクションが張ってもらえる感じです。たぶん。
15 for {
16 msg := <-rtm.IncomingEvents
17 log.Printf("MSG: %#v\n", msg.Data)
18
19 switch ev := msg.Data.(type) {
20 case *slack.HelloEvent:
21 log.Printf("Start up!")
22
23 case *slack.MessageEvent:
24 if strings.Contains(ev.Text, "おはよう") {
25 text := "Good morning!"
26 rtm.SendMessage(rtm.NewOutgoingMessage(text, ev.Channel))
27 }
28 }
29 }
30 }
無限ループでイベントを監視し続けています。
19 switch ev := msg.Data.(type) {
20 case *slack.HelloEvent:
というふうにして、msg の型に応じて処理を分けられます。
どんなイベントがあるかはソース読むとかなんとかすればわかると思います。たぶん。
https://github.com/nlopes/slack/tree/95b04eeaeb73ff58f430062cd0d39592e03d01ad/slackevents
社内サーバでデーモンとして実行させて常駐させよう
社内サーバがどうなっているかはわからないので、
「Centos デーモン化」
とかそんな感じでググってどうにかしてください。
ただし、Go ではクロスコンパイルが容易にできるので
$ GOOS=linux GOARCH=amd64 go build bots.go
$ ls
bots bots.go token
とかやるだけで簡単に Linux 用の実行ファイルがシングルバイナリでできちゃいます。
あとはこれ(bots
)をサーバに配布して実行するだけ。とっても簡単ですね。
小ネタ
せっかくスラッシュコマンドではなく Bot にしたんだから何か話しかけてる感欲しい!
と思ったときは、MessageEvents の Message の先頭に @bots
があるかどうか、みたいな判定をしてトリガーにしています。
30 case *slack.MessageEvent:
31 log.Printf("Message: %#v\n", ev)
32 message := ""
33
34 if strings.Contains(ev.Text, isuzu) {
このときの isuzu
には Bot の UserID(装飾済み)を記述しています。
18 isuzu := "<@hogehoge>"
Bot が受け取るメッセージではメンションが上記のように記載されているためです。
Bot の ID 取得するのにも API が使えますので、活用してみてください。
まとめ
Go で bot を作るとサーバに余計なものインストールしないで実行可能なバイナリを作れるので、社内 bot 作るのにとても便利です。
スラッシュコマンドと違って、誰かに使ってもらえてるってのがすごく可視化されるのと、ちょっとした単純なコールアンドレタスポン酢単語へのレスポンスを登録しておくだけで他の人へ挨拶したりして愛着が湧くので、とてもメンテナンスモチベーションが湧きます。
Slack bot おすすめですよ。